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

Compose Animations

Max
April 22, 2022

Compose Animations

Android tech-talk about compose animations

Max

April 22, 2022
Tweet

More Decks by Max

Other Decks in Programming

Transcript

  1. В мире View • Android.animation.* • Animator • AnimatorSet •

    ValueAnimator • TransitionsAPI • Animated Vector Drawable • Spring/Fling Animations • RecyclerView.ItemAnimator • 3D (min3d, Libgdx, OpenGl) • MotionLayout
  2. Базовый уровень ValueAnimator/ObjectAnimator • Тип анимированного значения private fun valueAnimator()

    { val startValue = 0F val endValue = resources.getDimensionPixelSize(R.dimen.test_offset).toFloat() ValueAnimator.ofFloat(startValue, endValue) .setDuration(3000) .apply { addUpdateListener { val translation = it.animatedValue as Float image.translationY = translation } } .start() }
  3. Базовый уровень ValueAnimator/ObjectAnimator • Тип анимированного значения • Начальное и

    конечное значение private fun valueAnimator() { val startValue = 0F val endValue = resources.getDimensionPixelSize(R.dimen.test_offset).toFloat() ValueAnimator.ofFloat(startValue, endValue) .setDuration(3000) .apply { addUpdateListener { val translation = it.animatedValue as Float image.translationY = translation } } .start() }
  4. Базовый уровень ValueAnimator/ObjectAnimator • Тип анимированного значения • Начальное и

    конечное значение • Время анимации private fun valueAnimator() { val startValue = 0F val endValue = resources.getDimensionPixelSize(R.dimen.test_offset).toFloat() ValueAnimator.ofFloat(startValue, endValue) .setDuration(3000) .apply { addUpdateListener { val translation = it.animatedValue as Float image.translationY = translation } } .start() }
  5. Базовый уровень ValueAnimator/ObjectAnimator • Тип анимированного значения • Начальное и

    конечное значение • Время анимации • Получение и применение анимированного значения private fun valueAnimator() { val startValue = 0F val endValue = resources.getDimensionPixelSize(R.dimen.test_offset).toFloat() ValueAnimator.ofFloat(startValue, endValue) .setDuration(3000) .apply { addUpdateListener { val translation = it.animatedValue as Float image.translationY = translation } } .start() }
  6. Базовый уровень ValueAnimator/ObjectAnimator • Тип анимированного значения • Начальное и

    конечное значение • Время анимации private fun objectAnimator() { ObjectAnimator.ofFloat(image, View.ALPHA, 0f, 1f) .setDuration(3000) .start() }
  7. Animatable • Тип анимированного значения @Composable fun MovingAnimatable() { val

    startValue = 0F val endValue = 300F val yOffsetAnimatable = remember { Animatable(startValue, Float.VectorConverter) } Image( painter = painterResource(R.drawable.android_hero), contentDescription = null, modifier = Modifier .size(300.dp, 300.dp) .offset(y = yOffsetAnimatable.value.dp) ) LaunchedEffect(true) { yOffsetAnimatable.animateTo( targetValue = endValue, animationSpec = tween(1500) ) } }
  8. Animatable • Тип анимированного значения • Начальное значение @Composable fun

    MovingAnimatable() { val startValue = 0F val endValue = 300F val yOffsetAnimatable = remember { Animatable(startValue, Float.VectorConverter) } Image( painter = painterResource(R.drawable.android_hero), contentDescription = null, modifier = Modifier .size(300.dp, 300.dp) .offset(y = yOffsetAnimatable.value.dp) ) LaunchedEffect(true) { yOffsetAnimatable.animateTo( targetValue = endValue, animationSpec = tween(1500) ) } }
  9. Animatable • Тип анимированного значения • Начальное значение • Получение

    и применение анимированного значения @Composable fun MovingAnimatable() { val startValue = 0F val endValue = 300F val yOffsetAnimatable = remember { Animatable(startValue, Float.VectorConverter) } Image( painter = painterResource(R.drawable.android_hero), contentDescription = null, modifier = Modifier .size(300.dp, 300.dp) .offset(y = yOffsetAnimatable.value.dp) ) LaunchedEffect(true) { yOffsetAnimatable.animateTo( targetValue = endValue, animationSpec = tween(1500) ) } }
  10. Animatable • Тип анимированного значения • Начальное значение • Получение

    и применение анимированного значения • Запуск анимации @Composable fun MovingAnimatable() { val startValue = 0F val endValue = 300F val yOffsetAnimatable = remember { Animatable(startValue, Float.VectorConverter) } Image( painter = painterResource(R.drawable.android_hero), contentDescription = null, modifier = Modifier .size(300.dp, 300.dp) .offset(y = yOffsetAnimatable.value.dp) ) LaunchedEffect(true) { yOffsetAnimatable.animateTo( targetValue = endValue, animationSpec = tween(1500) ) } }
  11. Animatable • Тип анимированного значения • Начальное значение • Получение

    и применение анимированного значения • Запуск анимации • Целевое значение @Composable fun MovingAnimatable() { val startValue = 0F val endValue = 300F val yOffsetAnimatable = remember { Animatable(startValue, Float.VectorConverter) } Image( painter = painterResource(R.drawable.android_hero), contentDescription = null, modifier = Modifier .size(300.dp, 300.dp) .offset(y = yOffsetAnimatable.value.dp) ) LaunchedEffect(true) { yOffsetAnimatable.animateTo( targetValue = endValue, animationSpec = tween(1500) ) } }
  12. Animatable • Тип анимированного значения • Начальное значение • Получение

    и применение анимированного значения • Запуск анимации • Целевое значение • Длительность @Composable fun MovingAnimatable() { val startValue = 0F val endValue = 300F val yOffsetAnimatable = remember { Animatable(startValue, Float.VectorConverter) } Image( painter = painterResource(R.drawable.android_hero), contentDescription = null, modifier = Modifier .size(300.dp, 300.dp) .offset(y = yOffsetAnimatable.value.dp) ) LaunchedEffect(true) { yOffsetAnimatable.animateTo( targetValue = endValue, animationSpec = tween(1500) ) } }
  13. Особенности Animatable • Класс, а не composable • При смене

    целевого значения не запустится сначала • Хранит текущее и целевое значение анимации • Можем получить данные о работе анимации • Нужны корутины • Только Float и Color
  14. А можно попроще? animate*AsState • State анимации @Composable fun AlphaAnimateAsState()

    { var imageEnabled by remember { mutableStateOf(false) } val alpha: Float by animateFloatAsState( targetValue = if (imageEnabled) 1f else 0f, animationSpec = tween(1500) ) Image( painter = painterResource(R.drawable.android_hero), contentDescription = null, modifier = Modifier .size(300.dp, 300.dp) .alpha(alpha) ) Button( onClick = { imageEnabled = imageEnabled.not() }, Modifier.offset(y = 300.dp) ) { Text(text = "Change imageEnabled state") } }
  15. А можно попроще? animate*AsState • State анимации • Целевое значение

    @Composable fun AlphaAnimateAsState() { var imageEnabled by remember { mutableStateOf(false) } val alpha: Float by animateFloatAsState( targetValue = if (imageEnabled) 1f else 0f, animationSpec = tween(1500) ) Image( painter = painterResource(R.drawable.android_hero), contentDescription = null, modifier = Modifier .size(300.dp, 300.dp) .alpha(alpha) ) Button( onClick = { imageEnabled = imageEnabled.not() }, Modifier.offset(y = 300.dp) ) { Text(text = "Change imageEnabled state") } }
  16. А можно попроще? animate*AsState • State анимации • Целевое значение

    • Продолжительность @Composable fun AlphaAnimateAsState() { var imageEnabled by remember { mutableStateOf(false) } val alpha: Float by animateFloatAsState( targetValue = if (imageEnabled) 1f else 0f, animationSpec = tween(1500) ) Image( painter = painterResource(R.drawable.android_hero), contentDescription = null, modifier = Modifier .size(300.dp, 300.dp) .alpha(alpha) ) Button( onClick = { imageEnabled = imageEnabled.not() }, Modifier.offset(y = 300.dp) ) { Text(text = "Change imageEnabled state") } }
  17. А можно попроще? animate*AsState • State анимации • Целевое значение

    • Продолжительность • Получение и применение значения @Composable fun AlphaAnimateAsState() { var imageEnabled by remember { mutableStateOf(false) } val alpha: Float by animateFloatAsState( targetValue = if (imageEnabled) 1f else 0f, animationSpec = tween(1500) ) Image( painter = painterResource(R.drawable.android_hero), contentDescription = null, modifier = Modifier .size(300.dp, 300.dp) .alpha(alpha) ) Button( onClick = { imageEnabled = imageEnabled.not() }, Modifier.offset(y = 300.dp) ) { Text(text = "Change imageEnabled state") } }
  18. Особенности animate*AsState • Composable • Запуск происходит только при изменении

    state • Поддерживает много типов • Обёртка над Animatable • Можем указать только целевое значение • Нет доступа к состоянию текущей анимации
  19. Transition • Создание экземпляра @Composable fun AlphaAndMovingTransition() { var startAnimation

    by remember { mutableStateOf(false) } val transition = updateTransition(startAnimation, label = "") val yOffset by transition.animateFloat( transitionSpec = { tween(durationMillis = 3000) }, label = "yOffsetAnimation" ) { if (it) 300F else 0F } val alpha by transition.animateFloat( transitionSpec = { tween(durationMillis = 3000) }, label = "alphaAnimation" ) { if (it) 1F else 0F } Image( painter = painterResource(R.drawable.android_hero), contentDescription = "firstIcon", modifier = Modifier .size(300.dp, 300.dp) .offset(y = yOffset.dp) .alpha(alpha) ) Button( onClick = { startAnimation = startAnimation.not() }, Modifier.offset(y = 300.dp) ) { Text(text = "Change startAnimation state") } }
  20. Transition • Создание экземпляра • State модель @Composable fun AlphaAndMovingTransition()

    { var startAnimation by remember { mutableStateOf(false) } val transition = updateTransition(startAnimation, label = "") val yOffset by transition.animateFloat( transitionSpec = { tween(durationMillis = 3000) }, label = "yOffsetAnimation" ) { if (it) 300F else 0F } val alpha by transition.animateFloat( transitionSpec = { tween(durationMillis = 3000) }, label = "alphaAnimation" ) { if (it) 1F else 0F } Image( painter = painterResource(R.drawable.android_hero), contentDescription = "firstIcon", modifier = Modifier .size(300.dp, 300.dp) .offset(y = yOffset.dp) .alpha(alpha) ) Button( onClick = { startAnimation = startAnimation.not() }, Modifier.offset(y = 300.dp) ) { Text(text = "Change startAnimation state") } }
  21. Transition • Создание экземпляра • State модель • Создание стейтов

    анимаций @Composable fun AlphaAndMovingTransition() { var startAnimation by remember { mutableStateOf(false) } val transition = updateTransition(startAnimation, label = "") val yOffset by transition.animateFloat( transitionSpec = { tween(durationMillis = 3000) }, label = "yOffsetAnimation" ) { if (it) 300F else 0F } val alpha by transition.animateFloat( transitionSpec = { tween(durationMillis = 3000) }, label = "alphaAnimation" ) { if (it) 1F else 0F } Image( painter = painterResource(R.drawable.android_hero), contentDescription = "firstIcon", modifier = Modifier .size(300.dp, 300.dp) .offset(y = yOffset.dp) .alpha(alpha) ) Button( onClick = { startAnimation = startAnimation.not() }, Modifier.offset(y = 300.dp) ) { Text(text = "Change startAnimation state") } }
  22. Transition • Создание экземпляра • State модель • Создание стейтов

    анимаций • Получение и применение значения @Composable fun AlphaAndMovingTransition() { var startAnimation by remember { mutableStateOf(false) } val transition = updateTransition(startAnimation, label = "") val yOffset by transition.animateFloat( transitionSpec = { tween(durationMillis = 3000) }, label = "yOffsetAnimation" ) { if (it) 300F else 0F } val alpha by transition.animateFloat( transitionSpec = { tween(durationMillis = 3000) }, label = "alphaAnimation" ) { if (it) 1F else 0F } Image( painter = painterResource(R.drawable.android_hero), contentDescription = "firstIcon", modifier = Modifier .size(300.dp, 300.dp) .offset(y = yOffset.dp) .alpha(alpha) ) Button( onClick = { startAnimation = startAnimation.not() }, Modifier.offset(y = 300.dp) ) { Text(text = "Change startAnimation state") } }
  23. MutableTransitionState • Начальное значение отличное от первого целевого @Composable fun

    MutableTransitionState() { val currentState = remember { MutableTransitionState(false) } currentState.targetState = true val transition = updateTransition(currentState, label = "") val yOffset by transition.animateFloat( transitionSpec = { tween(durationMillis = 1500) }, label = "" ) { if (it) 300F else 0F } Image( painter = painterResource(R.drawable.android_hero), contentDescription = "firstIcon", modifier = Modifier .size(300.dp, 300.dp) .offset(y = yOffset.dp) ) }
  24. MutableTransitionState • Начальное значение отличное от первого целевого • Запуск

    анимации на входе в композицию @Composable fun MutableTransitionState() { val currentState = remember { MutableTransitionState(false) } currentState.targetState = true val transition = updateTransition(currentState, label = "") val yOffset by transition.animateFloat( transitionSpec = { tween(durationMillis = 1500) }, label = "" ) { if (it) 300F else 0F } Image( painter = painterResource(R.drawable.android_hero), contentDescription = "firstIcon", modifier = Modifier .size(300.dp, 300.dp) .offset(y = yOffset.dp) ) }
  25. createChildTransition • Э-э-экспериментал! • State родительской анимации @Composable fun CreateChildTransition()

    { var parentState by remember { mutableStateOf(ParentState.InitialState) } val parentTransition = updateTransition(parentState, label = "") val firstAnimationTransition = parentTransition.createChildTransition { it ! = ParentState.InitialState } val secondAnimationTransition = parentTransition.createChildTransition { it = = ParentState.SecondParentState } val alpha by firstAnimationTransition.animateFloat( transitionSpec = { tween(durationMillis = 3000) }, label = "" ) { if (it) 1F else 0F } val yOffset by secondAnimationTransition.animateFloat( transitionSpec = { tween(durationMillis = 3000) }, label = "" ) { if (it) 400F else 100F } Image( painter = painterResource(R.drawable.android_hero), contentDescription = "firstIcon", modifier = Modifier .size(300.dp, 300.dp) .offset(y = yOffset.dp) .alpha(alpha) ) LaunchedEffect(true) { parentState = ParentState.FirstParentState delay(3000) parentState = ParentState.SecondParentState } }
  26. createChildTransition • Э-э-экспериментал! • State родительской анимации • Transition родительской

    анимации @Composable fun CreateChildTransition() { var parentState by remember { mutableStateOf(ParentState.InitialState) } val parentTransition = updateTransition(parentState, label = "") val firstAnimationTransition = parentTransition.createChildTransition { it ! = ParentState.InitialState } val secondAnimationTransition = parentTransition.createChildTransition { it = = ParentState.SecondParentState } val alpha by firstAnimationTransition.animateFloat( transitionSpec = { tween(durationMillis = 3000) }, label = "" ) { if (it) 1F else 0F } val yOffset by secondAnimationTransition.animateFloat( transitionSpec = { tween(durationMillis = 3000) }, label = "" ) { if (it) 400F else 100F } Image( painter = painterResource(R.drawable.android_hero), contentDescription = "firstIcon", modifier = Modifier .size(300.dp, 300.dp) .offset(y = yOffset.dp) .alpha(alpha) ) LaunchedEffect(true) { parentState = ParentState.FirstParentState delay(3000) parentState = ParentState.SecondParentState } }
  27. createChildTransition • Э-э-экспериментал! • State родительской анимации • Transition родительской

    анимации • Transition дочерних анимаций @Composable fun CreateChildTransition() { var parentState by remember { mutableStateOf(ParentState.InitialState) } val parentTransition = updateTransition(parentState, label = "") val firstAnimationTransition = parentTransition.createChildTransition { it ! = ParentState.InitialState } val secondAnimationTransition = parentTransition.createChildTransition { it = = ParentState.SecondParentState } val alpha by firstAnimationTransition.animateFloat( transitionSpec = { tween(durationMillis = 3000) }, label = "" ) { if (it) 1F else 0F } val yOffset by secondAnimationTransition.animateFloat( transitionSpec = { tween(durationMillis = 3000) }, label = "" ) { if (it) 400F else 100F } Image( painter = painterResource(R.drawable.android_hero), contentDescription = "firstIcon", modifier = Modifier .size(300.dp, 300.dp) .offset(y = yOffset.dp) .alpha(alpha) ) LaunchedEffect(true) { parentState = ParentState.FirstParentState delay(3000) parentState = ParentState.SecondParentState } }
  28. createChildTransition • Э-э-экспериментал! • State родительской анимации • Transition родительской

    анимации • Transition дочерних анимаций • Создание анимаций @Composable fun CreateChildTransition() { var parentState by remember { mutableStateOf(ParentState.InitialState) } val parentTransition = updateTransition(parentState, label = "") val firstAnimationTransition = parentTransition.createChildTransition { it ! = ParentState.InitialState } val secondAnimationTransition = parentTransition.createChildTransition { it = = ParentState.SecondParentState } val alpha by secondAnimationTransition.animateFloat( transitionSpec = { tween(durationMillis = 3000) }, label = "" ) { if (it) 1F else 0F } val yOffset by secondAnimationTransition.animateFloat( transitionSpec = { tween(durationMillis = 3000) }, label = "" ) { if (it) 400F else 100F } Image( painter = painterResource(R.drawable.android_hero), contentDescription = "firstIcon", modifier = Modifier .size(300.dp, 300.dp) .offset(y = yOffset.dp) .alpha(alpha) ) LaunchedEffect(true) { parentState = ParentState.FirstParentState delay(3000) parentState = ParentState.SecondParentState } }
  29. createChildTransition • Э-э-экспериментал! • State родительской анимации • Transition родительской

    анимации • Transition дочерних анимаций • Создание анимаций @Composable fun CreateChildTransition() { var parentState by remember { mutableStateOf(ParentState.InitialState) } val parentTransition = updateTransition(parentState, label = "") val firstAnimationTransition = parentTransition.createChildTransition { it ! = ParentState.InitialState } val secondAnimationTransition = parentTransition.createChildTransition { it = = ParentState.SecondParentState } val alpha by firstAnimationTransition.animateFloat( transitionSpec = { tween(durationMillis = 3000) }, label = "" ) { if (it) 1F else 0F } val yOffset by firstAnimationTransition.animateFloat( transitionSpec = { tween(durationMillis = 3000) }, label = "" ) { if (it) 400F else 100F } Image( painter = painterResource(R.drawable.android_hero), contentDescription = "firstIcon", modifier = Modifier .size(300.dp, 300.dp) .offset(y = yOffset.dp) .alpha(alpha) ) LaunchedEffect(true) { parentState = ParentState.FirstParentState delay(3000) parentState = ParentState.SecondParentState } }
  30. rememberInfiniteTransition • Бесконечная анимация • Запуск анимации на вхоже в

    композицию @Composable fun AlphaInfiniteTransition() { val transition = rememberInfiniteTransition() val alpha by transition.animateFloat( initialValue = 0F, targetValue = 1F, animationSpec = InfiniteRepeatableSpec( tween(durationMillis = 1500), repeatMode = RepeatMode.Reverse ), ) Image( painter = painterResource(R.drawable.android_hero), contentDescription = "secondIcon", modifier = Modifier .size(300.dp, 300.dp) .alpha(alpha) ) }
  31. Animation • Создание экземпляра @Composable fun RotationTargetBasedAnimation() { var rotation

    by remember { mutableStateOf(0F) } var startAnimation by remember { mutableStateOf(false) } val anim = remember { TargetBasedAnimation( animationSpec = tween(3000), typeConverter = Float.VectorConverter, initialValue = 0f, targetValue = 360f ) } LaunchedEffect(startAnimation) { var playTime: Long val startTime = withFrameNanos { it } do { playTime = withFrameNanos { it } - startTime rotation = anim.getValueFromNanos(playTime) } while (!anim.isFinishedFromNanos(playTime)) } Image( painter = painterResource(R.drawable.android_hero), contentDescription = "secondIcon", modifier = Modifier .size(300.dp, 300.dp) .rotate(rotation) ) Button( onClick = { startAnimation = startAnimation.not() }, Modifier.offset(y = 300.dp) ) { Text(text = "Change startAnimation state") } }
  32. Animation • Создание экземпляра • Получение времени отрисовки первого кадра

    @Composable fun RotationTargetBasedAnimation() { var rotation by remember { mutableStateOf(0F) } var startAnimation by remember { mutableStateOf(false) } val anim = remember { TargetBasedAnimation( animationSpec = tween(3000), typeConverter = Float.VectorConverter, initialValue = 0f, targetValue = 360f ) } LaunchedEffect(startAnimation) { var playTime: Long val startTime = withFrameNanos { it } do { playTime = withFrameNanos { it } - startTime rotation = anim.getValueFromNanos(playTime) } while (!anim.isFinishedFromNanos(playTime)) } Image( painter = painterResource(R.drawable.android_hero), contentDescription = "secondIcon", modifier = Modifier .size(300.dp, 300.dp) .rotate(rotation) ) Button( onClick = { startAnimation = startAnimation.not() }, Modifier.offset(y = 300.dp) ) { Text(text = "Change startAnimation state") } }
  33. Animation • Создание экземпляра • Получение времени отрисовки первого кадра

    • Получение времени текущего кадра @Composable fun RotationTargetBasedAnimation() { var rotation by remember { mutableStateOf(0F) } var startAnimation by remember { mutableStateOf(false) } val anim = remember { TargetBasedAnimation( animationSpec = tween(3000), typeConverter = Float.VectorConverter, initialValue = 0f, targetValue = 360f ) } LaunchedEffect(startAnimation) { var playTime: Long val startTime = withFrameNanos { it } do { playTime = withFrameNanos { it } - startTime rotation = anim.getValueFromNanos(playTime) } while (!anim.isFinishedFromNanos(playTime)) } Image( painter = painterResource(R.drawable.android_hero), contentDescription = "secondIcon", modifier = Modifier .size(300.dp, 300.dp) .rotate(rotation) ) Button( onClick = { startAnimation = startAnimation.not() }, Modifier.offset(y = 300.dp) ) { Text(text = "Change startAnimation state") } }
  34. Animation • Создание экземпляра • Получение времени отрисовки первого кадра

    • Получение времени текущего кадра • Получение анимированного значения @Composable fun RotationTargetBasedAnimation() { var rotation by remember { mutableStateOf(0F) } var startAnimation by remember { mutableStateOf(false) } val anim = remember { TargetBasedAnimation( animationSpec = tween(3000), typeConverter = Float.VectorConverter, initialValue = 0f, targetValue = 360f ) } LaunchedEffect(startAnimation) { var playTime: Long val startTime = withFrameNanos { it } do { playTime = withFrameNanos { it } - startTime rotation = anim.getValueFromNanos(playTime) } while (!anim.isFinishedFromNanos(playTime)) } Image( painter = painterResource(R.drawable.android_hero), contentDescription = "secondIcon", modifier = Modifier .size(300.dp, 300.dp) .rotate(rotation) ) Button( onClick = { startAnimation = startAnimation.not() }, Modifier.offset(y = 300.dp) ) { Text(text = "Change startAnimation state") } }
  35. DecayAnimation • Затухающие анимации • Не имеют конечного значения •

    Имеют начальное значение и скорость @Composable fun DecayAnimation() { var startAnimation by remember { mutableStateOf(false) } var offset by remember { mutableStateOf(0F) } val anim = remember { DecayAnimation( initialValue = 0F, animationSpec = FloatExponentialDecaySpec(), initialVelocity = 2350F ) } var playTime by remember { mutableStateOf(0L) } LaunchedEffect(startAnimation) { val startTime = withFrameNanos { it } do { playTime = withFrameNanos { it } - startTime offset = anim.getValueFromNanos(playTime) } while (!anim.isFinishedFromNanos(playTime)) } Image( painter = painterResource(R.drawable.android_hero), contentDescription = "secondIcon", modifier = Modifier .size(300.dp, 300.dp) .offset(y = offset.dp) ) Button( onClick = { startAnimation = startAnimation.not() }, Modifier.offset(y = 600.dp) ) { Text(text = "Change startAnimation state") } }
  36. tween Позволяет настроить • Продолжительность • Задержку перед началом •

    Характер изменения данных animationSpec = tween( delayMillis = 300, durationMillis = 3000, easing = LinearEasing )
  37. spring Физика пружины • Жёсткость • Упругость animationSpec = spring(

    dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessMedium )
  38. keyframes Интерполирует значение анимации между двумя кадрами animationSpec = keyframes

    { durationMillis = 3000 0f at 0 with LinearOutSlowInEasing 0.5f at 1500 with FastOutSlowInEasing }
  39. AnimatedVisibility • Compose функция var visible by remember { mutableStateOf(true)

    } AnimatedVisibility( visible = visible, enter = slideInVertically(), exit = slideOutVertically() ) { Column( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, ) { Image( painter = painterResource(R.drawable.android_hero), contentDescription = null, modifier = Modifier.size(200.dp, 200.dp) ) Text( Text = "Hello Effective!!!", fontSize = 48.sp, modifier = Modifier .align(Alignment.CenterHorizontally) .wrapContentWidth() ) } }
  40. AnimatedVisibility • Compose функция • С версии 1.1 не experimental

    • Для enter/exit можно указать animationSpec var visible by remember { mutableStateOf(true) } AnimatedVisibility( visible = visible, enter = slideInVertically(), exit = slideOutVertically() ) { Column( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, ) { Image( painter = painterResource(R.drawable.android_hero), contentDescription = null, modifier = Modifier.size(200.dp, 200.dp) ) Text( Text = "Hello Effective!!!", fontSize = 48.sp, modifier = Modifier .align(Alignment.CenterHorizontally) .wrapContentWidth() ) } }
  41. AnimatedVisibility • Отдельная настройка дочерних элементов с помощью animateEnterExit Text(

    "Hello Effective!!!”, fontSize = 48.sp, modifier = Modifier .align(Alignment.CenterHorizontally) .wrapContentWidth() .animateEnterExit( enter = fadeIn(), exit = fadeOut() ) )
  42. Особенности AnimatedVisibility • Composable container • Использует animationSpec • Имеет

    скопу для запуска дополнительных анимаций
  43. AnimatedContent • Э-э-эксперименты! • Composable • Позволяет настроить анимацию изменения

    контента с помощью transitionspec AnimatedContent( targetState = count, transitionSpec = { if (targetState > initialState) { slideInVertically { height -> height } with slideOutVertically { height -> -height } } else { slideInVertically { height -> -height } with slideOutVertically { height -> height } } } ) { targetCount -> val background by transition.animateColor( label = "", transitionSpec = { tween(2000) } ) { state -> if (state = = EnterExitState.Visible) Color.Green else Color.Red } // Важно использовать targetCount Text( fontSize = 48.sp, color = background, text = "$targetCount" ) }
  44. Особенности AnimatedContent • Composable container • Использует animationSpec • Имеет

    скоуп для запуска дополнительных анимаций
  45. animateContentSize • Модификатор анимации изменения размера контента var heroSize by

    remember { mutableStateOf(100) } Image( painter = painterResource(R.drawable.android_hero), contentDescription = null, modifier = Modifier .size(heroSize.dp, heroSize.dp) .clickable { heroSize += 50 } .animateContentSize( animationSpec = spring( dampingRatio = Spring.DampingRatioHighBouncy ) ) )
  46. View • Наследуется от ConstraintLayout • Позволяет анимировать изменение состояний

    дочерних View • Можно описывать декларативно в xml • Контроль за состоянием анимаций
  47. Compose • Composable container • Позволяет анимировать состояния дочерних composable

    • Наборы свойств состояний описываются на Kotlin или Json5 • Контроль за состоянием анимации
  48. Compose Motion Layout • ConstraintSet - определяет начальные и конечные

    состояния переходов. Можно создать из json строки • Progress - state с состоянием прогресса
  49. MotionLayout • Наполнение анимируемых composables MotionLayout( start = startConstraintSet(), end

    = endConstraintSet(), progress = animateMotionLayoutProgress, modifier = Modifier .fillMaxWidth() .height(screenHeight.dp) .swipeable( state = swipingState, thresholds = { _, _ -> FractionalThreshold(0.5f) }, orientation = Orientation.Vertical, anchors = mapOf( 0f to SwipingStates.COLLAPSED, screenHeight to SwipingStates.EXPANDED, ) ) ) { Image( painter = painterResource(id = R.drawable.android_hero), contentDescription = "", modifier = Modifier .layoutId("androidImage") .background(MaterialTheme.colors.primary) .alpha(alpha = 1f - animateMotionLayoutProgress), contentScale = ContentScale.FillWidth ) Text( text = "Hi, Android!”, modifier = Modifier .layoutId("title") .wrapContentHeight(), style = MaterialTheme.typography.h6, textAlign = TextAlign.Center ) }
  50. MotionLayout • Наполнение анимируемых composables • Добавление layoutId MotionLayout( start

    = startConstraintSet(), end = endConstraintSet(), progress = animateMotionLayoutProgress, modifier = Modifier .fillMaxWidth() .height(screenHeight.dp) .swipeable( state = swipingState, thresholds = { _, _ -> FractionalThreshold(0.5f) }, orientation = Orientation.Vertical, anchors = mapOf( 0f to SwipingStates.COLLAPSED, screenHeight to SwipingStates.EXPANDED, ) ) ) { Image( painter = painterResource(id = R.drawable.android_hero), contentDescription = "", modifier = Modifier .layoutId("androidImage") .background(MaterialTheme.colors.primary) .alpha(alpha = 1f - animateMotionLayoutProgress), contentScale = ContentScale.FillWidth ) Text( text = "Hi, Android!”, modifier = Modifier .layoutId("title") .wrapContentHeight(), style = MaterialTheme.typography.h6, textAlign = TextAlign.Center ) }
  51. MotionLayout • Наполнение анимируемых composables • Добавление layoutId • Добавление

    ConstraintSet • Часть доступно только в json private fun startConstraintSet() = ConstraintSet { val androidImage = createRefFor("pAndroidImage") val title = createRefFor("title") constrain(androidImage) { width = Dimension.fillToConstraints start.linkTo(parent.start) end.linkTo(parent.end) top.linkTo(parent.top) } constrain(title) { start.linkTo(parent.start, 16.dp) top.linkTo(androidImage.bottom, 16.dp) } }
  52. MotionLayout • Добавление прогресса val swipingState = rememberSwipeableState(initialValue = SwipingStates.EXPANDED)

    val animateMotionLayoutProgress by animateFloatAsState( targetValue = if (swipingState.progress.to = = SwipingStates.COLLAPSED) { swipingState.progress.fraction } else { 1f - swipingState.progress.fraction }, animationSpec = spring(Spring.DampingRatioHighBouncy) )
  53. Особенности MotionLayout • ConstraintSet поддерживает только общие свойства • Сложные

    жесты не работают из коробки • Есть возможность настраивать анимацию прогресса стандартным AnimationSpec • Experimental