Compose动画:AnimatedContent (2)

1,094 阅读3分钟

最近太忙了,忙着身份的转变 —— 喜得千金一枚 —— 所以,工作、家里事情一堆杂揉,已经很久没更文章了。

疫情走到了大多数人都不想要的状态,希望各位朋友,能避阳尽量避吧。

今天接着上篇《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 宽高变化,也就动起来了。

1.gif

这里,我们获取的 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 420420 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)
       })
   )
// ...

2.gif 有观察到动画的变化吗?

另外,不想用 spring 的话,还要以替换成 tween 类型的动画。

小结

今天先写么多,咳得我十分缺水。回见!