Jetapck Compose动画笔记3——AnimationSpec

Jetpack Compose------AnimationSpec

不管是使用 animateXxxAsState() 还是 Animatable,都可以通过参数 AnimationSpec<T> 来对动画进行详细配置,Spec 就是规格的意思,AnimationSpec 就是动画规格:

AnimationSpec<T> 是一个接口,继承树🌳如下:

FiniteAnimationSpec

DurationBasedAnimationSpec

TweenSpec

TweenSpec 就是 AnimationSpec 其中的一个实现类,用于配置动画的持续时间、延迟和缓动曲线。

这里的 "tween" 是 "between" 的缩写,因为 TweenSpec 配置的是两个动画值之间的动画。例如动画起始值是 0.dp,目标值是 100.dp,TweenSpec 可以配置从 0.dp 变化到 100.dp 这个过程的时长、过程开始的延迟,以及过程的速度曲线。

调用 tween() 可以快速得到一个 TweenSpec 对象

kotlin 复制代码
Box(modifier = Modifier.fillMaxSize()) {
    val boxSize = remember { 100.dp }
    val screenWidth = LocalConfiguration.current.screenWidthDp.dp
    var isStart by remember { mutableStateOf(true) }
    val offsetX = remember(isStart) { if (isStart) 0.dp else screenWidth - boxSize }
    val animatableOffsetX = remember {
        Animatable(
            initialValue = offsetX,
            typeConverter = Dp.VectorConverter
        )
    }

    LaunchedEffect(key1 = isStart) {
        animatableOffsetX.animateTo(
            targetValue = offsetX,
            animationSpec = tween(
                delayMillis = 1000,    // 延迟 1 秒
                durationMillis = 2000, // 持续 2 秒
                easing = LinearEasing  // 匀速变化
            )
        )
    }

    Box(
        modifier = Modifier
        .offset(x = animatableOffsetX.value)
        .size(boxSize)
        .background(MaterialTheme.colorScheme.primary)
        .clickable { isStart = !isStart }
    )
}

SnapSpec

Animatable.snapTo() 函数的作用差不多,SnapSpec 可以将动画配置成瞬间从初始值跳到目标值。不过 SnapSpec 可以设置动画延迟。

kotlin 复制代码
// 官方便捷函数
fun <T> snap(delayMillis: Int = 0) = SnapSpec<T>(delayMillis)

...

animatableOffsetX.animateTo(
    targetValue = offsetX,
    animationSpec = snap(delayMillis = 1000) // 延迟 1 秒
)

KeyFramesSpec

使用关键帧来配置动画。

这里推荐使用简便函数 keyFrames() 而不是 KeyFramesSpec 构造函数,如果使用构造函数的写法,还得手动创建一个 KeyFramesSpecConfig 对象:

kotlin 复制代码
// 官方便捷函数
fun <T> keyframes(
    init: KeyframesSpec.KeyframesSpecConfig<T>.() -> Unit
)

...

animatableOffsetX.animateTo(
    targetValue = offsetX,
    animationSpec = keyframes {
        durationMillis = 3000
        0.dp at 0
        (screenWidth - boxSize) / 2 at 1000 with FastOutLinearInEasing
        0.dp at 2000 with FastOutSlowInEasing
        (screenWidth - boxSize) at 3000
    }
)

首先,keyframes() 的参数 init 是一个拥有 KeyframesSpecConfig 上下文的函数类型。

  • 0.dp at 0 只是调用了调用了 KeyframesSpecConfig 对象的 infix 函数,在 0 ms 的位置添加了一个关键帧,值为 0 dp。
  • (screenWidth - boxSize) / 2 at 1000 with FastOutLinearInEasing:在 1000 ms 的位置添加值为 (screenWidth - boxSize) / 2 的关键帧,并指定缓动曲线。注意这个缓动曲线的生效区间是从当前关键帧 1000 到下一个关键帧 2000,而不是 0 到 1000。
  • 如果添加关键帧的时候没有指定缓动曲线,默认是 LinearEasing。

那么综上所述,上面的 KeyFramesSpec 共包含了 4 个关键帧,因为第一个和最后一个关键帧的位置就在动画的两端,所以实际上,这个动画被切了 2 刀,划分成了 3 部分:

  • 第一部分:时间 0-1000 ms,从位置 0 到位置 (screenWidth - boxSize) / 2,匀速运动;
  • 第二部分:时间 1000-2000 ms,从位置 (screenWidth - boxSize) / 2 到位置 0,加速运动;
  • 第三部分:时间 2000-3000 ms,从位置 0 到 位置 screenWidth - boxSize,运动先加速后减速;

再看一遍效果是不是:

前面提到的 TweenSpec、SnapSpec 和 KeyFramesSpec,这三个 Spec 都是 DurationBasedAnimationSpec 接口的实现类

DurationBasedAnimationSpec 的特点就是动画的时长是确切的、固定的。还有时长不确切的动画?还真有,无限循环的动画就是时间不确切的,以及接下来这个弹簧动画模型,它的时间也是不固定,或者说是不确切的。

SpringSpec

SpringSpec 可以创建出基于物理特性的弹簧动画。

