Jetpack Compose 动画笔记2——Animatable
作者:访客发布时间:2023-12-19分类:程序开发学习浏览:94
使用 ObjectAnimator 时,ObjectAnimator.ofFloat(circleView, "radius", 100f)
,只填一个 value 值,执行动画时会先调用 circleView.getRadius()
获取当前 radius 值作为动画起点,再运动至终点 100f。这和 animateFloatAsState
是非常相似的,执行运动时,都是以当前状态值为起点,运动至目标值。
如果使用 ObjectAnimator
时填入多个 value 值:ObjectAnimator.ofFloat(circleView, "radius", 20f, 100f)
,那么第一个值会被作为起点(初始值),最后一个值会被作为终点。注意了,初始值不一定就是当前状态值:例如,当前一刻圆的半径为 50f,我要在下一帧开始动画,要求动画初始值是 20f,目标值是 100f。与 ObjectAnimator.ofFloat(circleView, "radius", 20f, 100f)
对应的 Compose 动画应该怎么写呢?
用 animateFloatAsState()
是无法指定动画起始值的,它的动画起点只能是当前状态值。也就是说 animateFloatAsState
适用场景仅限于状态之间的来回切换(一个状态的终点是另一个状态的起点)。想要对动画做更多的定制,指定动画起始值,就要使用更底层的动画 API —— Animatable。
class Animatable<T, V : AnimationVector>(
initialValue: T,
val typeConverter: TwoWayConverter<T, V>,
private val visibilityThreshold: T? = null,
val label: String = "Animatable"
)
可以看到 Animatable 的构造函数有两个必填参数:初始值 initialValue 与 类型转换器 typeConverter。我们先不讨论怎么用 Animatable 指定动画初始值,从简单的开始,用 Animatable 来实现 animateDpAsState
在两个状态间来回切换的效果:
var isSmall by remember { mutableStateOf(true) }
// val size by animateDpAsState(targetValue = if (small) 100.dp else 200.dp)
val animatableSize = remember {
Animatable(
initialValue = if (isSmall) 100.dp else 200.dp,
typeConverter = Dp.VectorConverter
)
}
Box(
Modifier
.size(animatableSize.value)
.background(MaterialTheme.colorScheme.primary)
.clickable { isSmall = !isSmall }
)
- 首先,用
animateXxxAsState
就是自动挡,它的内部已经包装了一层 remember,Animatable 是手动挡,需要自己包装一层 remember; - 其次,创建 Animatable 对象实例时,不能也不应该能用 by 属性委托,应该直接用 =。想在这里使用 by 的人,无非是想着后续使用变量时,能直接写
animatable
获取到动画值 而不用animatable.value
,不过我们后续是要用到 Animatable 的一些方法的,所以这里不要用by
!// 假设这里可以用 by 属性委托 val animatableSize: Dp by remember { Animatable(...) } // 注意 animatableSize 现在是一个 Dp 实例,而不是 Animatable 实例 // 后续会需要用到 Animatable 实例,以调用 Animatable 的一些方法, // 但因为属性委托的原因,已经没机会获取到 Animatable 实例了
现在运行代码:
没有效果,回过头看填入数值的地方,你会发现 animateDpAsState(targetValue = ...)
的形参是 targetValue
,而构造函数 Animatable(initialValue = ...)
构造函数的参数名是 initialValue
。点击导致 isSmall 状态变化,只是重新设置了 Animatable 的初始值,而没有设置动画的目标值。
animateTo()
我们需要手动调用 Animatable.animateTo(targetValue = ...)
来设置动画的目标值,第一次使用时你会发现这个方法居然还是个挂起函数
.clickable {
small = !small
lifecycleScope.launch {
animatableSize.animateTo(if (small) 100.dp else 200.dp)
}
}
即使套上一层 lifecycleScope.launch{}
也是不行的,在 Compose 中不能直接使用 lifecycleScope.launch{}
,运行时会报错,即使不报错,也不应该这么写,在 .clickable{}
里用 animateTo()
设置动画本身就是不合理的。为什么呢?单纯的从设计理念的角度看,Compose 是声明式 UI 框架,状态驱动界面。事件产生处 .clickable{}
不应该直接和界面/动画打交道,而应该是修改状态,让状态驱动界面/动画:
var isSmall by remember { mutableStateOf(true) }
val animatableSize = remember {
Animatable(
initialValue = if (isSmall) 100.dp else 200.dp,
typeConverter = Dp.VectorConverter
)
}
// 状态驱动动画
remember(isSmall) {
协程.launch {
animatableSize.animateTo(if (isSmall) 100.dp else 200.dp)
}
}
Box(
Modifier
.size(animatableSize.value)
.background(MaterialTheme.colorScheme.primary)
.clickable {
// 修改状态
isSmall = !isSmall
}
)
LaunchedEffect()
在 Compose 里面,有一个函数专门用于启动协程:LaunchedEffect
,它的作用是在 Compose 的生命周期中启动协程
fun LaunchedEffect(
key1: Any?,
block: suspend CoroutineScope.() -> Unit
)
参数是 key 用于标识这个协程,当 key 变化时,会取消之前的协程,启动新的协程。所以我们可以在 LaunchedEffect
里面调用挂起函数 animateTo()
,完整代码如下:
var isSmall by remember { mutableStateOf(true) }
val size = remember(isSmall) { if (isSmall) 100.dp else 200.dp }
val animatableSize = remember {
Animatable(
initialValue = size,
typeConverter = Dp.VectorConverter
)
}
// 状态驱动动画
LaunchedEffect(isSmall) {
animatableSize.animateTo(targetValue = size)
}
Box(
Modifier
.size(animatableSize.value)
.background(MaterialTheme.colorScheme.primary)
.clickable {
// 修改状态
isSmall = !isSmall
}
)
snapTo()
animateTo()
会让动画从当前值运动至目标值,如果想让动画直接跳到目标值,可以使用 snapTo()
。那如果我要设置动画起始值,在调用 animateTo()
之前先调用 snapTo()
,不就可以让动画从指定的起始值运动至目标值了吗。
...
LaunchedEffect(isSmall) {
if (!isSmall) {
// 要变大时,先让动画跳到 0.dp,再运动至目标值
animatableSize.snapTo(targetValue = 0.dp)
}
animatableSize.animateTo(targetValue = size)
}
...
可以看到两个状态虽然是 100.dp 和 200.dp,不过在 100dp -> 200dp 的过程中,我们指定了动画的起始值是 0.dp,所以动画是会从 100.dp 跳到 0.dp,再运动至 200.dp。
- 程序开发学习排行
-
- 1鸿蒙HarmonyOS:Web组件网页白屏检测
- 2HTTPS协议是安全传输,为啥还要再加密?
- 3HarmonyOS鸿蒙应用开发——数据持久化Preferences
- 4记解决MaterialButton背景颜色与设置值不同
- 5鸿蒙HarmonyOS实战-ArkUI组件(Stack)
- 6鸿蒙HarmonyOS实战-ArkUI组件(RelativeContainer)
- 7鸿蒙HarmonyOS实战-ArkUI组件(mediaquery)
- 8[Android][NDK][Cmake]一文搞懂Android项目中的Cmake
- 9鸿蒙HarmonyOS实战-ArkUI组件(GridRow/GridCol)
- 最近发表
-
- WooCommerce最好的WordPress常用插件下载博客插件模块的相关产品
- 羊驼机器人最好的WordPress常用插件下载博客插件模块
- IP信息记录器最好的WordPress常用插件下载博客插件模块
- Linkly for WooCommerce最好的WordPress常用插件下载博客插件模块
- 元素聚合器Forms最好的WordPress常用插件下载博客插件模块
- Promaker Chat 最好的WordPress通用插件下载 博客插件模块
- 自动更新发布日期最好的WordPress常用插件下载博客插件模块
- WordPress官方最好的获取回复WordPress常用插件下载博客插件模块
- Img to rss最好的wordpress常用插件下载博客插件模块
- WPMozo为Elementor最好的WordPress常用插件下载博客插件模块添加精简版