
官方页面一、概念1.1 层级关系1.2 动画分类基于可组合项/修饰符AnimatedVisibility可见性显示/隐藏Modifier.animateContentSize()尺寸变化Modifier.animateBounds()尺寸和位置都变化AnimatedContent()内容改变视图切换CrossFade()只淡入淡出内容改变更简单的使用SharedTransitionLayout()共享元素跳转页面时的同一元素基于值animate***AsState()根据数值变化lerp()起始数值根据百分比变化updateTransition()并发动画InfiniteTransition()无限循环动画二、可选配置2.1 EnterTransition、ExitTransition 进出场效果关闭动画可以使用 EnterTransition.NONE、ExitTransition.NONE。EnterTransitionExitTransitionfadeIn淡入fadeOut淡出slideIn滑入slideOut滑出slideInHorizontally水平滑入slideOutHorizontally水平滑出slideInVertically垂直滑入slideOutVertically垂直滑出scaleIn缩放进入scaleOut缩放退出expendIn展开进入shrinkOut收缩退出expendHorizontally水平展开shrinkHorizontally水平收缩expendVertically垂直展开shrinkVertically垂直收缩2.1.1 Slide 效果纠正默认效果是反现实习惯的。如进场动画默认是向右进入而现实习惯的向左进入。根据源码是把组件宽度取反除以二-it / 2也就是【返回负宽度的一半】来实现【向左偏移其宽度的一半然后向右滑动进入】解决办法是直接返回 it不 /2 是避免退出时上一个界面卡一半再消失看起来不连贯进入退出都不 /2 来实现【将界面完整推入推出】。slideInHorizontally(animationSpec tween(500), initialOffsetX {it}) slideOutHorizontally(animationSpec tween(500), targetOffsetX {it})2.2 AnimationSpec 动画规范在 View 界面系统中对于基于时长的动画持续时间需要使用 ObjectAnimator 等API对于基于物理特性的动画加速曲线需要使用 SpringAnimation插值器实现起来简单但不符合物理轨迹。同时使用这两个不同的动画 API 并不容易Compose 中的 AnimationSpec 够以统一的方式处理这些动画。spring物理特性可在起始值和结束值之间创建基于物理特性的动画。相比基于时长的 AnimationSpec 类型spring 可以更流畅地处理中断因为它可以在目标值在动画中变化时保证速度的连续性打断自然适合跟手/反馈。spring 用作很多动画 API如 animate*AsState 和 updateTransition的默认 AnimationSpec。tween进出场between的缩写使用缓和曲线在起始值和结束值之间添加动画效果。能指定动画的时长、延迟、加速度模式。时长可控适合指示/转场。keyframes关键帧会根据在动画时长内的不同时间戳中指定的快照值添加动画效果。在任何给定时间动画值都将插值到两个关键帧值之间。对于其中每个关键帧可以指定 Easing 来确定插值曲线。可以选择在 0 毫秒和持续时间处指定值。如果不指定这些值它们将分别默认为动画的起始值和结束值。repeatable重复反复运行基于时长的动画例如 tween 或 keyframes直至达到指定的迭代计数。infiniteRepeatable无限重复重复无限次的迭代。snap立即跳到最终值没有动画效果。2.2.1 springStablefun T spring(dampingRatio: Float Spring.DampingRatioNoBouncy, //弹簧的弹性stiffness: Float Spring.StiffnessMedium, //弹簧的硬度向结束值移动的速度visibilityThreshold: T? null): SpringSpecT SpringSpec(dampingRatio, stiffness, visibilityThreshold)dampingRatio弹性DampingRatioNoBouncy 没有弹性看不到弹簧效果DampingRatioLowBouncyDampingRatioMediumBouncyDampingRatioHighBouncy 弹性最大回弹好几次stiffness硬度StiffnessLow 硬度最低回弹效果柔软StiffnessMediumLowStiffnessMediumStiffnessHigh 硬度最高回弹效果生硬val value by animateFloatAsState( targetValue 1f, animationSpec spring( dampingRatio Spring.DampingRatioHighBouncy, stiffness Spring.StiffnessMedium ) )2.2.2 tweenStablefun T tween(durationMillis: Int DefaultDurationMillis, //动画的持续时间delayMillis: Int 0, //延迟动画开始的时间easing: Easing FastOutSlowInEasing): TweenSpecT TweenSpec(durationMillis, delayMillis, easing)val value by animateFloatAsState( targetValue 1f, animationSpec tween( durationMillis 300, delayMillis 50, easing LinearOutSlowInEasing ) )2.2.3 keyframesStablefun T keyframes(init: KeyframesSpec.KeyframesSpecConfigT.() - Unit): KeyframesSpecT {return KeyframesSpec(KeyframesSpec.KeyframesSpecConfigT().apply(init))}val value by animateFloatAsState( targetValue 1f, animationSpec keyframes { durationMillis 375 0.0f at 0 with LinearOutSlowInEasing // for 0-15 ms 0.2f at 15 with FastOutLinearInEasing // for 15-75 ms 0.4f at 75 // ms 0.4f at 225 // ms } )2.2.4 repeatableStablefun T repeatable(iterations: Int,animation: DurationBasedAnimationSpecT,repeatMode: RepeatMode RepeatMode.Restart, //从头开始还是从尾开始initialStartOffset: StartOffset StartOffset(0)): RepeatableSpecT RepeatableSpec(iterations, animation, repeatMode, initialStartOffset)val value by animateFloatAsState( targetValue 1f, animationSpec repeatable( iterations 3, animation tween(durationMillis 300), repeatMode RepeatMode.Reverse ) )2.2.5 infiniteRepeatableStablefun T infiniteRepeatable(animation: DurationBasedAnimationSpecT,repeatMode: RepeatMode RepeatMode.Restart, //从头开始还是从尾开始initialStartOffset: StartOffset StartOffset(0)): InfiniteRepeatableSpecT InfiniteRepeatableSpec(animation, repeatMode, initialStartOffset)val value by animateFloatAsState( targetValue 1f, animationSpec infiniteRepeatable( animation tween(durationMillis 300), repeatMode RepeatMode.Reverse ) )2.2.6 snapStablefun T snap(delayMillis: Int 0 //延迟动画开始的时间) SnapSpecT(delayMillis)val value by animateFloatAsState( targetValue 1f, animationSpec snap(delayMillis 50) )2.3 Easing 加速度模式基于时长的 AnimationSpec 操作如 tween 或 keyframes使用 Easing 来调整动画的小数值。这样可让动画值加速和减速而不是以恒定的速率移动。Easing 实际上是一个函数它取一个介于 0 和 1.0 之间的小数值并返回一个浮点数。返回的值可能位于边界之外表示过冲或下冲。Easing 对象的运行方式与平台中 Interpolator 类的实例相同。不过它采用的不是 getInterpolation() 方法而是 transform() 方法。FastOutSlowInEasing快速开始并慢慢减速直到结束。适用于大多数常规动画。LinearOutSlowInEasing线性速度开始并逐渐减速到结束。适用于需要匀速过渡的动画。FastOutLinearEasing快速开始并以线性速度进行过渡。通常用于突出起始状态的动画。LinearEasing简单的线性过渡效果从开始到结束以匀速进行过渡。CubicBezierEasing(a, b, c, d)自定义参数是第一个控制点坐标(a, b)第二个控制点坐标(c, d)val CustomEasing Easing { fraction - fraction * fraction } Composable fun EasingUsage() { val value by animateFloatAsState( targetValue 1f, animationSpec tween( durationMillis 300, easing CustomEasing ) ) // ... }三、高级别动画3.1 数值动画 animate***AsState3.1.1 开箱即用的常见数值类型为单个值添加动画。当更改 targetValue 时会从当前值向目标值渐变自动计算出一套平滑的数值变化曲线从而实现数值变化的效果。Intfun animateIntAsState(targetValue: Int,animationSpec: AnimationSpecInt intDefaultSpring, //动画规格label: String IntAnimation,finishedListener: ((Int) - Unit)? null //动画结束的回调): StateIntFloatfun animateFloatAsState(targetValue: Float,animationSpec: AnimationSpecFloat defaultAnimation,visibilityThreshold: Float 0.01f,label: String FloatAnimation,finishedListener: ((Float) - Unit)? null): StateFloatColorfun animateColorAsState(targetValue: Color,animationSpec: AnimationSpecColor colorDefaultSpring,label: String ColorAnimation,finishedListener: ((Color) - Unit)? null): StateColorDpfun animateDpAsState(targetValue: Dp,animationSpec: AnimationSpecDp dpDefaultSpring,label: String DpAnimation,finishedListener: ((Dp) - Unit)? null): StateDpSizefun animateSizeAsState(targetValue: Size,animationSpec: AnimationSpecSize sizeDefaultSpring,label: String SizeAnimation,finishedListener: ((Size) - Unit)? null): StateSizeOffsetfun animateOffsetAsState(targetValue: Offset,animationSpec: AnimationSpecOffset offsetDefaultSpring,label: String OffsetAnimation,finishedListener: ((Offset) - Unit)? null): StateOffsetRectfun animateRectAsState(targetValue: Rect,animationSpec: AnimationSpecRect rectDefaultSpring,label: String RectAnimation,finishedListener: ((Rect) - Unit)? null): StateRectIntOffsetIntSizeanimateIntOffsetAsStateanimateIntSizeAsStateComposable fun Show() { val value by remember { flow { while (true) { emit(1.0F) delay(500) emit(0.8F) delay(500) emit(0.6F) delay(500) emit(0.4F) delay(500) emit(0.2F) } } }.collectAsState(1.0F) val animateValue: Float by animateFloatAsState(targetValue value) Box( modifier Modifier .graphicsLayer(alpha animateValue) .background(Color.Red) ) { Text(text 你好) } }var flag by remember { mutableStateOf(false) } val animationColor by animateColorAsState( targetValue if (flag) Color.Red else Color.Blue, label ) Button( colors buttonColors(containerColor animationColor), onClick { flag !flag }, modifier modifier.size(150.dp, 60.dp) ) { Text(text Switch Color) }3.1.2 为其它数据类型提供支持Compose 支持不同类型的数值用作动画值在动画播放期间任何动画值都表示为 AnimationVector对于自定义数据类型使用 TwoWayConverter 即可将值转为动画值 AnimationVector。animateValueAsState()数值动画Composablepublic fun T, V : AnimationVector animateValueAsState(targetValue: T, //目标值typeConverter: TwoWayConverterT, V,animationSpec: AnimationSpecT remember { spring() }, //动画规格visibilityThreshold: T? null,label: String ValueAnimation,finishedListener: ((T) - Unit)? null, //动画结束的回调): StateT内部实现是会开一个协程去逐渐的改变一个百分比的值从而达到从一个状态动画过度到另一个状态的效果。TwoWayConverter转换器public fun T, V : AnimationVector TwoWayConverter(convertToVector: (T) - V,convertFromVector: (V) - T,): TwoWayConverterT, VAnimationVector动画值AnimationVector1DAnimationVector2DAnimationVector3DAnimationVector4D一共四种最多支持4个值。例如 Int 只有一个值使用 AnimationVector1D Color 有 ARGB 四个值使用 AnimationVector4D。//自定义数据类型 data class MySize( val width: Dp, val height: Dp ) //自定义转换器 private val myConverter TwoWayConverterMySize, AnimationVector2D( convertToVector { value - AnimationVector2D(value.width.value, value.height.value) }, convertFromVector { vector - MySize(vector.v1.dp, vector.v2.dp) } ) Composable fun Demo() { var change by remember { mutableStateOf(false) } val animSize by animateValueAsState( targetValue if (change) MySize(200.dp, 200.dp) else MySize(100.dp, 100.dp), typeConverter myConverter, label ) Box(Modifier.size(300.dp)) { Box( modifier Modifier .width(animSize.width) .height(animSize.height) .background(Color.Red) .clickable { change !change } ) } }3.1.3 百分比数值动画 lerp()在起始值与结束值之间按给定比例进行线性插值。数值动画支持的类型lerp() 的重载都支持。lerp()fun lerp(start: Color,stop: Color,FloatRange(from 0.0, to 1.0) fraction: Float): Color3.2 可见性动画 AnimatedVisibility()为内容的显示或消失添加动画。默认以“淡入扩大出现淡出缩小消失”可通过 EnterTransition 和 ExitTransition 设置不同的动画可以用“”自由组合。Composablefun AnimatedVisibility(visible: Boolean, //控制内容是否可见modifier: Modifier Modifier,enter: EnterTransition fadeIn() expandIn(), //进入动画默认淡入扩大exit: ExitTransition shrinkOut() fadeOut(), //退出动画默认淡出缩小label: String AnimatedVisibility,content: Composable() AnimatedVisibilityScope.() - Unit //显示或消失的内容)val value flow { //可见不可见切换 emit(true) delay(500) emit(false) delay(500) emit(true) delay(500) emit(false) } Composable fun Show() { val enable: Boolean by value.collectAsState(initial true) //包裹住需要控制的内容这里是Text AnimatedVisibility( visible enable, ){ Text(text 你好) } }3.2.1 监听动画状态 MutableTransitionState重载版本可以监听动画的状态。AnimatedVisibilityComposablepublic fun AnimatedVisibility(visibleState: MutableTransitionStateBoolean, //监听动画状态modifier: Modifier Modifier,enter: EnterTransition fadeIn() expandIn(),exit: ExitTransition fadeOut() shrinkOut(),label: String AnimatedVisibility,content: Composable() AnimatedVisibilityScope.() - Unit,)重载版本。MutableTransitionStatepublic class MutableTransitionStateS(initialState: S)参数为默认值可对其属性 targetState 进行手动控制。Composable fun Demo( ) { val state remember { MutableTransitionState(false) } Column { Button({ state.targetState !state.targetState }) { Text(点击切换) } AnimatedVisibility(visibleState state) { Text(text Hello, world!) } Text( text when { state.isIdle state.currentState - Visible !state.isIdle state.currentState - Disappearing state.isIdle !state.currentState - Invisible else - Appearing } ) } }3.2.2 为直接或间接子元素单独设置动画效果 animateEnterExit可以为 AnimatedVisibility 不应用动画而为子元素们单独设置。AnimatedVisibility( visible visible, enter EnterTransition.None, //可以不应用动画 exit ExitTransition.None ) { // Fade in/out the background and the foreground. Box(Modifier.fillMaxSize().background(Color.DarkGray)) { Box( Modifier .align(Alignment.Center) .animateEnterExit( //为子元素单独设置 enter slideInVertically(), exit slideOutVertically() ) .sizeIn(minWidth 256.dp, minHeight 64.dp) .background(Color.Red) ) { } } }3.3 内容改变动画 AnimatedContent内容根据状态发生改变时添加动画。默认淡出后淡入。AnimatedContent()fun S AnimatedContent(targetState: S,modifier: Modifier Modifier,transitionSpec: AnimatedContentTransitionScopeS.() - ContentTransform {(fadeIn(animationSpec tween(220, delayMillis 90)) scaleIn(initialScale 0.92f, animationSpec tween(220, delayMillis 90))).togetherWith(fadeOut(animationSpec tween(90)))},contentAlignment: Alignment Alignment.TopStart,label: String AnimatedContent,contentKey: (targetState: S) - Any? { it },content: Composable() AnimatedContentScope.(targetState: S) - Unit)使用 transitionSpec 指定 ContentTransform 对象来配置进入动画和退出动画。ContentTransformpublic class ContentTransform(public val targetContentEnter: EnterTransition,public val initialContentExit: ExitTransition,targetContentZIndex: Float 0f,sizeTransform: SizeTransform? SizeTransform(),)togetherWith()public infix fun EnterTransition.togetherWith(exit: ExitTransition): ContentTransform中缀表达式是进场动画的扩展函数传入出场动画返回ContentTransform对象。using()public infix fun ContentTransform.using(sizeTransform: SizeTransform?): ContentTransform中缀表达式是出场动画的扩展函数传入SizeTransform返回ContentTransform对象。SizeTransform()public fun SizeTransform(clip: Boolean true,sizeAnimationSpec: (initialSize: IntSize, targetSize: IntSize) - FiniteAnimationSpecIntSize { _, _ -spring(stiffness Spring.StiffnessMediumLow,visibilityThreshold IntSize.VisibilityThreshold,)},): SizeTransform当新旧内容的尺寸不同时默认的切换会导致周围布局的突跳。AnimatedContent 提供了 SizeTransform通过在状态切换期间对容器的宽高进行插值计算实现平滑的尺寸过渡。会在测量阶段捕获子元素在不同状态下的期望尺寸并通过 Modifier.layout 动态干预容器的测量结果从而避免了硬性的重新布局。Column { var count by remember { mutableStateOf(0) } Button(onClick { count }) { Text(添加数据) } AnimatedContent( targetState count, transitionSpec { // 一般不用管 SizeTransform 就用默认的 scaleIn() togetherWith scaleOut() using SizeTransform { _, _ - tween( durationMillis 300, delayMillis 50, easing LinearOutSlowInEasing ) } } ) { targetCount - Text(text 数值:${targetCount}) //使用targetCount而不是count } }3.4 简单内容改变动画 CrossFade只有淡入淡出。fun T Crossfade(targetState: T, //目标状态每次更改都会触发动画modifier: Modifier Modifier,animationSpec: FiniteAnimationSpecFloat tween(), //动画规格label: String Crossfade,content: Composable (T) - Unit)Composable fun Sample() { var currentPage by remember { mutableStateOf(false) } Column { Crossfade(targetState currentPage, animationSpec tween(3000), label ) { screen - when (screen) { false - Box(modifier Modifier.background(Color.Blue).size(100.dp)) true - Box(modifier Modifier.background(Color.Red).size(100.dp)) } } Button(onClick { currentPage !currentPage }, modifier Modifier.width(100.dp)) { Text(text 点击切换) } } }3.5 尺寸改变动画 Modifier.animateContentSize()为可大小变化的控件添加动画如展开收起。注意在修饰符链中的顺序很重要为了确保动画流畅务必放置在任何大小修饰符如size或defaultMinSize前面以确保会将带动画效果的值的变化报告给布局。Modifier.animateContentSize(animationSpec: FiniteAnimationSpecIntSize spring(), //动画规格finishedListener: ((initialValue: IntSize, targetValue: IntSize) - Unit)? null //动画结束的回调)Composable private fun Content() { var isExpanded by remember{ mutableStateOf(false) } Column(Modifier.size(200.dp)) { Button(modifier Modifier.align(Alignment.CenterHorizontally), onClick { isExpanded !isExpanded } ) { Text(text if (isExpanded) 展开 else 收起) } Text( modifier Modifier .animateContentSize( animationSpec tween( durationMillis 300, delayMillis 50, easing LinearOutSlowInEasing ) ) .fillMaxWidth(), text Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World!, maxLines if (isExpanded) 5 else 1 ) } }3.6 尺寸位置都改变动画 Modifier.animateBounds()基于LookaheadScope能够自动为Composable的大小和位置变化添加动画实现平滑、预测性的过渡效果。将需要动画的组件放置在 LookaheadScope 中并添加 .animateBounds(lookaheadScope thisLookaheadScope) 修饰符。LookaheadScope { var expanded by remember { mutableStateOf(false) } Box( Modifier .width(if (expanded) 180.dp else 110.dp) .offset(x if (expanded) 0.dp else 100.dp) .animateBounds(lookaheadScope thisLookaheadScope) .background(Color.LightGray, shape RoundedCornerShape(12.dp)) .height(50.dp) .clickable { expanded !expanded } ) { Text(Layout Content, Modifier.align(Alignment.Center)) } }3.7 并发动画 updateTransiton使用同一个状态来驱动多个动画效果。定义一个状态用 updateTransition() 包裹创建 Transition 实例然后去调用不同的动画方法有简单值动画 transition.animateXXX()、可见性动画 transition.AnimatedVisibility()、内容改变动画 transition.AnimatedContent()为每个状态指定不同效果。当状态改变时 Transition 实例能监听并执行对应的动画。Composablefun T updateTransition(targetState: T, //目标状态可以是任何数据类型建议enum确保类型安全label: String? null): TransitionTenum class DemoState { Default, Clicked } Composable private fun Content() { var demoState by remember { mutableStateOf(DemoState.Default) } val transition updateTransition(demoState, label ) //边框宽度动画 val elevation by transition.animateDp(label ) { state - if (state DemoState.Default) 5.dp else 15.dp } //背景色动画 指定AnimationSpec val borderColor by transition.animateColor( transitionSpec { when { DemoState.Default isTransitioningTo DemoState.Clicked - spring(stiffness 50f) else - tween(durationMillis 500) } }, label ) { state - if (state DemoState.Default) Color.Blue else Color.Red } Surface( onClick { demoState if (transition.currentState DemoState.Default) DemoState.Clicked else DemoState.Default }, border BorderStroke(elevation, Color.Green), color borderColor ) { Column(modifier Modifier.fillMaxWidth().padding(16.dp)) { Text(text 点击切换) //内容改变动画AnimatedContent transition.AnimatedContent { demoState - if (demoState DemoState.Default) { Text(text 内容改变动画AnimatedContent(改变前前前)) } else { Text(text 内容改变动画AnimatedContent(改变后后后)) } } //可见性动画AnimatedVisibility transition.AnimatedVisibility( visible { state - state ! DemoState.Default }, enter expandVertically(), exit shrinkVertically() ) { Text(text 可见性动画AnimatedVisibility) } } } }3.7.1 封装以便复用在处理具有大量动画值的复杂组件时将动画和界面分开会更方便复用。enum class BoxState { Collapsed, Expanded } Composable fun AnimatingBox(boxState: BoxState) { val transitionData updateTransitionData(boxState) // UI tree Box( modifier Modifier .background(transitionData.color) .size(transitionData.size) ) } // Holds the animation values. private class TransitionData( color: StateColor, size: StateDp ) { val color by color val size by size } // Create a Transition and return its animation values. Composable private fun updateTransitionData(boxState: BoxState): TransitionData { val transition updateTransition(boxState) val color transition.animateColor { state - when (state) { BoxState.Collapsed - Color.Gray BoxState.Expanded - Color.Red } } val size transition.animateDp { state - when (state) { BoxState.Collapsed - 64.dp BoxState.Expanded - 128.dp } } return remember(transition) { TransitionData(color, size) } }3.8 无限循环动画 rememberInfiniteTransition动画一进入组合阶段就开始运行除非可组合项被移除否则不会停止。使用 rememberInfiniteTransition 创建 InfiniteTransition 实例使用 animateColor、animatedFloat 或 animatedValue 添加子动画还需要指定 infiniteRepeatable 以指定动画规范。val infiniteTransition rememberInfiniteTransition() val alpha by infiniteTransition.animateFloat( initialValue 0F, //初始值 targetValue 1F, //目标值 animationSpec InfiniteRepeatableSpec( animation keyframes { durationMillis 1000 //持续时间 1F at 500 //指定关键帧 }, repeatMode RepeatMode.Restart //重复模式还有一个 Reverse 反转播放会更自然 ) ) val color by infiniteTransition.animateColor( initialValue Color.Red, targetValue Color.Green, animationSpec infiniteRepeatable( animation tween(1000, easing LinearEasing), repeatMode RepeatMode.Reverse ) ) Box(modifier Modifier.alpha(alpha).background(color))四、低级别动画2.1 Animatable 基于协程的单值动画是一个值容器默认支持 Float 和 Color需要其它数据类型使用 TwoWayConverter 自行转换详见上方 3.1.2。Animatablepublic fun Animatable(initialValue: Float,visibilityThreshold: Float Spring.DefaultDisplacementThreshold,): AnimatableFloat, AnimationVector1D支持 Float 类型。public fun Animatable(initialValue: Color): AnimatableColor, AnimationVector4D支持 Color 类型。animateTo()public suspend fun animateTo(targetValue: T,animationSpec: AnimationSpecT defaultSpringSpec,initialVelocity: T velocity,block: (AnimatableT, V.() - Unit)? null,): AnimationResultT, V更改值时为值添加动画效果。animateDecay()public suspend fun animateDecay(initialVelocity: T,animationSpec: DecayAnimationSpecT,block: (AnimatableT, V.() - Unit)? null,): AnimationResultT, V用于启动播放从给定速度变慢的动画。这有助于实现投掷行为。snapTo()public suspend fun snapTo(targetValue: T)可立即将当前值设为目标值。如果动画本身不是唯一的可信来源且必须与其他状态如触摸事件同步该函数就非常有用。与 animate*AsState 相比Animatable 的初始值可以与第一个目标值不同。例如下面的代码示例首先显示一个灰色框然后立即开始通过动画呈现为绿色或红色。//初始为灰色并根据 ok 的值向绿色或红色进行动画过渡。 val color remember { Animatable(Color.Gray) } LaunchedEffect(ok) { color.animateTo(if (ok) Color.Green else Color.Red) } Box( Modifier .fillMaxSize() .background(color.value) )2.2 Animation 手动控制的动画几乎用不到除非需要手动控制动画时间。最低级别的动画 API只能用于控制动画事件无状态没有任何生命周期概念充当动画计算引擎。子类说明TargetBasedAnimationpublic class TargetBasedAnimationT, V : AnimationVector(animationSpec: AnimationSpecT,typeConverter: TwoWayConverterT, V,initialValue: T,targetValue: T,initialVelocityVector: V? null,) : AnimationT, V可以直接控制动画的播放时间。DecayAnimationpublic fun DecayAnimation(animationSpec: FloatDecayAnimationSpec,initialValue: Float,initialVelocity: Float 0f,): DecayAnimationFloat, AnimationVector1D不需要提供 targetValue而是根据起始条件由 initialVelocity 和 initialValue 设置以及所提供的 DecayAnimationSpec 计算其 targetValue。衰减动画通常在快滑手势之后使用用于使元素减速并停止。动画速度从 initialVelocityVector 设置的值开始然后逐渐变慢。//TargetAnimation 的播放时间将根据 withFrameNanos 提供的帧时间手动控制。 val anim remember { TargetBasedAnimation( animationSpec tween(200), typeConverter Float.VectorConverter, initialValue 200f, targetValue 1000f ) } var playTime by remember { mutableLongStateOf(0L) } LaunchedEffect(anim) { val startTime withFrameNanos { it } do { playTime withFrameNanos { it } - startTime val animationValue anim.getValueFromNanos(playTime) } while (someCustomCondition()) }