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. Table of Contents Why we animate Animations in Android Jetpack

    Compose Animations in Jetpack Compose High-level animations Customizing animations
  2. What we get back? 📈 Higher Conversion 👏 Better usability

    💁 More patient users 💫 Smoother experience
  3. 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
  4. “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”
  5. 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
  6. High-Level Animation APIs disclaimer: some of them are experimental Easy

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

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

    AnimatedVisibility(visible = toShow) { PlantCell() } Button( onClick = { toShow = !toShow }) { Text(text = "Check the flower" ) } }
  11. 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
  12. 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
  13. Crossfade var number by remember { mutableStateOf(0) } Column {

    Crossfade(targetState = number) { num -> Plants(number = num) } Row { Button(onClick = { number-- }) {...} Button(onClick = {number++ }) {...} } }
  14. 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
  15. 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
  16. AnimatedContent var number by remember { mutableStateOf(0) } Column {

    AnimatedContent(targetState = number) { num -> Plants(number = num) } Row { Button(onClick = { number-- }) {...} Button(onClick = {number++ }) {...} } }
  17. Modifier.animatedContentSize fun Modifier.animateContentSize ( animationSpec: FiniteAnimationSpec <IntSize> = spring(), finishedListener:

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

    = Modifier .clickable { toExpand = !toExpand }.animateContentSize() ) { if(toExpand) { Text(text = plant.description) } }
  19. 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
  20. 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
  21. 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) ) {...}
  22. 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
  23. 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
  24. 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) ) {. . . }
  25. rememberInfiniteTransition @Composable fun rememberInfiniteTransition (): InfiniteTransition { val infiniteTransition =

    remember { InfiniteTransition() } infiniteTransition .run() return infiniteTransition } * source code
  26. 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
  27. 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
  28. 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) ) {. . .}
  29. 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
  30. 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
  31. 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
  32. 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) ) { . . .}
  33. AnimationSpec: tween @Stable fun <T> tween( durationMillis: Int = DefaultDurationMillis,

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

    delayMillis: Int = 0, easing: Easing = FastOutSlowInEasing ): TweenSpec<T> = TweenSpec(durationMillis, delayMillis, easing) * source code
  35. 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) ) { . . .}
  36. AnimationSpec: keyframes @Stable fun <T> keyframes( init: KeyframesSpec.KeyframesSpecConfig <T>.() ->

    Unit ): KeyframesSpec<T> { return KeyframesSpec(KeyframesSpec.KeyframesSpecConfig< T>().apply(init)) } * source code
  37. 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 } )
  38. 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) ) { . . .}
  39. 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
  40. 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) ) { . . .}
  41. 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
  42. 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)){ }
  43. TransitionSpec val slideOutNegativeHeight = slideInVertically { height -> height }

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

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

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

    outside of composition 🧩 Fine-grained control of animations 🔎
  47. 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
  48. 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