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
}) {
}
}
}
复制代码
有兴趣的可以自己跑一下代码 看看 两者之间效果的差别
要理解上面的写法 不然后面很容易蒙蔽
首先要注意 下面的返回值
他返回的是一个State 而不是mutableState
这两者什么区别?
1个是可变,一个是不可变, 反馈到我们代码层面,就是State 是一个只读的,mutableState是可读且可写的
所以这行代码我们再仔细品味一下 就可以知道啥意思了
**这个targetValue的数值的变化 最终会促使 animateDpAsState发生变化 **
类似的 我们可以看下 还有多少种animate的api
animate 动画的缺陷
其实这个动画还是很好用的,但是有个缺点,是他没办法 设置动画的初始值, 为啥? 因为你不管怎么设置 animate的参数, 他始终都代表一个state的渐变过程, 不管是50-100 还是100-50 他都是渐变过去的, 他是没办法直接从50秒到100的
你再回过头想想看我们传统view的属性动画,是不是可以方便的设置 初始值?
我们还可以跟进一下代码:
可以发现 我们的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的用法 要点
首先我们来看下 下面代码的意思
这里其实字面上很好理解就是 创建了一个默认值,也就是初始值,只是有人会觉得奇怪,这个VectroConvter是用来干啥的?
这里要注意,对于Animatable来说,他在底层运算的时候 其实都是Float,而我们的dp 类型显然不是Float 类型,所以 通常情况下 我们要实现 TwoWayConverter 这个接口
这里 系统默认给我们实现了一个,所以 我们直接用就行
AnimationVector1D 这个东西又是啥?
细看一下 竟然还有2d 3d 4d?
其实不难理解,这个就是动画计算的维度,我们这里只是宽高 那自然就是1维的,如果是坐标系 那自然就是2d的
再接着看:
这里是干啥的? 为啥要这么写?
其实你可以发现这个animateto 是一个suspend协程的方法
有人问 那不是可以直接lifecyclescpe。launch?
其实ide 也已经告诉你了, 在compose中 不要直接使用 lifecyclescope ,为啥?
因为直接使用 会导致 每次recompose的时候。这些协程 又走一遍,十分低效。
所以用 LaunchedEffect 来替代前者,可以方便的解决这个问题
如图所示:
这个key 用来干啥?
key的意思就是 当这个key 发生变化的时候,这个block里面的协程 会执行, 如果key不变 则不执行(换句话说 只在初始化的时候执行一次)
很多时候 下面这种写法更为流行,代表就只在初始化的时候 执行一次
LaunchedEffect(Unit){
}
复制代码
另外回到开始的问题,Animatable 如何设置动画的初始值?
LaunchedEffect(change) {
anim.snapTo(size)
//anim.animateTo(size)
}
复制代码
关键就是snapTo方法,这个地方可以设置初始值,和下面的animateTo 其实不一样,下面的animateTo 是 动画执行,而snapTo 是直接执行,瞬间执行
用哪个更好?
能用animateAsState实现的 就用这个,因为简单,否则 就用Animatable