Compose自定义动画API指南

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第14天,点击查看活动详情

很多动画API都可以自定义其参数达到不同的效果,Compose也提供了相应的API供开发者进行自定义动画规范。

AnimationSpec

主要用存储动画规格,可以自定义动画的行为,在animate*AsState和updateTransition函数中,此函数默认参数为spring(),也可以说spring是默认的AnimationSpec。

Spring

spring可在起始值和结束值之间创建基于物理特性的动画。可实现类似于弹簧回弹效果的动画,先看看其构造函数:

@Stable
fun <T> spring(
    dampingRatio: Float = Spring.DampingRatioNoBouncy,
    stiffness: Float = Spring.StiffnessMedium,
    visibilityThreshold: T? = null
): SpringSpec<T> =
    SpringSpec(dampingRatio, stiffness, visibilityThreshold)
复制代码

这里有2个关键参数:dampingRatio 和 stiffness。其中,dampingRatio定义弹簧的弹性,默认值为Spring.DampingRatioNoBouncy。stiffness定义弹簧应向结束值移动的速度。默认值为 Spring.StiffnessMedium。例如以下示例:

private enum class ShowBoxState { START, END }

@ExperimentalAnimationApi
@Preview
@Composable
fun showAnim() {

    var mBoxState by remember { mutableStateOf(ShowBoxState.START) }

    val xOffset by animateDpAsState(
        targetValue = if (mBoxState == ShowBoxState.START) 0.dp else 300.dp,
        animationSpec = spring(
            dampingRatio = Spring.DampingRatioMediumBouncy,
            stiffness = Spring.StiffnessMedium,
            null
        )
    )

    Column() {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .fillMaxHeight(fraction = 0.1F)

        ) {
            Box(
                modifier = Modifier
                    .height(200.dp)
                    .absoluteOffset(xOffset)
                    .background(Color.Yellow)
            ) {
                Text(text = "Moving Box!!!")
            }
        }

        Row(
            modifier = Modifier.fillMaxSize(fraction = 1F),
            horizontalArrangement = Arrangement.Center
        ) {
            Button(onClick = {
                mBoxState =
                    when (mBoxState) {
                        ShowBoxState.START -> ShowBoxState.END
                        else -> ShowBoxState.START
                    }
            }) {
                Text(text = "Animate")
            }
        }
    }
}
复制代码

对应效果为:

Anim-spec-spring.gif

当然,dampingRatio 和 stiffness参数可以自定义很多其他值,例如:

image.gif 等等,这里就不一一说明了。

tween

渐变动画规范,在指定的时间(毫秒)内使用缓和曲线在起始值和结束值之间添加动画,也可以做到延迟效果。

我们按往常惯例,先看其构造函数:

@Stable
fun <T> tween(
    durationMillis: Int = DefaultDurationMillis,
    delayMillis: Int = 0,
    easing: Easing = FastOutSlowInEasing
): TweenSpec<T> = TweenSpec(durationMillis, delayMillis, easing)
复制代码

可见,其有三个参数,分别为:durationMillis、delayMillis、easing。durationMillis表示动画的时间间隔;delayMillis表示动画播放的延迟时间;easing是用于在开始和结束之间进行插值的缓动曲线接口,其类型为Easing,其源码有定义以下5种常用类型:

@Stable
fun interface Easing {
    fun transform(fraction: Float): Float
}
val FastOutSlowInEasing: Easing = CubicBezierEasing(0.4f, 0.0f, 0.2f, 1.0f)
val LinearOutSlowInEasing: Easing = CubicBezierEasing(0.0f, 0.0f, 0.2f, 1.0f)
val FastOutLinearInEasing: Easing = CubicBezierEasing(0.4f, 0.0f, 1.0f, 1.0f)
val LinearEasing: Easing = Easing { fraction -> fraction }
@Immutable
class CubicBezierEasing(
    private val a: Float,
    private val b: Float,
    private val c: Float,
    private val d: Float
) : Easing
复制代码

感觉其内部构造计算方式跟贝塞尔曲线有关,其实现细节我暂时还没看明白,这里不做赘述,以免误导各位。 示例如下:

@ExperimentalAnimationApi
@Composable
fun showAnim() {
    var isVisible by remember { mutableStateOf(true) }

    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        AnimatedVisibility(
            visible = isVisible,
            enter = fadeIn(
                // customize with tween AnimationSpec
                animationSpec = tween(
                    durationMillis = 2500,
                    delayMillis = 300,
                    easing = LinearOutSlowInEasing
                )
            ),
            // you can also add animationSpec in fadeOut if need be.
            exit = fadeOut() + shrinkHorizontally(),

            ) {
            Text(text = "ANIMATION SHOW")
        }
        Spacer(modifier = Modifier.height(18.dp))
        Button(onClick = {
            isVisible = !isVisible
        }) {
            Text(text = "start")
        }
    }
}
复制代码

以上代码中,我们修改渐入动画fadeIn,指定动画规范为tween,动画时长2500ms,延迟300ms以线性方式播放。对应的效果为:

Anim-spec-tween.gif

keyframes

根据在动画时长内的不同时间戳中指定的快照值添加动画效果,效果类似于原生动画中的帧动画,keyframes只有一个参数init,用于动画的初始化,对于动画中每个关键帧,都可以指定 Easing 来确定插值曲线。以下为官方示例:

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
    }
)
复制代码

