Compose 动画 (二) : 为什么animateDpAsState要用val ? MutableState和State有什么区别 ?

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
        }) {

}

运行程序,来看下效果
Compose 动画 (二) : 为什么animateDpAsState要用val ? MutableState和State有什么区别 ?_第1张图片
仔细看代码,我们可以发现,有这些疑问

  • 为什么animateDpAsState要用val ? 而mutableStateOf就用var ?
  • MutableStateState有什么区别 ?
  • 为什么animateDpAsState不需要包remember ?
  • 为什么把mutableStateOf替换为animateDpAsState,就可以实现动画渐变的效果 ?

接下来,我们带着疑问就来看一些这几个疑问。

2. 为什么animateDpAsState要用val ?

我们分别点进animateDpAsStatemutableStateOf,发现他们的返回值有区别

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
正是StateMutableState的区别,导致animateDpAsState需要使用val,而mutableStateOf使用var

3. MutableState和State有什么区别 ?

StateMutableState有什么区别呢 ?
我们查看源码可知

@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,从而通知界面改变的。

MutableStateState的区别在于, 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()
}

你可能感兴趣的:(Compose,动画,kotlin,android,Compose,动画,animateDpAs)