Compose 布局基础

Jetpack Compose 使设计和构建应用的 UI 变得更加容易。Compose 通过以下方式将状态转换为 UI 元素:

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

Compose transforming state to UI via composition, layout, drawing

本文档重点介绍元素的布局,解释 Compose 提供的一些构建块,以帮助您布局 UI 元素。

Compose 中布局的目标

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

可组合函数的基础知识

可组合函数是 Compose 的基本构建块。可组合函数是一个发出Unit的函数,它描述了 UI 的一部分。该函数接收一些输入并生成屏幕上显示的内容。有关可组合项的更多信息,请查看Compose 心智模型文档。

可组合函数可能会发出多个 UI 元素。但是,如果您没有提供关于如何排列它们的指导,Compose 可能会以您不希望的方式排列这些元素。例如,此代码生成两个文本元素

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

在没有关于您希望如何排列它们的指导的情况下,Compose 将文本元素堆叠在一起,使它们无法阅读

Two text elements drawn on top of each other, making the text unreadable

Compose 提供了一组现成的布局,以帮助您排列 UI 元素,并使定义您自己的更专业的布局变得容易。

标准布局组件

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

使用Column在屏幕上垂直放置项目。

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

Two text elements arranged in a column layout, so the text is readable

类似地,使用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)
        }
    }
}

Shows a more complex layout, with a small graphic next to a column of text elements

使用Box将元素放在另一个元素的顶部。Box还支持配置其包含元素的特定对齐方式。

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

Shows two elements stacked on one another

通常,这些构建块就是您所需要的。您可以编写自己的可组合函数,将这些布局组合成更复杂的布局以适合您的应用。

Compares three simple layout composables: column, row, and 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 { /*...*/ }
    }
}

Items are aligned to the right

布局模型

在布局模型中,UI 树在一遍扫描中布局。首先要求每个节点测量自身,然后递归地测量任何子节点,将大小约束向下传递到子节点的树中。然后,对叶子节点进行大小调整和放置,并将已解析的大小和放置指令传递回树的顶部。

简而言之,父节点在子节点之前进行测量,但在子节点之后进行大小调整和放置。

考虑以下SearchResult函数。

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

此函数产生以下 UI 树。

SearchResult
  Row
    Image
    Column
      Text
      Text

SearchResult示例中,UI 树布局遵循以下顺序

  1. 要求根节点Row进行测量。
  2. 根节点Row要求其第一个子节点Image进行测量。
  3. Image是叶子节点(即它没有子节点),因此它报告大小并返回放置指令。
  4. 根节点Row要求其第二个子节点Column进行测量。
  5. Column节点要求其第一个Text子节点进行测量。
  6. 第一个Text节点是叶子节点,因此它报告大小并返回放置指令。
  7. Column节点要求其第二个Text子节点进行测量。
  8. 第二个Text节点是叶子节点,因此它报告大小并返回放置指令。
  9. 现在Column节点已测量、调整大小并放置了其子节点,它可以确定自身的大小和放置位置。
  10. 现在根节点Row已测量、调整大小并放置了其子节点,它可以确定自身的大小和放置位置。

Ordering of measuring, sizing, and placement in Search Result UI tree

性能

Compose 通过仅测量一次子节点来实现高性能。单遍测量有利于性能,使 Compose 能够高效地处理深层 UI 树。如果一个元素对其子元素测量了两次,并且该子元素对其每个子元素测量了两次,依此类推,则尝试布局整个 UI 必须执行大量工作,这使得难以保持应用的性能。

如果您的布局由于某种原因需要多次测量,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),
        ) { /*...*/ }
    }
}

A still more complex layout, using modifiers to change how the graphics are arranged and which areas respond to user input

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

  • 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 项目时包含),以简化 UI 构建。提供了DrawerFloatingActionButtonTopAppBar等元素。

Material 组件大量使用槽 API,这是 Compose 引入的一种模式,用于在可组合项之上引入一层自定义。这种方法使组件更加灵活,因为它们接受一个可以配置自身的子元素,而不是必须公开子元素的每个配置参数。槽在 UI 中留出一个空位,供开发人员根据需要填充。例如,这些是可以自定义TopAppBar中的槽

A diagram showing the available slots in a Material Components app bar

可组合项通常采用content可组合 lambda(content: @Composable () -> Unit)。槽 API 公开了多个content参数以供特定用途使用。例如,TopAppBar允许您提供titlenavigationIconactions的内容。

例如,Scaffold允许您使用基本的 Material Design 布局结构实现 UI。Scaffold为最常见的顶级 Material 组件提供槽,例如TopAppBarBottomAppBarFloatingActionButtonDrawer。通过使用Scaffold,可以轻松确保这些组件正确放置并协同工作。

The JetNews sample app, which uses Scaffold to position multiple elements

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