Skip to content

Latest commit

 

History

History
317 lines (208 loc) · 9.26 KB

File metadata and controls

317 lines (208 loc) · 9.26 KB

State

mutableState

derivedStateOf

依赖其他状态的状态

Text 组件使用的状态 capitalizedName 的值。

var name by remember { mutableStateOf("android") }
val capitalizedName = remember { name.uppercase() }

Text(capitalizedName, modifier.clickable {
    name = "ios"
})

上面两个状态,Text 组件使用的 capitalizedName 依赖于 name 状态,当点击 Text 组件,在事件中修改 name 状态为 ios,但是 Text 组件使用的 capitalizedName 状态值并没有因为它依赖的状态 name 改变而重新计算 capitalizedName 状态的值,并且没有进行重组 Text 组件,Text 组件显示的还是 ANDROID

derivedStateOf 可以解决上面的问题,我们修改代码如下:

var name by remember { mutableStateOf("android") }
val capitalizedName by remember { derivedStateOf { name.uppercase() } }

Text(capitalizedName, modifier.clickable {
    name = "ios"
})

这是点击 Text 组件,按我们预期的效果 Text 组件文字变成了 IOS

derivedStateOf 确实解决了上面的这种情况,但是回顾我们在学习 remember 时,是不是 remember 可以传递一个参数,当这个参数变化时 remeber 函数的 lambda 会重新执行,重新计算出状态的值,看下面代码:

var name by remember { mutableStateOf("android") }
val capitalizedName = remember(name) { name.uppercase() }

Text(capitalizedName, modifier.clickable {
     name = "ios"
})

当我们再次点击 Text 组件时,Text 所显示的 文字编程了 IOS。那这里我们其实发现了一个很奇怪的问题,既然我们使用 remember 可以解决:A 状态依赖 B 状态,当修改 B 状态后,使用 A 的组件重组范围被标记为失效,等下次重组的时候,更新最新值。那么官方为什么还要给出 derivedStateOf ? 既然给出了 derivedStateOf ? 肯定有官方的原因,那么是什么原因呢?

要说明上面两个问题,我们就要再给出一个列子:

val names = remember { mutableStateListOf("android","ios") }
val capitalizedNames = remember(names) { names.map { it.uppercase() } }
Column {
     capitalizedNames.forEach {
          Text(it, modifier.clickable {
               names.add("linux")
           })
        }
}

上面列子很简单,我们的意图就是点击 Text 组件(任意一个都行),原来显示的 ANDROIDIOS,再多一个 LINUX 在它们两的下边。但是当运行到模拟器上,点击后没有任何反应。这是为什么?

这里直接给出答案,remember 函数的参数 key ,在重组时会对比 上一次重组时保存的 key本次重组时传入的 key 是不是相等,从上面代码看,上一次 remember 返回的 mutableStateList 对象 names 和 本次 remeber 返回的 mutableStateList 对象,由于 remeber 的特性可知,这个两次两个对象是一个对象,所以 key 值相等,remember 中的 lambda 并比会执行,所以上面例子中 Column lambda 重组范围并不会重新执行,那么界面上就还是 ANDROIDIOS

同上面的例子,我们反正出,但靠 remember + key 并不能解决 一个状态依赖另一个状态 时所有的状态使用情况。那么上面这群情况下,就需要用 derivedStateOf。我们把上面例子改一下:

val names = remember { mutableStateListOf("android", "ios") }
val capitalizedNames by remember { derivedStateOf { names.map { it.uppercase() } } }
Column {
     capitalizedNames.forEach {
            Text(it, modifier.clickable {
                names.add("linux")
            })
        }
}

我们运行起来再点击一下 Text 组件,发现是我们要的效果,ANDROIDIOSLINUX 都以 竖排出现在屏幕上。

从上面的讲解中,我们可以总结出:

当 状态 A 依赖状态 B ,伪代码如下:

val b = remember{ }
val a = remember{ b + 1}
  1. val a = remember(key){ b+1 }函数中的 key ,当这个 key 对象内部进行改变时,并不会引起使用了这个函数返回状态 a 的组件们发生必要的重组。
  2. 在 1 这种状态下,当明确 key 对象上次和本次重组时,对象内部发生变化而无法判断事,就应用使用 remember{ derivedStateOf{ b + 1} },来解决上面问题。

CompositionLocal

具有穿透函数功能的局部变量

不需要显示传递的函数参数

val LocalName = compositionLocalOf<String> { error("No Provide Value") }

CompositionLocalProvider(LocalName provides "Android"){
	Text(LocalName.current)
}

使用场景 :上下文 / 主题

