创建自定义布局

在 Compose 中,界面元素由可组合函数表示,此类函数在被调用后会发出一部分界面,这部分界面随后会被添加到呈现在屏幕上的界面树中。每个界面元素都有一个父元素,还可能有多个子元素。此外,每个元素在其父元素中都有一个位置,指定为 (x, y) 位置;也都有一个尺寸,指定为 width 和 height。

父元素定义其子元素的约束条件。元素需要在这些约束条件内定义尺寸。约束条件可限制元素的最小和最大 width 和 height。如果某个元素有子元素,它可能会测量每个子元素,以帮助确定其尺寸。一旦某个元素确定并报告了它自己的尺寸,就有机会定义如何相对于自身放置它的子元素,如创建自定义布局中所详述。

在界面树中布置每个节点的过程分为三个步骤。每个节点必须:

测量所有子项

确定自己的尺寸

放置其子项

图 1. 节点布局的三个步骤是:测量子项、确定尺寸和放置子项。

注意: Compose 界面不允许多遍测量。这意味着,布局元素不能为了尝试不同的测量配置而多次测量任何子元素。

作用域的使用决定了您可以衡量和放置子项的时机。只能在测量和布局传递期间测量布局,并且只能在布局传递期间(且仅在已进行测量之后)才能放置子项。由于 Compose 作用域(如 MeasureScope 和 PlacementScope),此操作在编译时强制执行。

使用布局修饰符

您可以使用 layout 修饰符来修改元素的测量和布局方式。Layout 是一个 lambda;它的参数包括您可以测量的元素(以 measurable 的形式传递)以及该可组合项的传入约束条件(以 constraints 的形式传递)。自定义布局修饰符可能如下所示:

fun Modifier.customLayoutModifier() =

layout { measurable, constraints ->

// ...

}CustomLayoutSnippets.kt

在界面上显示 Text,并控制从顶部到第一行文本基线的距离。这正是 paddingFromBaseline 修饰符的作用,您在这里将其作为一个示例来实现。为此,请使用 layout 修饰符将可组合项手动放置在屏幕上。Text 上内边距设为 24.dp 时的结果行为如下:

图 2. 应用了 paddingFromBaseline 的文本。

生成该间距的代码如下:

fun Modifier.firstBaselineToTop(

firstBaselineToTop: Dp

) = layout { measurable, constraints ->

// Measure the composable

val placeable = measurable.measure(constraints)

// Check the composable has a first baseline

check(placeable[FirstBaseline] != AlignmentLine.Unspecified)

val firstBaseline = placeable[FirstBaseline]

// Height of the composable with padding - first baseline

val placeableY = firstBaselineToTop.roundToPx() - firstBaseline

val height = placeable.height + placeableY

layout(placeable.width, height) {

// Where the composable gets placed

placeable.placeRelative(0, placeableY)

}

}CustomLayoutSnippets.kt

下面对该代码中发生的情况进行了说明:

在 measurable lambda 参数中,您需要通过调用 measurable.measure(constraints) 来测量以可测量参数表示的 Text。

您需要通过调用 layout(width, height) 方法指定可组合项的尺寸,该方法还会提供一个用于放置被封装元素的 lambda。在本例中,它是最后一条基线和增加的上内边距之间的高度。

您通过调用 placeable.place(x, y) 将被封装的元素放到屏幕上。如果未放置被封装的元素,它们将不可见。y 位置对应于上内边距,即文本的第一条基线的位置。

如需验证这段代码是否可以发挥预期的作用,请在 Text 上使用以下修饰符:

@Preview

@Composable

fun TextWithPaddingToBaselinePreview() {

MyApplicationTheme {

Text("Hi there!", Modifier.firstBaselineToTop(32.dp))

}

}

@Preview

@Composable

fun TextWithNormalPaddingPreview() {

MyApplicationTheme {

Text("Hi there!", Modifier.padding(top = 32.dp))

}

}CustomLayoutSnippets.kt

图 3. 应用于 Text 可组合项并已预览的修饰符。

创建自定义布局

layout 修饰符仅更改调用可组合项。如需测量和布置多个可组合项,请改用 Layout 可组合项。此可组合项可让您手动测量和布置子项。Column 和 Row 等所有较高级别的布局都使用 Layout 可组合项构建而成。

注意:在 View 系统中,创建自定义布局必须扩展 ViewGroup 并实现测量和布局函数。在 Compose 中,您只需使用 Layout 可组合项编写一个函数即可。

此示例构建了一个非常基本的 Column。大多数自定义布局都遵循以下模式:

@Composable

fun MyBasicColumn(

modifier: Modifier = Modifier,

content: @Composable () -> Unit

) {

Layout(

modifier = modifier,

content = content

) { measurables, constraints ->

// measure and position children given constraints logic here

// ...

}

}CustomLayoutSnippets.kt

measurables 与 layout 修饰符类似,是需要测量的子项的列表,而 constraints 是来自父项的约束条件。按照与前面相同的逻辑,可按如下方式实现 MyBasicColumn:

@Composable

fun MyBasicColumn(

modifier: Modifier = Modifier,

content: @Composable () -> Unit

) {

Layout(

modifier = modifier,

content = content

) { measurables, constraints ->

// Don't constrain child views further, measure them with given constraints

// List of measured children

val placeables = measurables.map { measurable ->

// Measure each children

measurable.measure(constraints)

}

// Set the size of the layout as big as it can

layout(constraints.maxWidth, constraints.maxHeight) {

// Track the y co-ord we have placed children up to

var yPosition = 0

// Place children in the parent layout

placeables.forEach { placeable ->

// Position item on the screen

placeable.placeRelative(x = 0, y = yPosition)

// Record the y co-ord placed up to

yPosition += placeable.height

}

}

}

}CustomLayoutSnippets.kt

可组合子项受 Layout 约束条件(没有 minHeight 约束条件)的约束,它们的放置基于前一个可组合项的 yPosition。

该自定义可组合项的使用方式如下:

@Composable

fun CallingComposable(modifier: Modifier = Modifier) {

MyBasicColumn(modifier.padding(8.dp)) {

Text("MyBasicColumn")

Text("places items")

Text("vertically.")

Text("We've done it by hand!")

}

}CustomLayoutSnippets.kt

图 4. 自定义 Column 实现。

布局方向

您可以通过更改 LocalLayoutDirection CompositionLocal 来更改可组合项的布局方向。

如果您要将可组合项手动放置在屏幕上,则 LayoutDirection 是 layout 修饰符或 Layout 可组合项的 LayoutScope 的一部分。

使用 layoutDirection 时,应使用 place 放置可组合项。与 placeRelative 方法不同,place 不会根据布局方向(从左到右与从右到左)发生变化。

自定义布局的实际运用

如需详细了解布局和修饰符,请参阅 Compose 中的基本布局;如需查看自定义布局的实际运用,请参阅创建自定义布局的 Compose 示例。

了解详情

如需详细了解 Compose 中的自定义布局,请参阅下面列出的其他资源。

视频

深入了解 Jetpack Compose 布局

为您推荐

注意:当 JavaScript 处于关闭状态时,系统会显示链接文字

Compose 布局中的固有特性测量

Compose 中的图形

Compose 修饰符