Compose 布局基础知识

Jetpack Compose 可让您更轻松地设计和构建应用的界面。Compose 通过以下方法将状态转换为界面元素:

  1. 元素的组合
  2. 元素的布局
  3. 元素的绘图

Compose 通过组合、布局、绘图将状态转换为界面

本文重点介绍了元素的布局,说明 Compose 为协助构建界面元素而提供的一些构建块。

Compose 中布局的目标

布局系统的 Jetpack Compose 实现有两个主要目标:

可组合函数的基础知识

可组合函数是 Compose 的基本构建块。可组合函数是一种发出 Unit 的函数,用于描述界面中的某一部分。该函数接受一些输入并生成屏幕上显示的内容。如需详细了解可组合项,请参阅 Compose 构思模型文档。

一个可组合函数可能会发出多个界面元素。不过,如果您未提供有关如何排列这些元素的指导,Compose 可能会以您不喜欢的方式排列它们。例如,以下代码会生成两个文本元素:

@Composable
fun ArtistCard() {
    Text("Alfred Sisley")
    Text("3 minutes ago")
}

如果您未提供有关如何排列这两个文本元素的指导,Compose 会将它们堆叠在一起,使其无法阅读:

两个文本元素相互叠加,使文本无法阅读

Compose 提供了一系列现成可用的布局来帮助您排列界面元素,并可让您轻松定义自己的更专业布局。

标准布局组件

在许多情况下,您只需使用 Compose 的标准布局元素即可。

使用 Column 可将多个项垂直地放置在屏幕上。

@Composable
fun ArtistCardColumn() {
    Column {
        Text("Alfred Sisley")
        Text("3 minutes ago")
    }
}

两个文本元素按列布局排列,因此文本清晰易读

同样,使用 Row 可将多个项水平地放置在屏幕上。ColumnRow 都支持配置它们所含元素的对齐方式。

@Composable
fun ArtistCardRow(artist: Artist) {
    Row(verticalAlignment = Alignment.CenterVertically) {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Column {
            Text(artist.name)
            Text(artist.lastSeenOnline)
        }
    }
}

图中显示了一个更复杂的布局,在一列文本元素旁边有一个小图形

使用 Box 可将元素放在其他元素上。Box 还支持为其包含的元素配置特定的对齐方式。

@Composable
fun ArtistAvatar(artist: Artist) {
    Box {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Icon(Icons.Filled.Check, contentDescription = "Check mark")
    }
}

图中显示两个元素堆叠在一起

通常,您只需要这些构建块。您可以自行编写可组合函数,将这些布局组合成更精美的布局,让其适合您的应用。

比较三个简单的布局可组合项:Column、Row 和 Box

如需在 Row 中设置子项的位置,请设置 horizontalArrangementverticalAlignment 参数。对于 Column,请设置 verticalArrangementhorizontalAlignment 参数:

@Composable
fun ArtistCardArrangement(artist: Artist) {
    Row(
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.End
    ) {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Column { /*...*/ }
    }
}

内容项靠右对齐

布局模型

在布局模型中,通过单次传递来布局界面树。首先,系统会要求每个节点对自身进行测量,然后以递归方式完成所有子节点的测量,并将尺寸约束条件沿着树向下传递给子节点。再后,确定叶节点的尺寸和放置位置,并将经过解析的尺寸和放置指令沿着树向上回传。

简而言之,父节点会在其子节点之前进行测量,但会在其子节点的尺寸和放置位置确定之后再对自身进行调整。

请参考以下 SearchResult 函数。

@Composable
fun SearchResult() {
    Row {
        Image(
            // ...
        )
        Column {
            Text(
                // ...
            )
            Text(
                // ...
            )
        }
    }
}

此函数会生成以下界面树。

SearchResult
  Row
    Image
    Column
      Text
      Text

SearchResult 示例中,界面树布局遵循以下顺序:

  1. 系统要求根节点 Row 对自身进行测量。
  2. 根节点 Row 要求其第一个子节点(即 Image)进行测量。
  3. Image 是一个叶节点(也就是说,它没有子节点),因此该节点会报告尺寸并返回放置指令。
  4. 根节点 Row 要求其第二个子节点(即 Column)进行测量。
  5. 节点 Column 要求其第一个子节点 Text 进行测量。
  6. 由于第一个节点 Text 是叶节点,因此该节点会报告尺寸并返回放置指令。
  7. 节点 Column 要求其第二个子节点 Text 进行测量。
  8. 由于第二个节点 Text 是叶节点,因此该节点会报告尺寸并返回放置指令。
  9. 现在,节点 Column 已测量其子节点,并已确定其子节点的尺寸和放置位置,接下来它可以确定自己的尺寸和放置位置了。
  10. 现在,根节点 Row 已测量其子节点,并已确定其子节点的尺寸和放置位置,接下来它可以确定自己的尺寸和放置位置了。

