Compose - 动画

官方页面

一、概念

1.1 层级关系

1.2 动画分类

基于

可组合项/

修饰符

AnimatedVisibility

可见性(显示/隐藏)

Modifier.animateContentSize()

尺寸变化(调整大小)

AnimatedContent

内容根据状态发生改变时

SharedTransitionLayout

共享元素(跳转页面时的同一元素)

基于值

animate***AsState

单个值(透明度)

updateTransition

并发动画

InfiniteTransition

无限循环动画

二、可选配置

2.1 EnterTransition、ExitTransition 进出场效果

EnterTransitionExitTransition 

fadeIn

淡入

fadeOut

淡出

slideIn

滑入

slideOut

滑出

slideInHorizontally

水平滑入

slideOutHorizontally

垂直滑出

slideInVertically

垂直滑入

slideOutVertically

垂直滑出

scaleIn

缩放进入

scaleOut

缩放退出

expendIn

展开进入

shrinkOut

收缩退出

expendHorizontally

水平展开

shrinkHorizontally

水平收缩

expendVertically

垂直展开

shrinkVertically

垂直收缩

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 spring

@Stable
fun <T> spring(
    dampingRatio: Float = Spring.DampingRatioNoBouncy,        //弹簧的弹性
    stiffness: Float = Spring.StiffnessMedium,        //弹簧的硬度(向结束值移动的速度)
    visibilityThreshold: T? = null
): SpringSpec<T> = SpringSpec(dampingRatio, stiffness, visibilityThreshold)

dampingRatio

弹性

DampingRatioNoBouncy        没有弹性,看不到弹簧效果
DampingRatioLowBouncy
DampingRatioMediumBouncy
DampingRatioHighBouncy        弹性最大,回弹好几次

stiffness

硬度

StiffnessLow        硬度最低,回弹效果柔软
StiffnessMediumLow
StiffnessMedium
StiffnessHigh        硬度最高,回弹效果生硬
val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = spring(
        dampingRatio = Spring.DampingRatioHighBouncy,
        stiffness = Spring.StiffnessMedium
    )
)

2.2.2 tween

@Stable
fun <T> tween(
    durationMillis: Int = DefaultDurationMillis,        //动画的持续时间
    delayMillis: Int = 0,        //延迟动画开始的时间
    easing: Easing = FastOutSlowInEasing
): TweenSpec<T> = TweenSpec(durationMillis, delayMillis, easing)
val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = tween(
        durationMillis = 300,
        delayMillis = 50,
        easing = LinearOutSlowInEasing
    )
)

2.2.3 keyframes

@Stable
fun <T> keyframes(
    init: KeyframesSpec.KeyframesSpecConfig<T>.() -> Unit
): KeyframesSpec<T> {
    return KeyframesSpec(KeyframesSpec.KeyframesSpecConfig<T>().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 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)
val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = repeatable(
        iterations = 3,
        animation = tween(durationMillis = 300),
        repeatMode = RepeatMode.Reverse
    )
)

2.2.5 infiniteRepeatable

@Stable
fun <T> infiniteRepeatable(
    animation: DurationBasedAnimationSpec<T>,
    repeatMode: RepeatMode = RepeatMode.Restart,        //从头开始还是从尾开始
    initialStartOffset: StartOffset = StartOffset(0)
): InfiniteRepeatableSpec<T> =
    InfiniteRepeatableSpec(animation, repeatMode, initialStartOffset)
val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = infiniteRepeatable(
        animation = tween(durationMillis = 300),
        repeatMode = RepeatMode.Reverse
    )
)

2.2.6 snap

