Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Mobile Summit: Animating your way into Jetpack Compose

Mobile Summit: Animating your way into Jetpack Compose

PRESENTED AT:
Women Who Code Mobile Summit 2022

https://hopin.com/events/women-who-code-mobile-summit/registration

DATE:
July 27, 2022

DESCRIPTION:
Long gone are the days of having to implement inflexible animations along with their listeners and having to manage them in view lifecycles. By using Animation API from Jetpack Compose, to handle transitions/motion, and callbacks, you can now declaratively write concise maintainable animations for your apps. In this talk, you'll learn a variety of composable functions that can be applied to screens/views/custom shapes.

MORE TALKS & ARTICLES FROM ME: https://cupsofcode.com/talks/

Aida Issayeva

July 27, 2022
Tweet

More Decks by Aida Issayeva

Other Decks in Programming

Transcript

  1. Animating your
    way into Jetpack
    Compose
    Aida Issayeva

    View Slide

  2. Aida Issayeva
    Senior Software Engineer
    Google Developer Expert for Android
    WomenTechmakers' ambassador
    @aida_isay

    View Slide

  3. Table of
    Contents
    Why we animate
    Animations in Android
    Jetpack Compose
    Animations in Jetpack Compose
    High-level animations
    Customizing animations

    View Slide

  4. 📣
    Attention
    👀
    Feedback

    Interface
    🎉
    Fun

    Wait
    Why we animate?

    View Slide

  5. What we get back?
    📈
    Higher
    Conversion
    👏
    Better
    usability
    💁
    More patient
    users
    💫
    Smoother
    experience

    View Slide

  6. Animations in Android

    View Slide

  7. Animations in View UI system
    1. Running two different Animation
    APIs simultaneously
    pain points
    2. Declaring animations in a separate
    resource file
    3. Requiring a boilerplate of code for
    simple animations
    4. Having a separate APIs in each
    component for the same
    animations

    View Slide

  8. “Jetpack
    Compose is
    Android’s
    modern toolkit
    for building
    native UI”
    https://developer.android.com/jetpack/compose/why-adopt
    “Jetpack Compose is
    Android’s modern toolkit
    for building native UI”

    View Slide

  9. Imperative vs Declarative
    Declarative
    Imperative
    How to do things

    Defining variables and changing
    their values

    Statements

    What to do

    Evaluating results based on input

    Expressions

    View Slide

  10. Animations in Jetpack Compose
    Low-Level APIs
    High-Level APIs


    View Slide

  11. High-Level Animation APIs
    disclaimer: some of them are experimental
    Easy to use Minimum
    actions
    Common
    patterns
    Material Design
    best practices


    View Slide

  12. High-Level Animation APIs
    5. animate*AsState
    6. updateTransition 7. rememberInfiniteTransition
    1. AnimatedVisibility 2. Crossfade 3. AnimatedContent
    4. Modifier.animatedContentSize

    View Slide

  13. AnimatedVisibility
    @Composable
    fun AnimatedVisibility
    (
    visible: Boolean,
    modifier: Modifier = Modifier,
    enter: EnterTransition = fadeIn() + expandIn(),
    exit: ExitTransition = shrinkOut() + fadeOut(),
    label: String = "AnimatedVisibility"
    ,
    content: @Composable() AnimatedVisibilityScope
    .() -> Unit
    ) {
    val transition = updateTransition(visible, label)
    AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
    }
    * source code

    View Slide

  14. AnimatedVisibility
    @Composable
    fun AnimatedVisibility
    (
    visible: Boolean,
    modifier: Modifier = Modifier,
    enter: EnterTransition = fadeIn() + expandIn(),
    exit: ExitTransition = shrinkOut() + fadeOut(),
    label: String = "AnimatedVisibility"
    ,
    content: @Composable() AnimatedVisibilityScope.() -> Unit
    ) {
    val transition = updateTransition(visible, label)
    AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
    }
    * source code

    View Slide

  15. AnimatedVisibility
    var toShow by remember {
    mutableStateOf(false)
    }
    Column {
    AnimatedVisibility(visible = toShow) {
    PlantCell()
    }
    Button( onClick = { toShow = !toShow }) {
    Text(text = "Check the flower"
    )
    }
    }

    View Slide

  16. AnimatedVisibility
    Composable
    Container
    Enter & Exit
    customization
    Child
    Animations

    View Slide

  17. Crossfade
    @OptIn(ExperimentalAnimationApi
    ::class)
    @Composable
    fun Crossfade(
    targetState: T,
    modifier: Modifier = Modifier,
    animationSpec: FiniteAnimationSpec
    = tween(),
    content: @Composable (T) -> Unit
    ) {
    val transition = updateTransition(targetState)
    transition.Crossfade(modifier, animationSpec, content = content)
    }
    * source code

    View Slide

  18. Crossfade
    @OptIn(ExperimentalAnimationApi
    ::class)
    @Composable
    fun Crossfade(
    targetState: T,
    modifier: Modifier = Modifier,
    animationSpec: FiniteAnimationSpec
    = tween(),
    content: @Composable (T) -> Unit
    ) {
    val transition = updateTransition(targetState)
    transition.Crossfade(modifier, animationSpec, content = content)
    }
    * source code

    View Slide

  19. Crossfade
    var number by remember {
    mutableStateOf(0)
    }
    Column {
    Crossfade(targetState = number) { num ->
    Plants(number = num)
    }
    Row {
    Button(onClick = { number-- }) {...}
    Button(onClick = {number++ }) {...}
    }
    }

    View Slide

  20. Crossfade
    Composable
    Container

    Child
    Animations

    animationSpec
    customization

    Experimental

    View Slide

  21. AnimatedContent
    @ExperimentalAnimationApi
    @Composable
    fun AnimatedContent(
    targetState: S,
    modifier: Modifier = Modifier,
    transitionSpec: AnimatedContentScope
    .() -> ContentTransform = {
    . . .
    },
    contentAlignment: Alignment = Alignment.TopStart,
    content: @Composable() AnimatedVisibilityScope
    .(targetState: S) -> Unit
    ) {
    . . .
    }
    * source code

    View Slide

  22. AnimatedContent
    @ExperimentalAnimationApi
    @Composable
    fun AnimatedContent(
    targetState: S,
    modifier: Modifier = Modifier,
    transitionSpec: AnimatedContentScope
    .() -> ContentTransform = {
    . . .
    },
    contentAlignment: Alignment = Alignment.TopStart,
    content: @Composable() AnimatedVisibilityScope.(targetState: S) -> Unit
    ) {
    . . .
    }
    * source code

    View Slide

  23. AnimatedContent
    var number by remember {
    mutableStateOf(0)
    }
    Column {
    AnimatedContent(targetState = number) { num ->
    Plants(number = num)
    }
    Row {
    Button(onClick = { number-- }) {...}
    Button(onClick = {number++ }) {...}
    }
    }

    View Slide

  24. AnimatedContent
    Composable
    Container

    Child
    Animations

    transitionSpec
    customization

    Experimental

    View Slide

  25. Modifier.animatedContentSize
    fun Modifier.animateContentSize
    (
    animationSpec: FiniteAnimationSpec
    = spring(),
    finishedListener: ((initialValue: IntSize, targetValue: IntSize) -> Unit)? = null
    ): Modifier = composed(
    inspectorInfo = debugInspectorInfo {. . .}
    ) {. . .}
    * source code

    View Slide

  26. Modifier.animatedContentSize
    var toExpand by remember {
    mutableStateOf(false)
    }
    Column(
    modifier = Modifier
    .clickable {
    toExpand = !toExpand
    }.animateContentSize()
    ) {
    if(toExpand) {
    Text(text = plant.description)
    }
    }

    View Slide

  27. Modifier.animatedContentSize
    Extension
    function

    animationSpec
    customization

    finishedListener

    View Slide

  28. animate*AsState
    *
    Float
    Size
    Color Dp
    Offset
    Rect
    Int
    IntOffset IntSize

    View Slide

  29. animate*AsState
    @Composable
    fun animateSizeAsState
    (
    targetValue: Size,
    animationSpec: AnimationSpec = sizeDefaultSpring,
    finishedListener: ((
    Size) -> Unit)? = null
    ): State {
    return animateValueAsState
    (
    targetValue,
    Size.VectorConverter,
    animationSpec,
    finishedListener = finishedListener
    )
    }
    * source code

    View Slide

  30. animate*AsState
    @Composable
    fun animateSizeAsState
    (
    targetValue: Size,
    animationSpec: AnimationSpec = sizeDefaultSpring,
    finishedListener: ((
    Size) -> Unit)? = null
    ): State {
    return animateValueAsState
    (
    targetValue,
    Size.VectorConverter,
    animationSpec,
    finishedListener = finishedListener
    )
    }
    * source code

    View Slide

  31. animate*AsState
    var count by remember {
    mutableStateOf(0)
    }
    val sizeState by animateSizeAsState(
    targetValue = when (count) {
    1 -> Size(150f, 300f)
    2 -> Size(200f, 200f)
    else -> Size(350f, 150f)
    })
    Column(
    modifier = Modifier
    .size(sizeState.width.dp, sizeState.height.dp)
    ) {...}

    View Slide

  32. animate*AsState
    Function
    🎞
    animationSpec
    customization
    ✨ 🦻
    finishedListener
    🎨
    Custom data
    type support

    View Slide

  33. updateTransition
    @Composable
    fun updateTransition(
    targetState: T,
    label: String? = null
    ): Transition {
    val transition = remember { Transition(targetState, label = label) }
    transition.animateTo(targetState)
    DisposableEffect(transition) {
    onDispose {
    transition.onTransitionEnd()
    }
    }
    return transition
    }
    * source code

    View Slide

  34. updateTransition
    @Composable
    fun updateTransition(
    targetState: T,
    label: String? = null
    ): Transition {
    val transition = remember { Transition(targetState, label = label) }
    transition.animateTo(targetState)
    DisposableEffect(transition) {
    onDispose {
    transition.onTransitionEnd()
    }
    }
    return transition
    }
    * source code

    View Slide

  35. updateTransition
    Float
    Size
    Color Dp
    Offset
    Rect
    Int
    IntOffset IntSize
    .animate*
    Value

    View Slide

  36. updateTransition
    val transition = updateTransition(targetState = count)
    val background by transition.animateColor { targetState ->
    if(targetState == 1) { Brown } else { Green }
    }
    val alpha by transition.animateFloat { targetState ->
    if (targetState % 2 == 0) { 1f } else {0.3f}
    }
    Column(
    modifier = Modifier
    .alpha(alpha = alpha)
    .background(color = background)
    ) {. . . }

    View Slide

  37. updateTransition
    Function
    🎞
    transitionSpec
    customization
    💫
    🗂
    Child transitions
    󰤠
    Multiple
    animations

    View Slide

  38. rememberInfiniteTransition
    @Composable
    fun rememberInfiniteTransition
    (): InfiniteTransition {
    val infiniteTransition = remember { InfiniteTransition() }
    infiniteTransition
    .run()
    return infiniteTransition
    }
    * source code

    View Slide

  39. rememberInfiniteTransition
    * source code
    Float Color
    .animate*
    Value

    View Slide

  40. rememberInfiniteTransition
    @Composable
    fun InfiniteTransition
    .animateColor(
    initialValue: Color,
    targetValue: Color,
    animationSpec: InfiniteRepeatableSpec

    ): State {
    val converter = remember {
    (Color.VectorConverter)(targetValue.colorSpace)
    }
    return animateValue(initialValue, targetValue, converter, animationSpec)
    }
    * source code

    View Slide

  41. rememberInfiniteTransition
    @Composable
    fun InfiniteTransition
    .animateColor(
    initialValue: Color,
    targetValue: Color,
    animationSpec: InfiniteRepeatableSpec
    ): State {
    val converter = remember {
    (Color.VectorConverter)(targetValue.colorSpace)
    }
    return animateValue(initialValue, targetValue, converter, animationSpec)
    }
    * source code

    View Slide

  42. rememberInfiniteTransition
    val transition = rememberInfiniteTransition()
    val width by transition.animateFloat(
    initialValue = 150f,
    targetValue = 350f,
    animationSpec = infiniteRepeatable(
    animation = tween(durationMillis = 1000),
    repeatMode = RepeatMode.Reverse
    )
    )
    Column(
    modifier = Modifier
    .width(width.dp)
    ) {. . .}

    View Slide

  43. rememberInfiniteTransition
    Function
    🎞
    animationSpec
    customization

    󰤠
    Multiple
    animations

    View Slide

  44. Customizing animations
    animationSpec
    transitionSpec
    Low-level APIs
    experimental

    View Slide

  45. AnimationSpec
    spring tween keyframes
    repeatable infiniteRepeatable
    snap

    View Slide

  46. AnimationSpec: spring
    @Stable
    fun spring(
    dampingRatio: Float = Spring.DampingRatioNoBouncy
    ,
    stiffness: Float = Spring.StiffnessMedium,
    visibilityThreshold: T? = null
    ): SpringSpec =
    SpringSpec(dampingRatio, stiffness, visibilityThreshold)
    * source code

    View Slide

  47. AnimationSpec: spring
    @Stable
    fun spring(
    dampingRatio: Float = Spring.DampingRatioNoBouncy,
    stiffness: Float = Spring.StiffnessMedium,
    visibilityThreshold: T? = null
    ): SpringSpec =
    SpringSpec(dampingRatio, stiffness, visibilityThreshold)
    * source code

    View Slide

  48. AnimationSpec: spring.dampingRatio

    View Slide

  49. AnimationSpec: spring
    @Stable
    fun spring(
    dampingRatio: Float = Spring.DampingRatioNoBouncy
    ,
    stiffness: Float = Spring.StiffnessMedium,
    visibilityThreshold: T? = null
    ): SpringSpec =
    SpringSpec(dampingRatio, stiffness, visibilityThreshold)
    * source code

    View Slide

  50. AnimationSpec: spring.stiffness

    View Slide

  51. AnimationSpec: spring
    * source code
    val offsetAnimation by animateDpAsState(
    targetValue = if (state == AnimationState.Idle) 0.dp
    else 330.dp,
    animationSpec = spring(
    dampingRatio = Spring.DampingRatioHighBouncy
    ,
    stiffness = Spring.StiffnessLow
    )
    )
    Column(
    modifier = Modifier
    .offset(y = offsetAnimation)
    ) { . . .}

    View Slide

  52. AnimationSpec: tween
    @Stable
    fun tween(
    durationMillis: Int = DefaultDurationMillis,
    delayMillis: Int = 0,
    easing: Easing = FastOutSlowInEasing
    ): TweenSpec = TweenSpec(durationMillis, delayMillis, easing)
    * source code

    View Slide

  53. AnimationSpec: tween
    @Stable
    fun tween(
    durationMillis: Int = DefaultDurationMillis,
    delayMillis: Int = 0,
    easing: Easing = FastOutSlowInEasing
    ): TweenSpec = TweenSpec(durationMillis, delayMillis, easing)
    * source code

    View Slide

  54. AnimationSpec: tween.easing

    View Slide

  55. AnimationSpec: tween.easing
    https://cubic-bezier.com/

    View Slide

  56. AnimationSpec: tween
    * source code
    val offsetAnimation by animateDpAsState(
    targetValue = if (state == AnimationState.Idle) 0.dp
    else 330.dp,
    animationSpec = tween(
    durationMillis = 500,
    easing = CubicBezierEasing(
    .68f,1.36f,.55f,-0.2f)
    )
    )
    Column(
    modifier = Modifier
    .offset(y = offsetAnimation)
    ) { . . .}

    View Slide

  57. AnimationSpec: keyframes
    @Stable
    fun keyframes(
    init: KeyframesSpec.KeyframesSpecConfig
    .() -> Unit
    ): KeyframesSpec {
    return KeyframesSpec(KeyframesSpec.KeyframesSpecConfig<
    T>().apply(init))
    }
    * source code

    View Slide

  58. AnimationSpec: keyframes
    * source code
    val offsetAnimation by animateDpAsState(
    targetValue = if (state == AnimationState.Idle) 0.dp
    else 330.dp,
    animationSpec = keyframes {
    durationMillis = 1000
    delayMillis = 10
    val firstValue = 25.dp
    val firstFrame = 200
    firstValue at firstFrame
    val secondValue = 280.dp
    val secondFrame = 600
    secondValue at secondFrame with FastOutLinearInEasing
    }
    )

    View Slide

  59. AnimationSpec: snap
    @Stable
    fun snap(delayMillis: Int = 0) = SnapSpec(delayMillis)
    * source code

    View Slide

  60. AnimationSpec: snap
    * source code
    val offsetAnimation by animateDpAsState(
    targetValue = if (state == AnimationState.Idle) 0.dp
    else 330.dp,
    animationSpec = snap(
    delayMillis = 500
    )
    )
    Column(
    modifier = Modifier
    .offset(y = offsetAnimation)
    ) { . . .}

    View Slide

  61. AnimationSpec: repeatable
    @Stable
    fun repeatable(
    iterations: Int,
    animation: DurationBasedAnimationSpec
    ,
    repeatMode: RepeatMode = RepeatMode.Restart,
    initialStartOffset: StartOffset = StartOffset(0)
    ): RepeatableSpec =
    RepeatableSpec(iterations, animation, repeatMode, initialStartOffset)
    * source code

    View Slide

  62. AnimationSpec: repeatable
    * source code
    val offsetAnimation by animateDpAsState(
    targetValue = if (state == AnimationState.Idle) 0.dp
    else 330.dp,
    animationSpec = repeatable(
    iterations = 3,
    repeatMode = RepeatMode.Reverse,
    animation = tween(durationMillis = 1000)
    )
    )
    Column(
    modifier = Modifier
    .offset(y = offsetAnimation)
    ) { . . .}

    View Slide

  63. AnimationSpec: infiniteRepeatable
    @Stable
    fun infiniteRepeatable
    (
    animation: DurationBasedAnimationSpec
    ,
    repeatMode: RepeatMode = RepeatMode.Restart,
    initialStartOffset: StartOffset = StartOffset(0)
    ): InfiniteRepeatableSpec
    =
    InfiniteRepeatableSpec(animation, repeatMode, initialStartOffset)
    * source code

    View Slide

  64. AnimationSpec: infiniteRepeatable
    * source code
    val offsetAnimation by animateDpAsState(
    targetValue = if (state == AnimationState.Idle) 0.dp
    else 330.dp,
    animationSpec = infiniteRepeatable(
    animation = tween(durationMillis = 1000),
    repeatMode = RepeatMode.Reverse,
    initialStartOffset = StartOffset(
    offsetMillis = 600,
    offsetType = StartOffsetType.FastForward
    )
    )
    )
    Column(modifier = Modifier.offset(y = offsetAnimation)){ }

    View Slide

  65. TransitionSpec
    combination of animationSpecs
    transition between screens
    ContentTransform

    View Slide

  66. TransitionSpec
    val slideOutNegativeHeight = slideInVertically { height -> height } + fadeIn() with
    slideOutVertically { height -> -height } + fadeOut()
    val slideOutPositiveHeight = slideInVertically { height -> -height } + fadeIn() with
    slideOutVertically { height -> height } + fadeOut()

    View Slide

  67. TransitionSpec
    val slideOutNegativeHeight = slideInVertically { height -> height } + fadeIn() with
    slideOutVertically { height -> -height } + fadeOut()
    val slideOutPositiveHeight = slideInVertically { height -> -height } + fadeIn() with
    slideOutVertically { height -> height } + fadeOut()

    View Slide

  68. TransitionSpec
    AnimatedContent(
    targetState = count,
    transitionSpec = {
    if (targetState > initialState) {
    slideOutNegativeHeight
    } else {
    slideOutPositiveHeight
    }.using(
    SizeTransform(clip = false)
    )
    }
    ) { targetCount ->
    Text(text = "$targetCount")
    }

    View Slide

  69. Low-level APIs
    Base for
    high-level APIs
    💁
    Can be created outside
    of composition
    🧩
    Fine-grained control of
    animations
    🔎

    View Slide

  70. Low-level APIs
    Animatable
    AnimationState
    Animation

    View Slide

  71. Recap
    1. Easy and flexible animations in
    Jetpack Compose
    2. High and low level APIs
    3. AnimationSpec for customization
    4. Extensive list of available default
    animations

    View Slide

  72. Resources
    1. https://proandroiddev.com/animate-with-jetpack-compose-animate-as-state-and-animation-specs-ffc708bb45f8
    2. https://maxkim.eu/things-you-need-to-know-before-switching-to-jetpack-compose
    3. https://developer.android.com/jetpack/compose/animation?hl=en
    4. https://developer.android.com/guide/topics/resources/drawable-resource
    5. https://touchlab.co/jetpack-compose-animations-state-change/
    6. https://developer.android.com/guide/topics/resources/animation-resource
    7. https://squidex.jugru.team/api/assets/srm/ca96238a-033a-4c7a-81be-13db1a5fe46b/igors-rybakov.pdf
    8. https://android-developers.googleblog.com/2022/02/jetpack-compose-11-now-stable.html
    9. https://medium.com/androiddevelopers/customizing-animatedcontent-in-jetpack-compose-629c67b45894
    10. https://proandroiddev.com/animate-with-jetpack-compose-animate-as-state-and-animation-specs-ffc708bb45f8
    11. https://medium.com/weekly-webtips/imperative-vs-declarative-programming-in-javascript-25511b90cdb7
    12. https://developer.android.com/reference/kotlin/androidx/compose/animation/package-summary
    13. https://material.io/design/material-studies/rally.html
    Special thank you to Rebecca Franks for reviewing the presentation

    View Slide

  73. Thank you
    @aida_isay
    cupsofcode.com

    View Slide