前言
Compose 会通过几个不同的“阶段”来渲染帧。如果我们观察一下 Android View 系统,就会发现它有 3 个主要阶段:测量、布局和绘制。Compose 和它非常相似,但开头多了一个叫做“组合”的重要阶段。
帧的三个阶段
组合(Composition)
布局(测量和放置)(Layout)
绘制 (Drawing)
View 的三个阶段
measure、layout、draw
compose 关键词
幂等:输入计算出相同结果的工作
单向数据流:数据能够沿一个方向(从组合到布局,再到绘制)生成帧. 【有特例】
智能重组: Compose 会跟踪不同阶段中的状态读取
状态读取
状态的创建:实现state 接口的类 例如:mutableStateOf() ,而 LiveData,RxJava,Flow 可以通过兼容api进行转换
读取:直接访问 value 属性,或使用 Kotlin 属性委托(值引用时与value读取方式等价)
重启作用域:每个可以在读取状态发生更改时重新执行的代码块
// State read without property delegate.
val paddingState: MutableState<Dp> = remember { mutableStateOf(8.dp) }
Text(
text = "Hello",
modifier = Modifier.padding(paddingState.value)
)
// State read with property delegate.
var padding: Dp by remember { mutableStateOf(8.dp) }
Text(
text = "Hello",
modifier = Modifier.padding(padding)
)
分阶段状态读取
第 1 阶段:组合
@Composable 函数或 lambda 代码块中的状态读取会影响组合阶段,并且可能会影响后续阶段。
当状态值发生更改时,Recomposer 会安排重新运行所有要读取相应状态值的可组合函数,如果输入未更改,则跳过
第 2 阶段:布局
布局阶段包含两个步骤:测量和放置。测量步骤会运行传递给 Layout 可组合项的测量 lambda,
放置步骤会运行 layout 函数的放置位置块、Modifier.offset { … } 的 lambda 块,等等。
@Compose
fun Layout(measure:(*)->Unit){
measure.invoke(*)
}
interface LayoutModifier{
MeasureScope.measure
}
更确切地说,测量步骤和放置步骤分别具有单独的重启作用域.放置步骤中的状态读取不会在此之前重新调用测量步骤。
不过,这两个步骤通常是交织在一起的,因此在放置步骤中读取的状态可能会影响属于测量步骤的其他重启作用域。
第 3 阶段:绘制
绘制代码期间的状态读取会影响绘制阶段。常见示例包括 Canvas()、Modifier.drawBehind 和 Modifier.drawWithContent。
当状态值发生更改时,Compose 界面只会运行绘制阶段。
优化状态读取
Box {
val listState = rememberLazyListState()
Image(
// Non-optimal implementation!
Modifier.offset(
with(LocalDensity.current) {
// State read of firstVisibleItemScrollOffset in composition
(listState.firstVisibleItemScrollOffset / 2).toDp()
}
)
)
LazyColumn(state = listState)
}
总体思路是正确的:尝试将状态读取定位到尽可能靠后的阶段,从而尽可能降低 Compose 需要执行的工作量。
重组循环(循环阶段依赖项)