@Stable
fun <T> snap(

    delayMillis: Int = 0        //延迟动画开始的时间

) = SnapSpec<T>(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 还有更多。
val CustomEasing = Easing { fraction -> fraction * fraction }

@Composable
fun EasingUsage() {
    val value by animateFloatAsState(
        targetValue = 1f,
        animationSpec = tween(
            durationMillis = 300,
            easing = CustomEasing
        )
    )
    // ...
}

三、高级别动画

3.1 数值动画 animate***AsState

3.1.1 开箱即用的常见数值类型

为单个值添加动画。当更改 targetValue 时,会从当前值向目标值渐变(自动计算出一套平滑的数值变化曲线从而实现数值变化的效果)。

Intfun animateIntAsState(
    targetValue: Int,
    animationSpec: AnimationSpec<Int> = intDefaultSpring,        //动画规格
    label: String = "IntAnimation",
    finishedListener: ((Int) -> Unit)? = null        //动画结束的回调
): State<Int>
Floatfun animateFloatAsState(
    targetValue: Float,
    animationSpec: AnimationSpec<Float> = defaultAnimation,
    visibilityThreshold: Float = 0.01f,
    label: String = "FloatAnimation",
    finishedListener: ((Float) -> Unit)? = null
): State<Float>
Colorfun animateColorAsState(
    targetValue: Color,
    animationSpec: AnimationSpec<Color> = colorDefaultSpring,
    label: String = "ColorAnimation",
    finishedListener: ((Color) -> Unit)? = null
): State<Color>
Dpfun animateDpAsState(
    targetValue: Dp,
    animationSpec: AnimationSpec<Dp> = dpDefaultSpring,
    label: String = "DpAnimation",
    finishedListener: ((Dp) -> Unit)? = null
): State<Dp>
Sizefun animateSizeAsState(
    targetValue: Size,
    animationSpec: AnimationSpec<Size> = sizeDefaultSpring,
    label: String = "SizeAnimation",
    finishedListener: ((Size) -> Unit)? = null
): State<Size>
Offsetfun animateOffsetAsState(
    targetValue: Offset,
    animationSpec: AnimationSpec<Offset> = offsetDefaultSpring,
    label: String = "OffsetAnimation",
    finishedListener: ((Offset) -> Unit)? = null
): State<Offset>
Rectfun animateRectAsState(
    targetValue: Rect,
    animationSpec: AnimationSpec<Rect> = rectDefaultSpring,
    label: String = "RectAnimation",
    finishedListener: ((Rect) -> Unit)? = null
): State<Rect>

IntOffset

IntSize

animateIntOffsetAsState

animateIntSizeAsState

@Composable
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()

数值动画

@Composable
public fun <T, V : AnimationVector> animateValueAsState(
    targetValue: T,        //目标值
    typeConverter: TwoWayConverter<T, V>,
    animationSpec: AnimationSpec<T> = remember { spring() },        //动画规格
    visibilityThreshold: T? = null,
    label: String = "ValueAnimation",
    finishedListener: ((T) -> Unit)? = null,        //动画结束的回调
): State<T>

内部实现是会开一个协程去逐渐的改变一个百分比的值,从而达到从一个状态动画过度到另一个状态的效果。

TwoWayConverter

转换器

public fun <T, V : AnimationVector> TwoWayConverter(
    convertToVector: (T) -> V,
    convertFromVector: (V) -> T,
): TwoWayConverter<T, V>

AnimationVector

动画值

AnimationVector1D

AnimationVector2D

AnimationVector3D

AnimationVector4D

一共四种,最多支持4个值。例如 Int 只有一个值使用 AnimationVector1D, Color 有 ARGB 四个值使用 AnimationVector4D。

//自定义数据类型
data class MySize(
    val width: Dp,
    val height: Dp
)
//自定义转换器
private val myConverter = TwoWayConverter<MySize, 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.2 可见性动画 AnimatedVisibility()

为内容的显示或消失添加动画。默认以“淡入扩大出现,淡出缩小消失”,可通过 EnterTransition 和 ExitTransition 设置,不同的动画可以用“+”自由组合。

@Composable
fun 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

重载版本,可以监听动画的状态。

AnimatedVisibility

@Composable
public fun AnimatedVisibility(
    visibleState: MutableTransitionState<Boolean>,        //监听动画状态
    modifier: Modifier = Modifier,
    enter: EnterTransition = fadeIn() + expandIn(),
    exit: ExitTransition = fadeOut() + shrinkOut(),
    label: String = "AnimatedVisibility",
    content: @Composable() AnimatedVisibilityScope.() -> Unit,
)

重载版本。

MutableTransitionState

public class MutableTransitionState<S>(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

内容根据状态发生改变时添加动画。默认淡出后淡入。

fun <S> AnimatedContent(
    targetState: S,
    modifier: Modifier = Modifier,
    transitionSpec: AnimatedContentTransitionScope<S>.() -> 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 对象来配置进入动画和退出动画(togetherWith 中缀表达式,是进入动画的扩展函数,传入退出动画,返回ContentTransform对象)。

Column {
    var count by remember { mutableStateOf(0) }
    Button(onClick = { count++ }) {
        Text("添加数据")
    }
    AnimatedContent(
        targetState = count,
        transitionSpec = {
            //togetherWith 中缀表达式,是进入动画的扩展函数,传入退出动画,返回的是ContentTransform
            scaleIn() togetherWith scaleOut()
        }
    ) { targetCount ->
        Text(text = "数值:${targetCount}")    //使用targetCount而不是count
    }
}

3.4 尺寸改变动画 Modifier.animateContentSize()

为可大小变化的控件添加动画(如展开收起)。注意:在修饰符链中的顺序很重要,为了确保动画流畅,务必放置在任何大小修饰符(如size或defaultMinSize)前面,以确保会将带动画效果的值的变化报告给布局。

Modifier.animateContentSize(
    animationSpec: FiniteAnimationSpec<IntSize> = 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.5 视图切换动画 Crossfad

为两个内容的切换添加淡入淡出动画。

fun <T> Crossfade(
    targetState: T,        //目标状态,每次更改都会触发动画
    modifier: Modifier = Modifier,
    animationSpec: FiniteAnimationSpec<Float> = 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.6 并发动画 updateTransiton

使用同一个状态来驱动多个动画效果。定义一个状态,用 updateTransition() 包裹创建 Transition 实例,然后去调用不同的动画方法(有简单值动画 transition.animateXXX()、可见性动画 transition.AnimatedVisibility()、内容改变动画 transition.AnimatedContent())为每个状态指定不同效果。当状态改变时 Transition 实例能监听并执行对应的动画。

@Composable
fun <T> updateTransition(
    targetState: T,        //目标状态,可以是任何数据类型,建议enum确保类型安全
    label: String? = null
): Transition<T> 

enum 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.6.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: State<Color>,
    size: State<Dp>
) {
    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.7 无限循环动画 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)。

Animatable

public fun Animatable(
    initialValue: Float,
    visibilityThreshold: Float = Spring.DefaultDisplacementThreshold,
): Animatable<Float, AnimationVector1D>

支持 Float 类型。

public fun Animatable(
    initialValue: Color
): Animatable<Color, AnimationVector4D>

支持 Color 类型。

animateTo()

public suspend fun animateTo(
    targetValue: T,
    animationSpec: AnimationSpec<T> = defaultSpringSpec,
    initialVelocity: T = velocity,
    block: (Animatable<T, V>.() -> Unit)? = null,
): AnimationResult<T, V>

更改值时为值添加动画效果。

animateDecay()

public suspend fun animateDecay(
    initialVelocity: T,
    animationSpec: DecayAnimationSpec<T>,
    block: (Animatable<T, V>.() -> Unit)? = null,
): AnimationResult<T, 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,只能用于控制动画事件,无状态没有任何生命周期概念,充当动画计算引擎。

子类说明
TargetBasedAnimation

public class TargetBasedAnimation<T, V : AnimationVector>(
    animationSpec: AnimationSpec<T>,
    typeConverter: TwoWayConverter<T, V>,
    initialValue: T,
    targetValue: T,
    initialVelocityVector: V? = null,
) : Animation<T, V>

可以直接控制动画的播放时间。

DecayAnimation

public fun DecayAnimation(
    animationSpec: FloatDecayAnimationSpec,
    initialValue: Float,
    initialVelocity: Float = 0f,
): DecayAnimation<Float, 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())
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值