在之前【 Compose 是如何将数据转换成 UI 的?】 文章中,我们分析过 Compose 数据和 UI 转换的各个阶段以及它们精确排序的规则,主要包含三个阶段:
1. 组合,2. 布局(测量 + 布局),3. 绘制。
思维模型如下:
这种思维模型适用于绝大多数 的 Layout 可组合项,但却有一个打破规则的布局不遵循此架构 - - SubcomposeLayout。
比如现在有一个需求:你要构建一个包含一千个 item 的列表,而这些 item 根本无法同时容纳在屏幕上。在这种情况下,组合所有这些子 item 将是不必要的资源浪费,如果其中大部分甚至都看不到,那为什么要预先组合这么多 item 呢?
常规的做法应该是:
示例图:
SubcomposeLayout 背后的主要思想:它需要首先对部分或所有子可组合项进行测量,然后使用该信息来确定是否组合部分或所有子项。所以我们可以把 SubcomposeLayout() 理解为是一种动态布局。
说到这,你是不是会想到用于创建列表的组件 LazyColumn 或者 LazyRow?难道它们内部也是用的 SubcomposeLayout?
@Composable
fun LazyColumn(
... ...
) {
LazyList(
... ...
)
}
@Composable
internal fun LazyList(
) {
LazyLayout(...)
}
@Composable
fun LazyLayout(
... ...
) {
SubcomposeLayout(
... ...
)
}
fun LazyRow(
... ...
) {
LazyList(
... ...
)
}
@Composable
internal fun LazyList(
) {
LazyLayout(...)
}
@Composable
fun LazyLayout(
... ...
) {
SubcomposeLayout(
... ...
)
}
我们发现 LazyColumn 和 LazyRow 组件都是构建在 SubcomposeLayout 之上的,这才使它们能够在滚动时按需添加内容。
SubcomposeLayout 能做到将一部分的组合过程延后到测量阶段或布局阶段再进行,这样能实现拿到需要测量或布局完成才能拿到的数据后再组合的业务需求,比如用于实现动态布局。也就是说,布局阶段的测量步骤需要先于组合阶段进行。
BoxWithConstraints 也在底层使用了 SubcomposeLayout,但这个组件有点不一样: BoxWithConstraints 允许你获取父级传递的约束(constrains),并在延迟的组合阶段使用它们:
setContent {
// BoxWithConstraints 是用 SubcomposeLayout 实现的,可以当成 Box() 使用
// 也是使用 SubcomposeLayout 的简化组件,比如只需要拿到测量尺寸限制
// 在 BoxWithConstraints 可以获取到 constraints 变量,它是父组件提供的尺寸限制
// constraints 是在测量阶段才能拿到的东西,组合阶段要早于测量阶段
// 所以如果使用 Box()是拿不到该变量的,因为 Box() 还处在组合过程,还没到测量阶段
BoxWithConstraints() {
// BoxWithConstraints() 的 lambda 发生在测量阶段
Text("Hi, Compose", Modifier.align(Alignment.Center))
}
}
动态布局的场景很常见,比如我们在原生 View 系统开发需要针对不同的屏幕比如横屏或竖屏提供两套同名但布局不同的 xml 文件,会定义在不同的 layout 目录下分别加载;因为 Compose 是纯代码开发的,所以这类场景可以根据测量阶段的尺寸限制判断要使用哪种布局。
setContent {
// 根据不同尺寸加载不同的布局
BoxWithConstraints {
// maxHeight 调用的是 constraints.maxHeight
if (maxHeight < 300.dp) {
SmallImage()
} else {
BigImage()
}
}
}
由于 SubcomposeLayout 改变了 Compose 各个阶段的常规的流程以允许动态执行,因此在性能方面存在一定的成本和限制。
那么什么时候应该使用 SubcomposeLayout 呢?
判断需不需要用 SubcomposeLayout 的一种快速的好方法:至少一个子可组合项的组合阶段取决于另一个子可组合项的测量结果。
如果你只需要一个子项的测量值来测量其他子项,则可以使用常规 Layout() 可组合项来实现。
所以,一句话总结:相比传统 Layout(),SubcomposeLayout 性能要差一点,尽量少用。