State
State, MutableState
compose
中, 系统需要监听值的变化, 进行自动重组, 普通对象无法监听变化, 需要使用 State
, MutableState
val count = mutableIntStateOf(1)
remember
compose 重组时, 由于 state 声明在函数体中, 会被重新初始化, 为了保证值不变, 加上 remember {}
, 保证在重组时, 值不变
val count = remember { mutableIntStateOf(0) }
由于直接用 remember{}
获取的是 State
对象, 使用其 value
值时不方便; 借助 by
委托语法, 直接赋值, 方便使用
var count2 by remember { mutableStateOf(10) }
rememberSaveable
remember
解决了重组过程中的值重新初始化的问题, 但是当 Activity 横竖屏切换时, 依旧会重新初始化; Compose 内部使用 rememberSaveable
来解决
val count3 by rememberSaveable { mutableIntStateOf(1) }
Stateless, Stateful, 状态管理
状态上提
StateHolder
viewModel
LiveData, RxJava, Flow 转 State
转换方法 | 依赖 |
---|---|
LiveData.observeAsState() |
androidx.compose:runtime-livedata |
Flow.collectAsState() | |
Observable.subscribeAsState() | androidx.compose: runtime-rxjava3 |
深层嵌套传值
创建 CompositionLocal
val CountLocalContent = compositionLocalOf(neverEqualPolicy()){
100
}
声明 Provider, 提供值
CompositionLocalProvider(CountLocalContent.provides(1001 + count2)) {
CompositionLocalProvider(CountLocalContent.provides(1101 + count2)) {
CountText(count = count2)
}
}
在子 Composeable 中, 获取值
@Composable
fun CountLocalText() {
val countLocal = CountLocalContent.current
Text(
text = "CountLocalText count Text $countLocal "
)
}
副作用
和 React
类似, Compose
重组, 方法体会重复执行, 但是部分操作并不需要每次都执行; 使用 Effect
去避免这种情况,
SideEffect
在重组成功时执行; Compose
会有多次重组, 但不一定每次都成功; SideEffect
在重组成功时执行
- 重组成功时执行
- 可能执行多次
DisposableEffect
和 React
中的 useEffect
类似, 在指定数据发生变化时执行; 如果传入 Unit
则在 Composeable
整个流程中, 只执行一次
DisposableEffect(vararg keys: Any?) {
// register(callback)
onDispose {
// unregister(callback)
}
}
LaunchedEffect
在 Composeable
中启动协程; 在进入 composition
阶段执行, 在离开 composition
时, 自动取消; 也可以传入 key
, 监听值变化时执行
LaunchedEffect(vararg keys: Any?) {
// do Something async
}
rememberCoroutineScope
LaunchedEffect
只能在 Compose
函数中执行; 如果在非 Composeable
中执行协程 (eg. Button
的点击事件中), 则可以由 rememberCoroutineScope
获取协程的 scope
@Composable fun Test() {
val scope = rememberCoroutineScope()
Button( onClick = {
scope.launch { // do something }
} ) {
Text("click me")
}
}
rememberUpdatedState
一般和 LaunchEffect
和 DisposableEffect
一起使用;
- 当
LaunchEffect
不监听任何 state 时, 且内部使用传入Composable
参数时 - 该
Composeable
状态变化, 重组传入新值, 由于LaunchedEffect
和生命周期绑定, 不会重建 -
LaunchedEffect
获取传入的参数值, 永远是第一次的值 - 使用
rememberUpdatedState
则可以获取Composeable
重组后的新值
React
也有类似的问题, 当props
变化时, 内部并不会获取新的值
@Composable
fun CountText(count: Int) {
val count by rememberUpdatedState(newValue = count)
// 如果不使用 rememberUpdatedState, 底下LaunchedEffect始终获取的是第一次进入composition阶段的值
LaunchedEffect(Unit) {
Log.d("zy", "------ LaunchedEffect-- start $count")
for (i in 1..10) {
delay(1 * 1000L)
Log.d("zy", "------ LaunchedEffect- $i >>> $count")
}
Log.d("zy", "------ LaunchedEffect-- end $count")
}
Text(
text = "count Text $count "
)
}
snapshotFlow
将 State
值的变化, 转换为 Flow
冷流
上面的 rememberUpdateState
, 在 LaunchedEffect
绑定 Composeable
生命周期时, 只能获取到最新的值, snapshotFlow
可以获取到每一次值的变化
val count by rememberUpdatedState(newValue = count)
LaunchedEffect(Unit) {
snapshotFlow { count }
.collect {
delay(1 * 100L)
Log.d("zy", "------ LaunchedEffect-- snapshotFlow $count")
}
}
derivedStateOf
根据一个 State
派生出另一个 State
;
仅在两个 State
变化状态不同步时使用, 如果两个 State
是同时变化, 就直接使用 源State
更新 UI 了, 使用 derivedStateOf
纯粹添加性能开销
@Composable
fun MessageList(messages: List<Message>) {
Box {
val listState = rememberLazyListState()
LazyColumn(state = listState) {
// ...
}
// 根据 listState 派生出 showButton;
// showButton 变化频率低, 使用 derivedStateOf,部分UI可以不用重组, 可以降低性能开销
val showButton by remember {
derivedStateOf {
listState.firstVisibleItemIndex > 0
}
}
AnimatedVisibility(visible = showButton) {
ScrollToTopButton()
}
}
}
produceState
开启一个协程, 将普通数据, 转换为 State
; eg. 将 Flow
, LiveData
之类的数据, 转换成 State
内部有一个 awaitDispose
方法, 可以在离开 Composition
时做清理工作
val countChange = produceState(initialValue = 1) {
value = count + 1
awaitDispose {
// 做清理工作
}
}