上述代码就是在0毫秒和持续时间处指定值。如果不特殊指定,它们将分别默认为动画的起始值和结束值。示例可通过修改tween()的示例代码看到效果,这里不再赘述。

repeatable

看名字应该就知道了,是重复性动画类别,如果你是这样猜的,那恭喜你猜对了。repeatable反复运行基于时长的动画,直到达到指定的迭代计数。我们先观察其构造函数:

@Stable
fun <T> repeatable(
    iterations: Int,
    animation: DurationBasedAnimationSpec<T>,
    repeatMode: RepeatMode = RepeatMode.Restart
): RepeatableSpec<T> =
    RepeatableSpec(iterations, animation, repeatMode)
复制代码

iterations、animation和repeatMode。iterations表示动画重复次数,animation就是要重复的动画,repeatMode用来指定动画是从头开始(RepeatMode.Restart)还是从结尾开始(RepeatMode.Reverse)重复播放。这三个参数足够我们编写精美重复动画了,例如以下示例:

@ExperimentalAnimationApi
@Composable
fun showAnim() {
    val isShow = remember { mutableStateOf(value = true) }
    val isEnabled = remember { mutableStateOf(true)}
    val alpha by animateFloatAsState(
        targetValue = if (isShow.value) 0.1f else 1.0f,
        animationSpec = repeatable(
            iterations = 7, 
            animation = tween(durationMillis = 1000),
            repeatMode = RepeatMode.Reverse
        ),
        finishedListener = {
            isEnabled.value = true
        }
    )
    Column(
        modifier = Modifier
            .fillMaxSize()
            .background(Color(0xFFEDC9AF))
            .padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Button(
            onClick = {
                isShow.value = !isShow.value
                isEnabled.value = false
            },
            colors = ButtonDefaults.buttonColors(
                Color(0xFF6C541E), Color(0xCCFFFFFF)
            ),
            enabled = isEnabled.value
        ) {
            Text(
                text = "Animation Show! ",
                modifier = Modifier.padding(12.dp)
            )
        }
        Icon(
            Icons.Filled.Favorite,
            "",
            tint = Color(0xFFCE2029),
            modifier = Modifier
                .size(300.dp)
                .alpha(alpha = alpha)
        )
    }
}
复制代码

对应的效果为:

Anim-spec- repeat.gif

这里是设置了重复次数的,对应也有无重复次数设置的API。

infiniteRepeatable

infiniteRepeatable 与 repeatable 很像,但它会重复无限次的迭代,其构造函数如下:

@Stable
fun <T> infiniteRepeatable(
 animation: DurationBasedAnimationSpec<T>,
 repeatMode: RepeatMode = RepeatMode.Restart
): InfiniteRepeatableSpec<T> =
 InfiniteRepeatableSpec(animation, repeatMode)
复制代码

可见,其参数相较于repeatable 就少了一个次数限制,其参数含义都一样,有兴趣可以修改repeatable 中的示例,你会发现动画会一直播放,不会停下。这里不再赘述。

snap

主要用于立即将值切换到结束值,很多情况下我们需要提前结束动画,这时就要用到snap()。其构造函数如下:

@Stable
fun <T> snap(delayMillis: Int = 0) = SnapSpec<T>(delayMillis)
复制代码

可见,这里只有一个参数,延时毫秒数,用来指定延迟动画播放的开始时间。其使用方式如下:

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = snap(delayMillis = 50)
)
复制代码

按照官方所言,

在 View 界面系统中,对于基于时长的动画,需要使用 ObjectAnimator 等 API;对于基于物理特性的动画,则需要使用 SpringAnimation。同时使用这两个不同的动画 API 并不容易。但Compose 中的 AnimationSpec 让我们能够以统一的方式处理这些动画。
复制代码

AnimationVector

大多数 Compose 动画 API 都支持将 Float、Color、Dp 以及其他基本数据类型作为开箱即用的动画值,但有时也需要为其他数据类型(包括您的自定义类型)添加动画效果。其核心意义在于动画播放期间,任何动画值都表示为AnimationVector。使用相应的TwoWayConverter即可将值转换为AnimationVector,反之亦然。例如,用于Int的TwoWayConverter如下所示:

val IntToVector: TwoWayConverter<Int, AnimationVector1D> =     TwoWayConverter({ AnimationVector1D(it.toFloat()) }, { it.value.toInt() })
复制代码

动画中使用的每种数据类型都可以根据其维度转换为 AnimationVector1D、AnimationVector2D、AnimationVector3D 或 AnimationVector4D(因为Color色值实际上是 red、green、blue 和 alpha 这 4 个值的集合)。目前来说,我还没见过用这个函数的,感觉实际参考意义不大,如果有更好的意见,请留言,大家互相学习。

总结

结合前两篇文章,我们对Compose动画算是有了一个整体认识,前两篇文章为高级别动画和低级别动画,高级别动画是由低级别动画封装而来,低级别动画指的是动画更偏于底层。这里对其常见使用场景做一个梳理:

·AnimatedVisibility : 控制布局显示隐藏;

·animate*Size : 对应布局、颜色、大小等发生变化时可用;

·updateTransition : 存在多个动画,对动画进行组合时可用;

·Animatable : 控制动画初始值等过程时可用。

... ...

目前而言,Compose处于起步后加速阶段,官方正在强推,也许迟早会变得常见,就像几年前的Kotlin一样。

猜你喜欢

转载自juejin.im/post/7108754204855894024