在搜索结果界面树中进行测量以及确定尺寸和放置位置的顺序

性能

Compose 通过只测量一次子项来实现高性能。单遍测量对性能有利,使 Compose 能够高效地处理较深的界面树。如果某个元素测量了它的子元素两次,而该子元素又测量了它的子元素两次,依此类推,那么一次尝试布置整个界面就不得不做大量的工作,这将很难让应用保持良好的性能。

如果布局由于某种原因需要多次测量,Compose 会提供一个特殊的系统,即“固有特性测量”。如需详细了解此功能,请参阅 Compose 布局中的固有特性测量

由于测量和放置是布局传递的不同子阶段,因此任何仅影响项的放置而不影响测量的更改都可以单独执行。

在布局中使用修饰符

Compose 修饰符中所述,您可以使用修饰符来修饰或扩充可组合项。修饰符对于自定义布局至关重要。例如,我们在这里链接多个修饰符来自定义 ArtistCard

@Composable
fun ArtistCardModifiers(
    artist: Artist,
    onClick: () -> Unit
) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        Row(verticalAlignment = Alignment.CenterVertically) { /*...*/ }
        Spacer(Modifier.size(padding))
        Card(
            elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
        ) { /*...*/ }
    }
}

一个更复杂的布局,使用修饰符来更改图形的排列方式,以及哪些区域响应用户输入

请注意,在上面的代码中,结合使用了不同的修饰符函数。

  • clickable 使可组合项响应用户输入,并显示涟漪。
  • padding 在元素周围留出空间。
  • fillMaxWidth 使可组合项填充其父项为它提供的最大宽度。
  • size() 指定元素的首选宽度和高度。

可滚动布局

如需详细了解可滚动布局,请参阅 Compose 手势文档。

如需了解列表和延迟列表,请参阅 Compose 列表文档

自适应布局

在设计布局时,应考虑不同的屏幕方向和设备类型尺寸。Compose 提供了一些开箱即用的机制,可帮助您根据各种屏幕配置调整可组合项的布局。

约束条件

如需了解来自父项的约束条件并相应地设计布局,您可以使用 BoxWithConstraints。您可以在内容 lambda 的作用域内找到测量约束条件。您可以使用这些测量约束条件,为不同的屏幕配置组成不同的布局:

@Composable
fun WithConstraintsComposable() {
    BoxWithConstraints {
        Text("My minHeight is $minHeight while my maxWidth is $maxWidth")
    }
}

基于槽位的布局

Compose 提供了大量基于 Material Design 的可组合项以及 androidx.compose.material:material 依赖项(在 Android Studio 中创建 Compose 项目时提供),旨在简化界面的构建。诸如 DrawerFloatingActionButtonTopAppBar 之类的元素都有提供。

Material 组件大量使用槽位 API,这是 Compose 引入的一种模式,它在可组合项之上带来一层自定义设置。这种方法使组件变得更加灵活,因为它们接受可以自行配置的子元素,而不必公开子元素的每个配置参数。槽位会在界面中留出空白区域,让开发者按照自己的意愿来填充。例如,下面是您可以在 TopAppBar 中自定义的槽位:

显示 Material Components 应用栏中的可用槽位的图表

可组合项通常采用 content 可组合 lambda (content: @Composable () -> Unit)。槽位 API 会针对特定用途公开多个 content 参数。例如,TopAppBar 可让您为 titlenavigationIconactions 提供内容。

例如,Scaffold 可让您实现具有基本 Material Design 布局结构的界面。Scaffold 可以为最常见的顶级 Material 组件(如 TopAppBarBottomAppBarFloatingActionButtonDrawer)提供槽位。通过使用 Scaffold,可轻松确保这些组件得到适当放置且正确地协同工作。

JetNews 示例应用,该应用使用 Scaffold 确定多个元素的位置

@Composable
fun HomeScreen(/*...*/) {
    ModalNavigationDrawer(drawerContent = { /* ... */ }) {
        Scaffold(
            topBar = { /*...*/ }
        ) { contentPadding ->
            // ...
        }
    }
}