Compose 延迟布局

266 阅读3分钟

Lazy layouts in Compose

了解如何在 Compose 中制作滚动列表,以及这样为什么比使用 RecyclerView 更简单。了解为什么不允许嵌套滚动列表、如何采用不同方式实现嵌套、为什么列表项的大小绝不能为 0 像素、为什么提供唯一的键非常重要,以及项动画如何运作。最后,您将探索如何显示网格、使用自定义布局管理器,以及了解如何优化性能以提高滚动速度。

1. Lazy Lists

  • LazyColumn
  • LazyRow
  • Lazy grids

image.png

Lazy Lists 中可以添加一个 item,也可以添加多个 item。 截屏2025-09-03 20.26.17.png

截屏2025-09-03 20.27.56.png

给 contentPadding 传入 PaddingValues 为 Lazy lists 设置内边距。这个内边距不会缩减 items 的显示区域。 截屏2025-09-03 20.28.53.png

使用 horizontalArrangement 给 LazyRow 设置 item 与 item 之间的间距。其他 Lazy lists 类似。使用 Arrangement.spacedBy() 可以自定义间距。 截屏2025-09-03 20.32.06.png

LazyListState 存储滚动位置还有列表的一些实用信息。 截屏2025-09-03 20.35.38.png

实现点击底部按钮使列表滚动到顶部。 截屏2025-09-03 20.39.55.png

2. Lazy Grids

  • LazyVerticalGrid
  • LazyHorizontalGrid

截屏2025-09-03 20.46.35.png

可以同时设置垂直方向和水平方向的 item 间距。 截屏2025-09-03 20.48.40.png

LazyGridState 和 LazyListState 具有相同的作用。 截屏2025-09-03 20.50.45.png

3. Lazy Layout

截屏2025-09-04 11.42.18.png

截屏2025-09-04 11.43.42.png

Item animations

传入 Modifier.animateItemPlacement() 即可使用动画。 截屏2025-09-04 11.49.08.png

一定要为 item 提供 key,以便找到被移动元素的新位置,item 内的记忆状态也会一起移动。 截屏2025-09-04 11.52.47.png

传入的 key 类型一定是 Bundle 支持的类型,比如基础类型、枚举、Parcelable 等等。其作用是创建 Activity 时保留相应状态。 截屏2025-09-04 11.53.27.png

截屏2025-09-04 11.55.50.png

4. Useful Tips

4.1 Don't use 0-pixel sized item

不要使用大小为 0 像素的 item。因为 item 大小为 0,Lazy Layout 会组合它所有的 item。

4.2 Avoid nesting components scrollable in the same direction

避免嵌套可向同一方向滚动的组件。

仅适用于没有预定义尺寸的子级组件嵌套在可向同一方向滚动的另一个父级组件中。

嵌套不同滚动方向的组件是允许的。

子级组件有预定义尺寸也可以嵌套。

4.3 Beaware of putting multiple elements in one item

注意将多个元素放入一个 item 中的情况。

  • 元素无法单独组合,作为一个整体组合。

截屏2025-09-03 21.07.25.png

  • 会干扰 scrollToItem 和 animateScrollToItem

scrollToItem(2) 会使 Item(3) 显示在顶部。

截屏2025-09-03 21.12.41.png

  • 也有一些将多个元素放入同一个 item 的有效用例,比如在一个列表内添加多个分割线。不希望分割线更改滚动索引,因此分割线不应该作为一个独立元素。而且分割线的尺寸很小,不会影响性能。 截屏2025-09-03 21.15.27.png

  • Consider using custom arrangements 考虑使用自定义排列方式

列表元素不足够填充整个屏幕,但是需要在列表底部展示 Footer。需要自定义 verticalArrangement。 截屏2025-09-04 10.51.36.png

/**
 * Vertically places the layout children.
 *
 * @param totalSize Available space that can be occupied by the children, in pixels.
 * @param sizes An array of sizes of all children, in pixels.
 * @param outPositions An array of the size of [sizes] that returns the calculated positions
 *   relative to the top, in pixels.
 */
fun Density.arrange(totalSize: Int, sizes: IntArray, outPositions: IntArray)

截屏2025-09-04 10.54.28.png

5. Performance

只有在发布模式下运行且启用了 R8 优化时,才能可靠地衡量延迟布局的性能。 截屏2025-09-04 11.05.58.png

6. Optimization

6.1 Reusing

重复使用组合。

不需要丢弃 item 并启动全新组合可以节省一些时间。 截屏2025-09-04 11.11.41.png

截屏2025-09-04 11.12.16.png

重复使用布局节点。 截屏2025-09-04 11.12.59.png

为不同类型的 item 设置 contentType,Compose 只能在相同类型的 item 之间重复使用组合。 截屏2025-09-04 11.09.38.png

截屏2025-09-04 11.16.11.png

6.2 Prefetching

预提取

当无需处理任何新项的情况下滚动时。 截屏2025-09-04 11.27.59.png

如果有新项进入屏幕,将需要完成更多的步骤。 截屏2025-09-04 11.29.43.png

这样可能会导致渲染有延迟,导致卡顿,也就是掉帧。 截屏2025-09-04 11.30.27.png

我们可以提前准备这些工作(Compose、Measure),在 UI 线程空闲时候完成下一帧的组合和测量(Compose、Measure)。 截屏2025-09-04 11.32.36.png