Share
Embed
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
Transcript
73 slides
-
Aida Issayeva Senior Software Engineer Google Developer Expert for Android
WomenTechmakers' ambassador @aida_isay -
Table of Contents Why we animate Animations in Android Jetpack
Compose Animations in Jetpack Compose High-level animations Customizing animations -
📣 Attention 👀 Feedback ✨ Interface 🎉 Fun ⏳ Wait
Why we animate? -
What we get back? 📈 Higher Conversion 👏 Better usability
💁 More patient users 💫 Smoother experience -
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 -
“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” -
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 -
High-Level Animation APIs disclaimer: some of them are experimental Easy
to use Minimum actions Common patterns Material Design best practices ✌ -
High-Level Animation APIs 5. animate*AsState 6. updateTransition 7. rememberInfiniteTransition 1.
AnimatedVisibility 2. Crossfade 3. AnimatedContent 4. Modifier.animatedContentSize -
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 -
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 -
AnimatedVisibility var toShow by remember { mutableStateOf(false) } Column {
AnimatedVisibility(visible = toShow) { PlantCell() } Button( onClick = { toShow = !toShow }) { Text(text = "Check the flower" ) } } -
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 -
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 -
Crossfade var number by remember { mutableStateOf(0) } Column {
Crossfade(targetState = number) { num -> Plants(number = num) } Row { Button(onClick = { number-- }) {...} Button(onClick = {number++ }) {...} } } -
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 -
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 -
AnimatedContent var number by remember { mutableStateOf(0) } Column {
AnimatedContent(targetState = number) { num -> Plants(number = num) } Row { Button(onClick = { number-- }) {...} Button(onClick = {number++ }) {...} } } -
Modifier.animatedContentSize fun Modifier.animateContentSize ( animationSpec: FiniteAnimationSpec <IntSize> = spring(), finishedListener:
((initialValue: IntSize, targetValue: IntSize) -> Unit)? = null ): Modifier = composed( inspectorInfo = debugInspectorInfo {. . .} ) {. . .} * source code -
Modifier.animatedContentSize var toExpand by remember { mutableStateOf(false) } Column( modifier
= Modifier .clickable { toExpand = !toExpand }.animateContentSize() ) { if(toExpand) { Text(text = plant.description) } } -
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 -
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 -
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) ) {...} -
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 -
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 -
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) ) {. . . } -
updateTransition Function 🎞 transitionSpec customization 💫 🗂 Child transitions
Multiple animations -
rememberInfiniteTransition @Composable fun rememberInfiniteTransition (): InfiniteTransition { val infiniteTransition =
remember { InfiniteTransition() } infiniteTransition .run() return infiniteTransition } * source code -
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 -
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 -
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) ) {. . .} -
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 -
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 -
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 -
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) ) { . . .} -
AnimationSpec: tween @Stable fun <T> tween( durationMillis: Int = DefaultDurationMillis,
delayMillis: Int = 0, easing: Easing = FastOutSlowInEasing ): TweenSpec<T> = TweenSpec(durationMillis, delayMillis, easing) * source code -
AnimationSpec: tween @Stable fun <T> tween( durationMillis: Int = DefaultDurationMillis,
delayMillis: Int = 0, easing: Easing = FastOutSlowInEasing ): TweenSpec<T> = TweenSpec(durationMillis, delayMillis, easing) * source code -
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) ) { . . .} -
AnimationSpec: keyframes @Stable fun <T> keyframes( init: KeyframesSpec.KeyframesSpecConfig <T>.() ->
Unit ): KeyframesSpec<T> { return KeyframesSpec(KeyframesSpec.KeyframesSpecConfig< T>().apply(init)) } * source code -
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 } ) -
AnimationSpec: snap @Stable fun <T> snap(delayMillis: Int = 0) =
SnapSpec<T>(delayMillis) * source code -
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) ) { . . .} -
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 -
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) ) { . . .} -
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 -
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)){ } -
TransitionSpec val slideOutNegativeHeight = slideInVertically { height -> height }
+ fadeIn() with slideOutVertically { height -> -height } + fadeOut() val slideOutPositiveHeight = slideInVertically { height -> -height } + fadeIn() with slideOutVertically { height -> height } + fadeOut() -
TransitionSpec val slideOutNegativeHeight = slideInVertically { height -> height }
+ fadeIn() with slideOutVertically { height -> -height } + fadeOut() val slideOutPositiveHeight = slideInVertically { height -> -height } + fadeIn() with slideOutVertically { height -> height } + fadeOut() -
TransitionSpec AnimatedContent( targetState = count, transitionSpec = { if (targetState
> initialState) { slideOutNegativeHeight } else { slideOutPositiveHeight }.using( SizeTransform(clip = false) ) } ) { targetCount -> Text(text = "$targetCount") } -
Low-level APIs Base for high-level APIs 💁 Can be created
outside of composition 🧩 Fine-grained control of animations 🔎 -
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 -
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