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 ->
            // ...
        }
    }
}