$30 off During Our Annual Pro Sale. View Details »

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

  2. Aida Issayeva Senior Software Engineer Google Developer Expert for Android

    WomenTechmakers' ambassador @aida_isay
  3. Table of Contents Why we animate Animations in Android Jetpack

    Compose Animations in Jetpack Compose High-level animations Customizing animations
  4. 📣 Attention 👀 Feedback ✨ Interface 🎉 Fun ⏳ Wait

    Why we animate?
  5. What we get back? 📈 Higher Conversion 👏 Better usability

    💁 More patient users 💫 Smoother experience
  6. Animations in Android

  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
  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”
  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
  10. Animations in Jetpack Compose Low-Level APIs High-Level APIs

  11. High-Level Animation APIs disclaimer: some of them are experimental Easy

    to use Minimum actions Common patterns Material Design best practices ✌
  12. High-Level Animation APIs 5. animate*AsState 6. updateTransition 7. rememberInfiniteTransition 1.

    AnimatedVisibility 2. Crossfade 3. AnimatedContent 4. Modifier.animatedContentSize
  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
  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
  15. AnimatedVisibility var toShow by remember { mutableStateOf(false) } Column {

    AnimatedVisibility(visible = toShow) { PlantCell() } Button( onClick = { toShow = !toShow }) { Text(text = "Check the flower" ) } }
  16. AnimatedVisibility Composable Container Enter & Exit customization Child Animations

  17. Crossfade @OptIn(ExperimentalAnimationApi ::class) @Composable fun <T> Crossfade( targetState: T, modifier:

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

    Modifier = Modifier, animationSpec: FiniteAnimationSpec <Float> = tween(), content: @Composable (T) -> Unit ) { val transition = updateTransition(targetState) transition.Crossfade(modifier, animationSpec, content = content) } * source code
  19. Crossfade var number by remember { mutableStateOf(0) } Column {

    Crossfade(targetState = number) { num -> Plants(number = num) } Row { Button(onClick = { number-- }) {...} Button(onClick = {number++ }) {...} } }
  20. Crossfade Composable Container Child Animations animationSpec customization ✨ Experimental ‼

  21. AnimatedContent @ExperimentalAnimationApi @Composable fun <S> AnimatedContent( targetState: S, modifier: Modifier

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

    = Modifier, transitionSpec: AnimatedContentScope <S>.() -> ContentTransform = { . . . }, contentAlignment: Alignment = Alignment.TopStart, content: @Composable() AnimatedVisibilityScope.(targetState: S) -> Unit ) { . . . } * source code
  23. AnimatedContent var number by remember { mutableStateOf(0) } Column {

    AnimatedContent(targetState = number) { num -> Plants(number = num) } Row { Button(onClick = { number-- }) {...} Button(onClick = {number++ }) {...} } }
  24. AnimatedContent Composable Container Child Animations transitionSpec customization Experimental ‼

  25. Modifier.animatedContentSize fun Modifier.animateContentSize ( animationSpec: FiniteAnimationSpec <IntSize> = spring(), finishedListener:

    ((initialValue: IntSize, targetValue: IntSize) -> Unit)? = null ): Modifier = composed( inspectorInfo = debugInspectorInfo {. . .} ) {. . .} * source code
  26. Modifier.animatedContentSize var toExpand by remember { mutableStateOf(false) } Column( modifier

    = Modifier .clickable { toExpand = !toExpand }.animateContentSize() ) { if(toExpand) { Text(text = plant.description) } }
  27. Modifier.animatedContentSize Extension function animationSpec customization ✨ finishedListener

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

    IntSize
  29. animate*AsState @Composable fun animateSizeAsState ( targetValue: Size, animationSpec: AnimationSpec<Size> =

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

    sizeDefaultSpring, finishedListener: (( Size) -> Unit)? = null ): State<Size> { return animateValueAsState ( targetValue, Size.VectorConverter, animationSpec, finishedListener = finishedListener ) } * source code
  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) ) {...}
  32. animate*AsState Function 🎞 animationSpec customization ✨ 🦻 finishedListener 🎨 Custom

    data type support
  33. updateTransition @Composable fun <T> updateTransition( targetState: T, label: String? =

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

    null ): Transition<T> { val transition = remember { Transition(targetState, label = label) } transition.animateTo(targetState) DisposableEffect(transition) { onDispose { transition.onTransitionEnd() } } return transition } * source code
  35. updateTransition Float Size Color Dp Offset Rect Int IntOffset IntSize

    .animate* Value
  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) ) {. . . }
  37. updateTransition Function 🎞 transitionSpec customization 💫 🗂 Child transitions 󰤠

    Multiple animations
  38. rememberInfiniteTransition @Composable fun rememberInfiniteTransition (): InfiniteTransition { val infiniteTransition =

    remember { InfiniteTransition() } infiniteTransition .run() return infiniteTransition } * source code
  39. rememberInfiniteTransition * source code Float Color .animate* Value

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

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

    InfiniteRepeatableSpec<Color> ): State<Color> { val converter = remember { (Color.VectorConverter)(targetValue.colorSpace) } return animateValue(initialValue, targetValue, converter, animationSpec) } * source code
  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) ) {. . .}
  43. rememberInfiniteTransition Function 🎞 animationSpec customization ✨ 󰤠 Multiple animations

  44. Customizing animations animationSpec transitionSpec Low-level APIs experimental

  45. AnimationSpec spring tween keyframes repeatable infiniteRepeatable snap

  46. AnimationSpec: spring @Stable fun <T> spring( dampingRatio: Float = Spring.DampingRatioNoBouncy

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

    stiffness: Float = Spring.StiffnessMedium, visibilityThreshold: T? = null ): SpringSpec<T> = SpringSpec(dampingRatio, stiffness, visibilityThreshold) * source code
  48. AnimationSpec: spring.dampingRatio

  49. AnimationSpec: spring @Stable fun <T> spring( dampingRatio: Float = Spring.DampingRatioNoBouncy

    , stiffness: Float = Spring.StiffnessMedium, visibilityThreshold: T? = null ): SpringSpec<T> = SpringSpec(dampingRatio, stiffness, visibilityThreshold) * source code
  50. AnimationSpec: spring.stiffness

  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) ) { . . .}
  52. AnimationSpec: tween @Stable fun <T> tween( durationMillis: Int = DefaultDurationMillis,

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

    delayMillis: Int = 0, easing: Easing = FastOutSlowInEasing ): TweenSpec<T> = TweenSpec(durationMillis, delayMillis, easing) * source code
  54. AnimationSpec: tween.easing

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

  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) ) { . . .}
  57. AnimationSpec: keyframes @Stable fun <T> keyframes( init: KeyframesSpec.KeyframesSpecConfig <T>.() ->

    Unit ): KeyframesSpec<T> { return KeyframesSpec(KeyframesSpec.KeyframesSpecConfig< T>().apply(init)) } * source code
  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 } )
  59. AnimationSpec: snap @Stable fun <T> snap(delayMillis: Int = 0) =

    SnapSpec<T>(delayMillis) * source code
  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) ) { . . .}
  61. AnimationSpec: repeatable @Stable fun <T> repeatable( iterations: Int, animation: DurationBasedAnimationSpec

    <T>, repeatMode: RepeatMode = RepeatMode.Restart, initialStartOffset: StartOffset = StartOffset(0) ): RepeatableSpec<T> = RepeatableSpec(iterations, animation, repeatMode, initialStartOffset) * source code
  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) ) { . . .}
  63. AnimationSpec: infiniteRepeatable @Stable fun <T> infiniteRepeatable ( animation: DurationBasedAnimationSpec <T>,

    repeatMode: RepeatMode = RepeatMode.Restart, initialStartOffset: StartOffset = StartOffset(0) ): InfiniteRepeatableSpec <T> = InfiniteRepeatableSpec(animation, repeatMode, initialStartOffset) * source code
  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)){ }
  65. TransitionSpec combination of animationSpecs transition between screens ContentTransform

  66. TransitionSpec val slideOutNegativeHeight = slideInVertically { height -> height }

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

    + fadeIn() with slideOutVertically { height -> -height } + fadeOut() val slideOutPositiveHeight = slideInVertically { height -> -height } + fadeIn() with slideOutVertically { height -> height } + fadeOut()
  68. TransitionSpec AnimatedContent( targetState = count, transitionSpec = { if (targetState

    > initialState) { slideOutNegativeHeight } else { slideOutPositiveHeight }.using( SizeTransform(clip = false) ) } ) { targetCount -> Text(text = "$targetCount") }
  69. Low-level APIs Base for high-level APIs 💁 Can be created

    outside of composition 🧩 Fine-grained control of animations 🔎
  70. Low-level APIs Animatable AnimationState Animation

  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
  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
  73. Thank you @aida_isay cupsofcode.com