1. 前言
我们首先来实现一个Compose
的动画(animateDpAsState
)
var big by remember {
mutableStateOf(false)
}
val size by animateDpAsState(if (big) 100.dp else 50.dp)
Box(
Modifier
.size(size)
.background(Color.Blue)
.clickable {
big = !big
}) {
}
运行程序,来看下效果
仔细看代码,我们可以发现,有这些疑问
- 为什么
animateDpAsState
要用val
? 而mutableStateOf
就用var
? MutableState
和State
有什么区别 ?- 为什么
animateDpAsState
不需要包remember ? - 为什么把
mutableStateOf
替换为animateDpAsState
,就可以实现动画渐变的效果 ?
接下来,我们带着疑问就来看一些这几个疑问。
2. 为什么animateDpAsState要用val ?
我们分别点进animateDpAsState
和mutableStateOf
,发现他们的返回值有区别
animateDpAsState
:
@Composable
fun animateDpAsState(
targetValue: Dp,
animationSpec: AnimationSpec<Dp> = dpDefaultSpring,
finishedListener: ((Dp) -> Unit)? = null
): State<Dp> {
return animateValueAsState(
targetValue,
Dp.VectorConverter,
animationSpec,
finishedListener = finishedListener
)
}
mutableStateOf
:
fun <T> mutableStateOf(
value: T,
policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy()
): MutableState<T> = createSnapshotMutableState(value, policy)
animateDpAsState
返回值是State
,而mutableStateOf
的返回值是MutableState
正是State
和MutableState
的区别,导致animateDpAsState
需要使用val
,而mutableStateOf
使用var
3. MutableState和State有什么区别 ?
那State
和MutableState
有什么区别呢 ?
我们查看源码可知
@Stable
interface MutableState<T> : State<T> {
override var value: T
operator fun component1(): T
operator fun component2(): (T) -> Unit
}
@Stable
interface State<out T> {
val value: T
}
MutableState
继承自State
,都有value
这个值。value
这个值改变的时候,Compose
会进行hook
,从而通知界面改变的。
而MutableState
和State
的区别在于, State
是只读的,而MutableState
是可写可读的
从代码中我们也可以看出
State
实现了kotlin
委托机制中的getValue
inline operator fun <T> State<T>.getValue(thisObj: Any?, property: KProperty<*>): T = value
而MutableState
才实现了kotlin委托机制中的setValue
inline operator fun <T> MutableState<T>.setValue(thisObj: Any?, property: KProperty<*>, value: T) {
this.value = value
}
如果有童鞋不知道Kotlin
委托的,可以看我的另一篇博客 看似普通的Android开发黑科技 - Kotlin 委托
所以animateValueAsState
为什么要返回State
呢 ? 相比答案也很明确了,因为动画是需要时间去过渡的,所以value
的变化需要交给内部去处理,不能直接给value
进行赋值,只能去指定目标值targetValue
。
4. 为什么animateDpAsState不需要包remember ?
我们点击animateValueAsState
,查看其内部源码,可以发现其内部已经包了remember
,所以就不需要我们自己再调用remember
了,但这也意味着animateXXXAsState
只能在Composable
函数内部调用了,可以看到代码中都有@Composable
注解。
@Composable
fun <T, V : AnimationVector> animateValueAsState(
targetValue: T,
typeConverter: TwoWayConverter<T, V>,
animationSpec: AnimationSpec<T> = remember {
spring(visibilityThreshold = visibilityThreshold)
},
visibilityThreshold: T? = null,
finishedListener: ((T) -> Unit)? = null
): State<T>
5. 为什么把mutableStateOf替换为animateDpAsState,就可以实现动画渐变的效果 ?
到这里,我们想必已经明白了,animateDpAsState
的返回值是State
,其内部的value
只能通过内部改变,从而更新Compose界面,animateDpAsState
内部已经帮我们处理好动画渐变值平滑的过度了。
@Composable
fun <T, V : AnimationVector> animateValueAsState(
targetValue: T,
typeConverter: TwoWayConverter<T, V>,
animationSpec: AnimationSpec<T> = remember {
spring(visibilityThreshold = visibilityThreshold)
},
visibilityThreshold: T? = null,
finishedListener: ((T) -> Unit)? = null
): State<T> {
val animatable = remember { Animatable(targetValue, typeConverter) }
val listener by rememberUpdatedState(finishedListener)
val animSpec by rememberUpdatedState(animationSpec)
val channel = remember { Channel<T>(Channel.CONFLATED) }
SideEffect {
channel.trySend(targetValue)
}
LaunchedEffect(channel) { //Compose中的协程
for (target in channel) {
// This additional poll is needed because when the channel suspends on receive and
// two values are produced before consumers' dispatcher resumes, only the first value
// will be received.
// It may not be an issue elsewhere, but in animation we want to avoid being one
// frame late.
val newTarget = channel.tryReceive().getOrNull() ?: target
launch {
if (newTarget != animatable.targetValue) {
animatable.animateTo(newTarget, animSpec) //平滑过度到目标值
listener?.invoke(animatable.value)
}
}
}
}
return animatable.asState()
}