深度解析 Jetpack Compose 布局

本文详细解析了Jetpack Compose的布局模型,包括可组合项、自定义布局、修饰符的使用以及性能优化。通过示例展示了如何实现自定义布局,如Column和VerticalGrid,强调了修饰符在布局中的关键作用。同时,讨论了固有特性测量、ParentData、对齐线等高级功能,并提供了提高布局性能的技巧,以帮助开发者构建高性能、个性化的Android界面。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Jetpack Compose 是用于构建原生 Android 界面的新工具包。它可简化并加快 Android 上的界面开发,使用更少的代码、强大的工具和直观的 Kotlin API,快速让应用生动而精彩。Compose 使用全新的组件——可组合项 (Composable) 来布局界面,使用 修饰符 (Modifier) 来配置可组合项。

本文会为您讲解由可组合项和修饰符提供支持的组合布局模型,并深入探究其背后的工作原理以及它们的功能,让您更好地了解所用布局和修饰符的工作方式,和应如何以及在何时构建自定义布局,从而实现满足确切应用需求的设计。

如果您更喜欢通过视频了解本文内容,请 点击这里 观看。

布局模型

Compose 布局系统的目标是提供易于创建的布局,尤其是 自定义布局。这要求布局系统具备强大的功能,使开发者能创建应用所需的任何布局,并且让布局具备优异的性能。接下来,我们来看看 Compose 的布局模型 是如何实现这些目标的。

Jetpack Compose 可将状态转换为界面,这个过程分为三步: 组合、布局、绘制。组合阶段执行 可组合函数,这些函数可以生成界面,从而创建界面树。例如,下图中的 SearchResult 函数会生成对应的界面树:

△ 可组合函数生成对应的界面树

△ 可组合函数生成对应的界面树

可组合项中可以包含逻辑和控制流,因此可以根据不同的状态生成不同的界面树。在布局阶段,Compose 会遍历界面树,测量界面的各个部分,并将每个部分放置在屏幕 2D 空间中。也就是说,每个节点决定了其各自的宽度、高度以及 x 和 y 坐标。在绘制阶段,Compose 将再次遍历这棵界面树,并渲染所有元素。

本文将深入探讨布局阶段。布局阶段又细分为两个阶段: 测量和放置。这相当于 View 系统中的 onMeasure 和 onLayout。但在 Compose 中,这两个阶段会交叉进行,因此我们把它看成一个布局阶段。将界面树中每个节点布局的过程分为三步: 每个节点必须测量自身的所有子节点,再决定自身的尺寸,然后放置其子节点。如下例,单遍即可对整个界面树完成布局。

△ 布局过程

△ 布局过程

其过程简述如下:

  1. 测量根布局 Row;
  2. Row 测量它的第一个子节点 Image;
  3. 由于 Image 是一个不含子节点的叶子节点,它会测量自身尺寸并加以报告,还会返回有关如何放置其子节点的指令。Image 的叶子节点通常是空节点,但所有布局都会在设置其尺寸的同时返回这些放置指令;
  4. Row 测量它的第二个子节点 Column;
  5. Column 测量其子节点,首先测量第一个子节点 Text;
  6. Text 测量并报告其尺寸以及放置指令;
  7. Column 测量第二个子节点 Text;
  8. Text 测量并报告其尺寸以及放置指令;
  9. Column 测量完其子节点,可以决定其自身的尺寸和放置逻辑;
  10. Row 根据其所有子节点的测量结果决定其自身尺寸和放置指令。

测量完所有元素的尺寸后,将再次遍历界面树,并且会在放置阶段执行所有放置指令。

Layout 可组合项

我们已经了解这个过程涉及的步骤,接下来看一下它的实现方式。先看看组合阶段,我们采用 Row、Column、Text 等更高级别的可组合项来表示界面树,每个高级别的可组合项实际上都是由低级别的可组合项构建而成。以 Text 为例,可以发现它由若干更低级别的基础构建块组成,而这些可组合项都会包含一个或多个 Layout 可组合项。

△ 每个可组合项都包含一个或多个 Layout

△ 每个可组合项都包含一个或多个 Layout

Layout 可组合项是 Compose 界面的基础构建块,它会生成 LayoutNode。在 Compose 中,界面树,或者说组合 (composition) 是一棵 LayoutNode 树。以下是 Layout 可组合项的函数签名:

@Composable
fun Layout(
    content: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    measurePolicy: MeasurePolicy
) {
    …
}

△ Layout 可组合项的函数签名

其中,content 是可以容纳任何子可组合项的槽位,出于布局需要,content 中也会包含子 Layout。modifier 参数所指定的修饰符将应用于该布局,这在下文中会详细介绍。measurePolicy 参数是 MeasurePolicy 类型,它是一个函数式接口,指定了布局测量和放置项目的方式。一般情况下,如需实现自定义布局的行为,您要在代码中实现该函数式接口:

@Composable
fun MyCustomLayout(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
         modifier = modifier,
         content = content
    ) { measurables: List<Measurable>,
         constraints: Constraints ->
        // TODO 测量和放置项目
   }
}

△ 实现 MeasurePolicy 函数式接口

在 MyCustomLayout 可组合项中,我们调用 Layout 函数并以 Trailing Lambda 的形式提供 MeasurePolicy 作为参数,从而实现所需的 measure 函数。该函数接受一个 Constraints 对象来告知 Layout 它的尺寸限制。Constraints 是一个简单类,用于限制 Layout 的最大和最小宽度与高度:

class Constraints {
    val minWidth: Int
    val maxWidth: Int
    val minHeight: Int
    val maxHeight: Int
}

△ Constraints

measure 函数还会接受 List 作为参数,这表示的是传入的子元素。Measurable 类型会公开用于测量项目的函数。如前所述,布局每个元素需要三步: 每个元素必须测量其所有子元素,并以此判断自身尺寸,再放置其子元素。其代码实现如下:

@Composable
fun MyCustomLayout(
    content: @Composable () -> Unit,
    modifier: Modifier = Modifier
) {
    Layout(
         modifier = modifier,
         content = content
    ) { measurables: List<Measurable>,
         constraints: Constraints ->
        // placeables 是经过测量的子元素,它拥有自身的尺寸值
        val placeables = measurables.map { measurable ->
            // 测量所有子元素,这里不编写任何自定义测量逻辑,只是简单地
            // 调用 Measurable 的 measure 函数并传入 constraints
            measurable.measure(c
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值