最近太忙了,忙着身份的转变 —— 喜得千金一枚 —— 所以,工作、家里事情一堆杂揉,已经很久没更文章了。
疫情走到了大多数人都不想要的状态,希望各位朋友,能避阳尽量避吧。
今天接着上篇《Compose动画:AnimatedContent》,继续说 AnimatedContent,讲讲动画效果的相关细节。
动画自定义
既然讲动画,必然「自定义」是很重要的,否则光沿用默认效果,那就实在太鸡肋了。在上篇文章中,其实我们为了延长动画时长,已经做了动画的自定义了,只是当时仅仅简单 copy 了源码,然后修改了时间参数而已 —— 简单来说,自定义的核心就是生成 ContentTransform 对象,用以覆盖 transitionSpec 参数的默认值。
ContentTransform
ContentTransform 即「内容转换」类。
@ExperimentalAnimationApi
class ContentTransform(
val targetContentEnter: EnterTransition,
val initialContentExit: ExitTransition,
targetContentZIndex: Float = 0f,
sizeTransform: SizeTransform? = SizeTransform()
) {
var targetContentZIndex by mutableStateOf(targetContentZIndex)
var sizeTransform: SizeTransform? = sizeTransform
internal set
}
源码很简单了,几个参数说明:
- targetContentEnter:目标组件显示动画
- initialContentExit:当前组件消失动画
- targetContentZIndex:组件 Z 轴高度
- sizeTransform:尺寸变化控制
前两个我们已经比较熟悉了,不多说。 targetContentZIndex 主要是用于控制组件的层级关系。默认情况下,所有组件的 z 轴值为 0。控制此值,就可以控制进入显示的组件的 z 向位置。只要进入组件的 zIndex 值大于等于其他组件,那它都是更「高」的,即,盖在其他组件上。sizeTransform 则是设置尺寸变化时的变化方式,因为退出、进入的组件,从尺寸上讲,可不一定是一样的。
案例:尺寸变换动画
根据上面的介绍,我们来实现这样的一个动画:组件形状在纵向矩形和横向矩形之间切换。翻译一下,组件本身未变,只是作为矩形的长边方向变了 —— 那么,改变宽高就能做到了。如下:
@Composable
fun AnimatedContentTest2() {
Box(Modifier.background(Color.LightGray.copy(alpha = 0.3f)).padding(16.dp).fillMaxWidth().height(200.dp)) {
val landscaped = remember { mutableStateOf(false) }
AnimatedContent(landscaped.value, transitionSpec = {
ContentTransform(
fadeIn(),
fadeOut(),
sizeTransform = SizeTransform()
)
}) { ls ->
Text(
text = "$DES\n$DES\n$DES\n$DES\n$DES\n$DES\n$DES\n$DES\n$DES\n", modifier = Modifier
.background(Color.Blue)
.width(if (ls) 120.dp else 40.dp)
.height(if (ls) 40.dp else 120.dp)
.padding(8.dp), textAlign = TextAlign.Center
)
}
Button(onClick = { landscaped.value = !landscaped.value }, modifier = Modifier.padding(16.dp).align(Alignment.BottomCenter)) {
Text(text = "切为${if (landscaped.value) "纵" else "横"}向")
}
}
}
横纵变换的组件就是 AnimatedContent 内部的 Text 了,AnimatedContent 监听 landscaped 的值变化,从而触发 Text 宽高变化,也就动起来了。
这里,我们获取的 ContentTransform 对象,是直接构造出来的,这和之前文章里面的 with 方法,没有本质区别。其中 SizeTransform 也是使用了默认方法构造:
@ExperimentalAnimationApi
fun SizeTransform(
clip: Boolean = true,
sizeAnimationSpec: (initialSize: IntSize, targetSize: IntSize) -> FiniteAnimationSpec<IntSize> =
{ _, _ -> spring(visibilityThreshold = IntSize.VisibilityThreshold) }
): SizeTransform = SizeTransformImpl(clip, sizeAnimationSpec)
sizeAnimationSpec 的参数,就确定了尺寸变化时「动」的方式,其 lambda 会回调两个尺寸参数:初始大小、目标大小。这二位是指动画组件的动前、动后尺寸吗?我们打印出来看看。
// ....
AnimatedContent(landscaped.value, transitionSpec = {
ContentTransform(
fadeIn(),
fadeOut(),
sizeTransform = SizeTransform(sizeAnimationSpec = { initialSize: IntSize, targetSize: IntSize ->
Log.d("jx-test", "$initialSize - $targetSize")
spring(visibilityThreshold = IntSize.VisibilityThreshold)
})
)
}) { ls ->
// ....
点击从纵向切到横向时,打印如下:
2022-12-21 15:26:15.119 7487-7487 jx-test com.example.animation D 140 x 420 - 420 x 140
再从横向切回纵向时:
2022-12-21 15:26:20.005 7487-7487 jx-test com.example.animation D 420 x 140 - 140 x 420
140 x 420 和 420 x 140,确实就对应着 Text 的两个方向的实际像素宽高。
再来看,默认效果是 spring「弹跳」,我们适当调整下它的参数来看看有什么变化。
AnimatedContent(landscaped.value, transitionSpec = {
ContentTransform(
fadeIn(),
fadeOut(),
sizeTransform = SizeTransform(sizeAnimationSpec = { initialSize: IntSize, targetSize: IntSize ->
Log.d("jx-test", "$initialSize - $targetSize")
// dampingRatio改成了中度
spring(dampingRatio = Spring.DampingRatioMediumBouncy, visibilityThreshold = IntSize.VisibilityThreshold)
})
)
// ...
有观察到动画的变化吗?
另外,不想用 spring 的话,还要以替换成 tween 类型的动画。
小结
今天先写么多,咳得我十分缺水。回见!