Jetpack Compose - Animatable与animateAsState (六)

Compose 能用属性动画吗?

不可以,至少目前没有方便使用属性动画的方式,传统view开发的时候用的是属性动画比较多,但是在Compose中 不存在属性动画的概念, 谷歌针对Compose的特性,开发了一套新的动画api

生硬的界面变化

来看下下面的代码,这个代码很简单 其实就是点击了以后 这个区域变大了而已,但是显然这是没有动画的, 效果比较生硬

setContent {

    var size by remember {
        mutableStateOf(50.dp)
    }

    Box(
        Modifier
            .size(size)
            .background(Color.Blue)
            .clickable {
                size = 100.dp
            }) {

    }

}
复制代码

动画渐变大小

看下 下面的代码,主要看 size2的写法,size 就是上一小节的写法 主要用来对比用的

var change by mutableStateOf(false)

setContent {

    var size by remember {
        mutableStateOf(50.dp)
    }


    // MutableState 可以修改 而State 不能直接手动改
    val size2 by animateDpAsState(if (change) 100.dp else 50.dp)

    Row{
        Box(
            Modifier
                .size(size)
                .background(Color.Blue)
                .clickable {
                    size = 100.dp
                }) {

        }
        Box(
            Modifier
                .size(size2)
                .background(Color.Red)
                .clickable {
                    change = true
                }) {

        }
    }
}
复制代码

有兴趣的可以自己跑一下代码 看看 两者之间效果的差别

要理解上面的写法 不然后面很容易蒙蔽

首先要注意 下面的返回值

image.png

他返回的是一个State 而不是mutableState

这两者什么区别?

image.png

1个是可变,一个是不可变, 反馈到我们代码层面,就是State 是一个只读的,mutableState是可读且可写的

所以这行代码我们再仔细品味一下 就可以知道啥意思了

image.png

**这个targetValue的数值的变化 最终会促使 animateDpAsState发生变化 **

类似的 我们可以看下 还有多少种animate的api

image.png

animate 动画的缺陷

其实这个动画还是很好用的,但是有个缺点,是他没办法 设置动画的初始值, 为啥? 因为你不管怎么设置 animate的参数, 他始终都代表一个state的渐变过程, 不管是50-100 还是100-50 他都是渐变过去的, 他是没办法直接从50秒到100的

你再回过头想想看我们传统view的属性动画,是不是可以方便的设置 初始值?

我们还可以跟进一下代码:

image.png

可以发现 我们的animate动画的实现 实际上是基于这个animatable的实现!

那么 animatable是不是可以支持 设置动画初始值的操作呢?(当然可以

Animatable 的 用法

看下面这段代码,其实运行起来的结果和前面一个小节的代码是一模一样的,只不过这里使用的是 Animatable 这个会比前面那个小节的写法要复杂许多

var change by mutableStateOf(false)
setContent {
    val size = remember(change) {
        if (change) 50.dp else 100.dp
    }
    val anim = remember { Animatable(size, Dp.VectorConverter) }
    LaunchedEffect(change) {
        anim.animateTo(size)
    }
    Row {

        Box(
            Modifier
                .size(anim.value)
                .background(Color.Red)
                .clickable {
                    change = !change
                }) {

        }
    }
}
复制代码

下面我们来解释下Animatable的用法 要点

首先我们来看下 下面代码的意思

image.png

这里其实字面上很好理解就是 创建了一个默认值,也就是初始值,只是有人会觉得奇怪,这个VectroConvter是用来干啥的?

这里要注意,对于Animatable来说,他在底层运算的时候 其实都是Float,而我们的dp 类型显然不是Float 类型,所以 通常情况下 我们要实现 TwoWayConverter 这个接口

image.png

这里 系统默认给我们实现了一个,所以 我们直接用就行

AnimationVector1D 这个东西又是啥?

细看一下 竟然还有2d 3d 4d?

image.png

其实不难理解,这个就是动画计算的维度,我们这里只是宽高 那自然就是1维的,如果是坐标系 那自然就是2d的

再接着看:

image.png

这里是干啥的? 为啥要这么写?

其实你可以发现这个animateto 是一个suspend协程的方法 image.png

有人问 那不是可以直接lifecyclescpe。launch?

image.png

其实ide 也已经告诉你了, 在compose中 不要直接使用 lifecyclescope ,为啥?

因为直接使用 会导致 每次recompose的时候。这些协程 又走一遍,十分低效。

所以用 LaunchedEffect 来替代前者,可以方便的解决这个问题

如图所示: image.png

这个key 用来干啥?

key的意思就是 当这个key 发生变化的时候,这个block里面的协程 会执行, 如果key不变 则不执行(换句话说 只在初始化的时候执行一次)

很多时候 下面这种写法更为流行,代表就只在初始化的时候 执行一次

LaunchedEffect(Unit){
    
}
复制代码

另外回到开始的问题,Animatable 如何设置动画的初始值?

LaunchedEffect(change) {
    anim.snapTo(size)
    //anim.animateTo(size)
}
复制代码

关键就是snapTo方法,这个地方可以设置初始值,和下面的animateTo 其实不一样,下面的animateTo 是 动画执行,而snapTo 是直接执行,瞬间执行

用哪个更好?

能用animateAsState实现的 就用这个,因为简单,否则 就用Animatable

猜你喜欢

转载自juejin.im/post/7111494489935970340