使用 Modifier.layout() 手动控制元素的测量和布局。通常layout修饰符的使用方法像下面这样。

fun Modifier.customLayoutModifier(…) = Modifier.layout { measurable, constraints ->
…
})

当使用layout修饰符时,你传入的回调lambda需要包含两个参数:measurable、constraints

measurable:子元素的测量句柄,通过提供的api完成测量与布局过程

constraints: 子元素的测量约束,包括宽度与高度的最大值与最小值。

Layout Modifier使用示例

有时你想在屏幕上展示一段文本信息,通常你会使用到Compose内置的Text组件。单单显示文本是不够的,你希望指定Text顶部到文本基线的高度,让文本看的更自然一些。使用内置的padding修饰符是无法满足你的需求的,他只能指定Text顶部到文本顶部的高度,此时你就需要使用到layout修饰符了。

image.png

我们首先创建一个 firstBaselineToTop 修饰符

fun Modifier.firstBaselineToTop(
firstBaselineToTop: Dp
) = Modifier.layout { measurable, constraints ->
…
}

正如我们在Compose布局原理中所提到的,每个子元素只允许被测量一次。

通过使用 measurable.measure(constraints) 完成子元素的测量,如果将lambda的constraints直接传入则意味着你将父元素给当前元素的限制直接提供了当前元素的子元素,自身没有增加任何额外的限制。子元素测量的结果被包装在一个 Placeable 实例中,可通过该Placeable 实例获取子元素测量结果。

在我们的示例中当前Text元素也不对子元素进行额外限制。

fun Modifier.firstBaselineToTop(
firstBaselineToTop: Dp
) = Modifier.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
…
}

现在子元素已经完成了测量流程,你需要计算当前元素的打算并通过 layout(width, height) 方法对当前元素的宽度与高度进行指定。并将子元素的布局流程写入在 layout(width, height) 的lambda参数中。

在我们的示例中当前Text元素的宽度则是文本宽度,而高度则是我们指定的Text顶部到文本基线高度与文本基线到Text底部的高度之和。

fun Modifier.firstBaselineToTop(
firstBaselineToTop: Dp
) = Modifier.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
val firstBaseline = placeable[FirstBaseline]
val placeableY = firstBaselineToTop.roundToPx() - firstBaseline
val height = placeable.height + placeableY
layout(placeable.width, height) {
…
}
}

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享

现在你可以通过使用 placeable.placeRelative(x, y) 来完成子元素的布局流程,这是必要的。placeRelative 会根据当前 layoutDirection 自动调整子元素的位置。

在我们的示例中,当前子元素的横向坐标相对当前元素为零,而纵向坐标则为Text组件顶部到文本顶部的距离。

fun Modifier.firstBaselineToTop(
firstBaselineToTop: Dp
) = Modifier.layout { measurable, constraints ->
…
val placeableY = firstBaselineToTop.roundToPx() - firstBaseline
val height = placeable.height + placeableY
layout(placeable.width, height) {
placeable.placeRelative(0, placeableY)
}
}

为预览布局结果,我们创建了两个预览视图。

@Preview
@Composable
fun TextWithPaddingToBaselinePreview() {
LayoutsCodelabTheme {
Text(“Hi there!”, Modifier.firstBaselineToTop(24.dp))
}
}

@Preview
@Composable
fun TextWithNormalPaddingPreview() {
LayoutsCodelabTheme {
Text(“Hi there!”, Modifier.padding(top = 24.dp))
}
}

预览效果

image.png

使用Layout Composable

Layout Modifier会将当前元素的所有子元素视作为整体进行统一的测量与布局,多适用于统一处理的场景。然而我们有时是需要精细化测量布局每一个子组件,这需要我们进行完全的自定义Layout。这类似于传统View系统中定制View与ViewGroup测量布局流程的区别。对于定制“ViewGroup”的场景,我们应该使用Layout Composable了。首先我们需要创建一个Layout Composable。

@Composable
fun CustomLayout(
modifier: Modifier = Modifier,
// custom layout attributes
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
// measure and position children given constraints logic here
}
}

可以看到,Layout需要填写三个参数:modifier,content,measurePolicy

modifier:由外部传入的修饰符,会决定该UI元素的constraints

content:在content中声明所有子元素信息

measurePolicy:默认场景下只实现measure即可,上面示例中最后传入的lambda就是measure的实现。当你想要为你的Layout Composable适配Intrinsics时(官方中文翻译为固有特性测量),则需要重写 minIntrinsicWidth 、minIntrinsicHeight、maxIntrinsicWidth 、maxIntrinsicHeight 方法,有关于固有特性测量的文章后续会更新,请持续关注。

Layout Composable使用示例

我们可以通过Layout Composable定制一个自己专属的Column,首先我们需要声明这个Composable。

@Composable
fun MyOwnColumn(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
// measure and position children given constraints logic here
}
}

和Layout Modifier一样,我们需要对所有子组件进行一次测量。切记,每个子元素只允许被测量一次。

与Layout Modifier不同的是,这里的measurables是一个List,而Layout Modifier则只是一个measurable,因为他将所有子元素看作了一个整体。

在我们的示例中仍然不对子元素进行额外限制,最终将每次测量的结果保存到placeables这个List中。

@Composable
fun MyOwnColumn(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
val placeables = measurables.map { measurable ->
// Measure each child
measurable.measure(constraints)
}
}
}

现在在将这些子元素布局之前,你需要计算当前定制column所应该占用的屏幕宽度与高度。这样为了出于简单考虑,选择将宽度与高度设置为其父元素所允许的最大高度与宽度。与Layout Modifier一样通过 layout(width, height) 方法对当前元素的宽度与高度进行指定。

@Composable
fun MyOwnColumn(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
度与高度设置为其父元素所允许的最大高度与宽度。与Layout Modifier一样通过 layout(width, height) 方法对当前元素的宽度与高度进行指定。

@Composable
fun MyOwnColumn(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