前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >来聊聊 Jetpack Compose 动画,一篇搞定(下篇)

来聊聊 Jetpack Compose 动画,一篇搞定(下篇)

作者头像
玖柒的小窝
发布2021-12-06 22:42:56
5870
发布2021-12-06 22:42:56
举报
文章被收录于专栏:各类技术文章~各类技术文章~

引言

  • 在上篇我们详细介绍了 Compose 封装的各种高级别的 API,让我们知道能用极少的代码来写出自己想要的动画效果,并且体会到了声明式编程的魅力。
  • 本篇会进一步深入了解 Compose 的动画使用,了解一些更底层的 API。

一、动画的底层 API 调用

我们建议结合协程来管理你的自定义动画(Coroutine-based Animations)

  • Jetpack Compose 的动画,最终都离不开 Animation<T, V : AnimationVector> 这个接口,我们可以看下这张图
animation-low-level.svg
animation-low-level.svg
  • 图中表示官方提供了实现 Animation 接口的两个类,分别是 TargetBasedAnimationDecayAnimation ,他们分别使用着独特的动画规范, AnimationSpec<T>DecayAnimationSpec<T>
  • TargetBasedAnimation 是为基于目标的动画服务的。
  • 它会一直持有的动画的起始值起始速度,以及不因动画进行和发生改变的结束值,保存下来的值会提供便捷的 API 供外部访问。
  • 需要注意的是,Compose 的动画是可以被打断的。若当前的 TargetBasedAnimation 被打断,会使用当前状态的数值速度来构造一个新的实例;
  • 建议直接使用 Animatable.animateTo 来使用 TargetBasedAnimation
  • DecayAnimation 直译为衰减动画。指的是随着时间的推移,初始设定的速度会逐渐变慢。
  • 同样的,提供了便捷的 API 来访问动画过程中的状态,如初始速度、初始速度方向、动画规范、类型转化器等;
  • 手动控制 DecayAnimation触发时机是没有作用的,建议直接使用高级 API 如 Animatable.animateDecay 等。
  • 因为使用的思路大致相同,本文会重点聊聊 Animatable.animateToAnimatable.animateDecay 将不详述。

二、今天的主角:挂起函数 AnimateTo

2.1 先来聊聊 Animatable

  • Animatable 是被设计成数据容器的类,用于保存目标数值。当动画执行的时候(如执行 animateTo ),它所持有的数据会根据设定进行改变;
  • 上面提到,当动画被打断时,创建的新的 TargetBasedAnimation 会“继承”当前的数值和速度,而数值就是从 Animatable 对象中获取的;
  • 在代码中,我们可以通过以下方式创建一个 Animatable 对象
代码语言:javascript
复制
val alpha = remember { Animatable(0f) } // 指的是 alpha 是一个 Animatable 对象,它当前持有的数值为 0f
复制代码

2.2 接着是主角 animateTo

代码语言:javascript
复制
// Animatable.kt
suspend fun animateTo(
    targetValue: T,
    animationSpec: AnimationSpec<T> = defaultSpringSpec,
    initialVelocity: T = velocity,
    block: (Animatable<T, V>.() -> Unit)? = null
): AnimationResult<T, V>
复制代码
  • animateTo 是一个挂起函数,也是 Animatable 对象的拓展函数;
  • 在当前方法中,我们可以设置 TargetBasedAnimation目标值动画规范初始速度
  • 其中, block 参数回调的是每一帧动画返回的 Animatable 对象,即我们可以在这里监听并获取动画的实时状态;
  • 方法的返回值返回的是动画的结果 AnimationResult
  • 由于这是挂起函数,所以方法的结束表明了动画的结束。在同一个协程域中,在方法后执行的逻辑都表明在动画结束后执行。
  • 动画的结果 AnimationResult 包含两部分,一部分是动画的结束状态 endState ,包含了动画停止时最后一帧的状态(详见 AnimationState );另一部分是动画的结束原因,包含正常结束或者是触达边界结束(详见 AnimationEndReason )。