kotlin 复制代码
// 官方便捷函数
fun <T> spring(
    dampingRatio: Float = Spring.DampingRatioNoBouncy,
    stiffness: Float = Spring.StiffnessMedium,
    visibilityThreshold: T? = null
) : SpringSpec<T>

...

animatableOffsetX.animateTo(
    targetValue = offsetX,
    animationSpec = spring(
        dampingRatio = Spring.DampingRatioHighBouncy,
        stiffness = Spring.StiffnessLow
    )
)
  • 第一个参数 dampingRatio 用于设置弹簧的阻尼比。阻尼比是用来描述系统在受到扰动后振荡及衰减的情形。

    例如吊在弹簧上正处于平衡状态的重物,若用力往上提起重物再松手,就会上上下下的摆动。在摆动过程中,物体试图回到平衡位置,不过会出现过冲。因为有损耗(例如摩擦力)会形成系统的阻尼,会使物体的振荡渐渐变小,最后衰减。阻尼比就是描述系统的振荡多快可以衰减。

    说人话就是设置"弹性",默认值是无弹性 Spring.DampingRatioNoBouncy。

  • 第二个参数 stiffness 设置的是弹簧的刚度。刚度就是物体在外力作用下抵抗变形的程度。

    下面两个处于平衡状态的弹簧,如果要把他俩拉长 2 cm,哪一边需要更大的力气?明显是左边吧,那是因为左边弹簧的刚性更强。

  • 第三个参数 visibilityThreshold,可见阈值,其实物理模型里是不存在这个东西的,它用于设置弹簧震荡的最小可见阈值。例如刚才的例子,松手后弹簧会上上下下地震荡,震荡会因为阻尼而越来越小,到最后可能震荡地幅度可能已经小到几毫米,人眼已经无法察觉。手机里的弹簧动画也一样,到最后弹簧的震荡已经小到肉眼无法察觉,可是手机的 CPU 却还是在计算物体的速度、施加的力,这时候纯粹是在浪费资源。设置一个 visibilityThreshold,如果震荡幅度已经小于可见阈值,那么就让物体不再运动,直接置于静止状态,节省资源。

RepeatableSpec

RepeatableSpec 用来配置重复播放动画,直至达到指定的迭代计数。有个前提是,被重复执行的动画,它的时长必须是已经固定的。换句人话就是动画必须是 DurationBasedAnimationSpec 的子类型。

kotlin 复制代码
// 官方便捷函数
fun <T> repeatable(
    iterations: Int,
    animation: DurationBasedAnimationSpec<T>,
    repeatMode: RepeatMode = RepeatMode.Restart,
    initialStartOffset: StartOffset = StartOffset(0)
): RepeatableSpec<T>

...

animatableOffsetX.animateTo(
    targetValue = offsetX,
    animationSpec = repeatable(
        iterations = 3, // 重复 3 次
        animation = tween(), // 指定要重复的动画
        repeatMode = RepeatMode.Reverse // 反转上一次迭代的动画
    )
)
  • iterations:迭代次数;
  • animation:需要重复的动画,注意必须是 DurationBasedAnimationSpec 子类型;
  • repeatMode:指定动画重复时是从头开始 (RepeatMode.Restart) 还是反转上一次动画 (RepeatMode.Reverse) ;
  • initialStartOffset:初始偏移,指的是时间偏移,之所以不直接用 Long 类型,是因为偏移有两种类型:
    • StartOffsetType.Delay:延迟型初始偏移;
    • StartOffsetType.FastForward:快进型初始偏移。

InfiniteRepeatableSpec

InfiniteRepeatableSpec 与 RepeatableSpec 类似,但它会无限重复播放动画。

kotlin 复制代码
// 官方便捷函数
fun <T> infiniteRepeatable(
    animation: DurationBasedAnimationSpec<T>,
    repeatMode: RepeatMode = RepeatMode.Restart,
    initialStartOffset: StartOffset = StartOffset(0)
): InfiniteRepeatableSpec<T>
相关推荐
网络研究院1 小时前
Android 安卓内存安全漏洞数量大幅下降的原因
android·安全·编程·安卓·内存·漏洞·技术
凉亭下1 小时前
android navigation 用法详细使用
android
小比卡丘4 小时前
C语言进阶版第17课—自定义类型:联合和枚举
android·java·c语言
前行的小黑炭5 小时前
一篇搞定Android 实现扫码支付:如何对接海外的第三方支付;项目中的真实经验分享;如何高效对接,高效开发
android
落落落sss6 小时前
MybatisPlus
android·java·开发语言·spring·tomcat·rabbitmq·mybatis
代码敲上天.7 小时前
数据库语句优化
android·数据库·adb
一丝晨光9 小时前
Java、PHP、ASP、JSP、Kotlin、.NET、Go
java·kotlin·go·php·.net·jsp·asp
GEEKVIP9 小时前
手机使用技巧:8 个 Android 锁屏移除工具 [解锁 Android]
android·macos·ios·智能手机·电脑·手机·iphone
model200511 小时前
android + tflite 分类APP开发-2
android·分类·tflite
彭于晏68911 小时前
Android广播
android·java·开发语言