定义 CompositionLocal 有没有默认值视情况而定,但大多数情况下抛异常。

  1. compositionLocalOf 跟踪使用它的值,当值发生变化时 重组刷新使用到值的组件。(有跟踪上的性能消耗,但当它改变时并不需要全部重组刷新)
  2. staticCompositionLocalOf 不跟踪使用值,但当值变化时,会进行全部范围重组。(没有了跟踪时的性能消耗,但却提高了当它改变时的性能消耗)

什么时候用 compositionLocalOf 创建 CompositionLocal ,使用时候使用 staticCompositionLocalOf 创建 CompositionLocal?

<<<<<<< HEAD

Animation

AnimaXxxState

动画

动画的api 共性

animateXxxAsState 状态是 remember + State。重组时 即会被 remember ,也有 State 记录被调用处的功能,重组时,只刷新被记住的位置。

animateXxxState 状态转移型动画

animateState 是 State 不是 MutableState

State 不能手动修改 State 中的值

    var big by remember { mutableStateOf(false) }
    val size by animateDpAsState(if (big) 120.dp else 48.dp)
    Box(
        modifier = modifier
            .size(size)
            .background(Color.Green)
            .clickable {
                big = !big
            }, contentAlignment = Alignment.Center) {
        Text(
            text = "Hello $name!",
            modifier = Modifier.padding(16.dp)
        )

    }

Animatable

实现上面一样的动画使用 Animatable

   var big by remember { mutableStateOf(false) }
   val size = remember(big) { if (big) 120.dp else 48.dp }
   val anim = remember { Animatable(size, Dp.VectorConverter) }

    LaunchedEffect(big) {
        // anim.snapTo(size) 不以动画完成,瞬间到达某个动画值
        anim.animateTo(size)
    }
    Box(
        modifier = modifier
            .size(anim.value)
            .background(Color.Green)
            .clickable {
                big = !big
            }, contentAlignment = Alignment.Center
    ) {
        Text(
            text = "Hello $name!",
            modifier = Modifier.padding(16.dp)
        )
    }

Animation Spec

TweenSpect

SnapSpect

describes a jump-cut type of animation

//对象
SnapSpec<T>(delayMillis)
//简便调用
fun <T> snap(delayMillis: Int = 0) = SnapSpec<T>(delayMillis)

KeyframesSpec

关键帧动画

fun <T> keyframes(init: KeyframesSpec.KeyframesSpecConfig<T>.() -> Unit): KeyframesSpec<T>

使用例子

 var isBig by remember { mutableStateOf(false) }
    val size by animateDpAsState(
        if (isBig) 200.dp else 100.dp, animationSpec =
            keyframes {
                200.dp at 0 using LinearEasing // 设置 这段动画的 速率,给一个关键帧设置 速率,设置的是从当前 开始 到一下一个关键帧的 速率
                300.dp at 150 
                20.dp at 300
                durationMillis = 1000 // 设置动画总时长
                delayMillis = 1000 // 设置动画的延时
            })
    Box(
        modifier
            .size(size)
            .background(Color.Green)
            .clickable {
                isBig = !isBig
            })

SpringSpec

/
 * dampingRatio: 阻尼比,描述弹簧有多弹
 * stiffness: 刚度,描述弹簧刚度有多强
 * visibilityThreshold:阈值,控制弹簧停止的阈值
 */
@Stable
fun <T> spring(
    dampingRatio: Float = Spring.DampingRatioNoBouncy,
    stiffness: Float = Spring.StiffnessMedium,
    visibilityThreshold: T? = null
): SpringSpec<T> =
    SpringSpec(dampingRatio, stiffness, visibilityThreshold)

RepeatableSpec

/
* 
* iterations: 重复的次数
* animation: 指定要重复的动画,只能是 DurationBasedAnimationSpec 的子类动画
* repeatMode: 重复模式:1.重启 2. 倒放、
*/
@Stable
fun <T> repeatable(
    iterations: Int,
    animation: DurationBasedAnimationSpec<T>,
    repeatMode: RepeatMode = RepeatMode.Restart,
    initialStartOffset: StartOffset = StartOffset(0)
): RepeatableSpec<T> =
    RepeatableSpec(iterations, animation, repeatMode, initialStartOffset)

InfiniteReatableSpect

和 RepeatableSpec 的区别就是一个有次数限制,一个没有。

AnimateDecay 消散型动画

惯性衰减

exponentialDecay

splineBasedDecay

Animate Listener

Animatable

animateTo(block)

animateDecay(block)

这个两个方法中,block 是动画执行每一帧时会进行回调的 lambda