2.3 更便捷的写法 animate

  • 官方在 SuspendAnimation.kt 中定义了如下方法:
代码语言:javascript
复制
suspend fun animate(
	initialValue: Float,
	targetValue: Float,
	initialVelocity: Float = 0f,
	animationSpec: AnimationSpec<Float> = spring(),
	block: (value: Float, velocity: Float) -> Unit
)
复制代码
  • 这是一种更简洁的写法,优点是代码量更少,无需提前定义 animatable 变量,缺点是当前方法不会返回任何当前动画相关的属性;
  • 同样的,衰减动画也有与之对应的 API animateDecay

三、多动画并发执行实践

3.1 监听动画的开始和结束

  • 得益于协程,我们可以非常方便地监听到执行动画中的不同阶段;
  • 也不要再想什么 onAnimationStart()onAnimationEnd() 的监听器了,在动画的挂起函数里面是没有的。
代码语言:javascript
复制
// 顺序动画
val scope = rememberCoroutineScope()
scope.launch {
	animate(/*...*/) // 先执行 animationA
	viewmodel.doSomething() // animationA 指定完成后执行 viewModel 逻辑
	animate(/*...*/) // 等待 viewModel 执行完成后执行 animationB
}
复制代码

3.2 并行执行动画

  • 我们建议在 CoroutineScope 中使用 launch 创建新的协程来并发执行动画;
代码语言:javascript
复制
// 并行动画
val scope = rememberCoroutineScope()
scope.launch {
	launch {
		animate(/*...*/) // 动画 A
	}
	launch {
		animate(/*...*/) // 动画 B
	}
}
// 上述写法,动画 A 和动画 B 会在同时执行。
复制代码

四、简单说说动画的触发机制

  • 对Compose 有一定了解的同学都会知道,Compose 界面的重组都是依靠 State<T> 来触发的,而动画也不例外。
  • 动画触发 UI 刷新的类是 AnimationState<T, V> ,上面提到的返回最后一帧的动画状态也就是前面提到的这个类。而在动画的执行过程中,负责动画的挂起函数会持续发送新的 State 到 UI 上,具体可以看到这个方法 Animatable.runAnimation
代码语言:javascript
复制
// All the different types of animation code paths eventually converge to this method.
private suspend fun runAnimation(
    animation: Animation<T, V>,
    initialVelocity: T,
    block: (Animatable<T, V>.() -> Unit)?
): AnimationResult<T, V> {
    // Store the start time before it's reset during job cancellation.
    val startTime = internalState.lastFrameTimeNanos
    return mutatorMutex.mutate {
        try {
            // ...
            endState.animate(
                animation,
                startTime
            ) {
                updateState(internalState) // 关键逻辑
                // ...
            }
            val endReason = if (clampingNeeded) BoundReached else Finished
            endAnimation()
            AnimationResult(endState, endReason)
        } catch (e: CancellationException) {
            endAnimation()
            throw e
        }
    }
}
复制代码

五、小结

  1. 了解 Compose 动画的底层 API ,知道了 AnimationAnimatableAnimationStateanimateTo 之间的关系;
  2. 知道了 Compose 动画主要划分为 TargetBasedAnimationDecayAnimation 两种;
  3. 知道了更高定制化的动画可以使用 Animatable 中的挂起函数实现;
  4. 了解到多个动画顺序和并发执行的写法;
  5. 了解到动画在源码中的触发机制入口,知道了内部是 AnimationState 触发数据刷新;

完结撒花。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言
  • 一、动画的底层 API 调用
  • 二、今天的主角:挂起函数 AnimateTo
    • 2.1 先来聊聊 Animatable
      • 2.2 接着是主角 animateTo
        • 2.3 更便捷的写法 animate
        • 三、多动画并发执行实践
          • 3.1 监听动画的开始和结束
            • 3.2 并行执行动画
            • 四、简单说说动画的触发机制
            • 五、小结
            相关产品与服务
            容器服务
            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档