Slide 1

Slide 1 text

Animating your way into Jetpack Compose Aida Issayeva

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

📣 Attention 👀 Feedback ✨ Interface 🎉 Fun ⏳ Wait Why we animate?

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

Animations in Android

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

“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”

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

AnimatedVisibility Composable Container Enter & Exit customization Child Animations

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

Crossfade Composable Container Child Animations animationSpec customization ✨ Experimental ‼

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

AnimatedContent Composable Container Child Animations transitionSpec customization Experimental ‼

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

Modifier.animatedContentSize Extension function animationSpec customization ✨ finishedListener

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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) ) {...}

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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) ) {. . . }

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

rememberInfiniteTransition * source code Float Color .animate* Value

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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) ) {. . .}

Slide 43

Slide 43 text

rememberInfiniteTransition Function 🎞 animationSpec customization ✨ 󰤠 Multiple animations

Slide 44

Slide 44 text

Customizing animations animationSpec transitionSpec Low-level APIs experimental

Slide 45

Slide 45 text

AnimationSpec spring tween keyframes repeatable infiniteRepeatable snap

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

AnimationSpec: spring.dampingRatio

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

AnimationSpec: spring.stiffness

Slide 51

Slide 51 text

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) ) { . . .}

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

AnimationSpec: tween.easing

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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) ) { . . .}

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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 } )

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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) ) { . . .}

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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) ) { . . .}

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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)){ }

Slide 65

Slide 65 text

TransitionSpec combination of animationSpecs transition between screens ContentTransform

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

Low-level APIs Animatable AnimationState Animation

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

Thank you @aida_isay cupsofcode.com