布局可以描述为容纳View我们在应用程序 UI 上看到的对象(例如按钮、文本字段、图像等)的容器。它们定义视图在应用程序 UI 上的排列和显示方式。
Jetpack Compose 是 Android 的现代 UI 工具包,提供了一些常见的布局类型供开发人员使用。但是,您也可以使用 Jetpack Compose 根据您的需要创建自定义布局。
让我们详细了解 Android 应用程序的自定义布局以及如何使用 Jsatisetpack Compose 创建它们。在本文中:
为什么你应该知道如何创建自定义布局
Jetpack Compose 中的布局概述
在 Jetpack Compose 中构建自定义布局的步骤
Jetpack Compose 布局背后的理论
使用Layout可组合
测量自定义布局中的所有视图
向自定义布局添加大小约束
在布局中放置视图
最终的 Jetpack Compose 项目代码
测试我们的自定义 Android 应用布局
Jetpack Compose 提供了许多开发人员工具来构建更快的 Android 应用程序,包括各种布局选项。有时,您可以使用Jetpack Compose 中的这些现有布局来实现应用程序 UI 的设计要求。
然而,这些现有布局并不总能满足项目设计要求。在这种情况下,您应该知道如何创建自定义布局来满足您项目的确切要求。
Jetpack Compose 中的一些常见布局是:
Box: 将其视图置于另一个之上的布局
Column: 将视图按垂直顺序放置的布局
Row: 将视图按水平顺序放置的布局
ConstraintLayout: 将其视图相对于其他视图放置的布局
最近,LazyVerticalGrid正在LazyHorizontalGrid测试中的网格布局和 已完全发布。
与此更新一起出现[了一个令人兴奋的新布局,称为](https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/layout/package-summary?hl=en#LazyLayout(androidx.compose.foundation.lazy.layout.LazyLayoutItemsProvider,androidx.compose.ui.Modifier,androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchState,kotlin.Function2)LazyLayout. 这是一种仅组合和布置当前需要的项目的布局 - 换句话说,可以在某个时间点在设备屏幕上可见的项目。
您可以使用惰性布局来构建高效的可滚动布局。LazyLayout变化包括:
LazyList
,它在以下位置显示可滚动列表:
垂直序列 ( LazyColumn),或
水平序列 ( LazyRow)
LazyGrid
,它在以下位置显示可滚动的网格:
网格行 ( LazyVerticalGrid),或
网格的列 ( LazyHorizontalGrid)
我知道你经常看到“懒惰”这个词,不,这并不意味着这些布局不愿意执行它们的功能(就像我们中的一些人一样)。相反,它只是意味着惰性布局仅在必要时才会执行其功能。换句话说,它真的很有效。
这种效率就是为什么惰性布局用于打算显示大量视图的布局,允许它们以列表和网格的形式轻松组织和滚动。
为了让您有效地了解构建自己的布局的过程,我将使用一个简单的示例。我们将构建一个我喜欢称之为ReverseFlowRow.
这种布局只是将其视图彼此相邻放置,当当前行填满时移动到下一行。然而,它开始从屏幕的结束位置到开始位置排列它的视图——换句话说,从右到左:
这样的布局是我觉得应该用于Jetpack Compose 的AlertDialog按钮以满足 Material Design 准则。
目前,正在使用类似的布局,但它从屏幕的开始位置到结束位置,不符合这些准则。您可以使用 IssueTracker 找到我提交的问题。
为了在屏幕上显示视图,Jetpack Compose 组合了节点的 UI 树(代表视图),在 UI 树中布置每个视图,并将它们中的每一个绘制到屏幕上。
就本文而言,我们只对视图的布局感兴趣,因为我们可以在此过程中处理创建自定义布局。在布局中布置视图的过程分为三个步骤:
测量布局中的所有视图(即子视图)
确定布局的大小
将孩子放置在布局的边界内
在 Jetpack Compose 中,可以使用可组合实现布局视图Layout,其定义为:
@Composable inline fun Layout( content: @Composable @UiComposable () -> Unit, modifier: Modifier = Modifier, measurePolicy: MeasurePolicy )
该参数指定您希望在此布局中content的一个或多个视图(称为s)。Composable该modifier参数用于定义对布局的一些修改,可以从父视图传递或可组合。
上面代码中最重要的部分是MeasurePolicy,它定义了子视图的度量、布局的大小以及子视图在布局中的位置。
所以我们的ReverseFlowRow开始是这样的:
@Composable fun ReverseFlowRow( content: @Composable () -> Unit ) = Layout(content) { measurables, constraints -> // measuring children, layout sizing, and placing children takes place here. }
您可能会注意到我们表示MeasurePolicy为 lambda。这是可能的,因为MeasurePolicy是一个功能接口。
同样在上面的代码中,measurables是需要测量的子项列表,constraints而是父项的布局边界。
超过 20 万开发人员使用 LogRocket 来创造更好的数字体验了解更多 →
我们通过调用每个孩子来衡量每个孩子的约束measure(constraints)。这将返回 a Placeable,它对应于可以由其父布局定位的子布局。
val placeables = measurables.map { measurable -> // Measure each child. measurable.measure(constraints) }
请注意,我们在测量每个孩子时使用了父母的约束。如果可能,这允许每个孩子都能够访问父母中的整个空间。
接下来,我们通过调用该layout()方法并至少指定其宽度和高度来定义布局的大小。
layout(constraints.maxWidth, constraints.maxHeight) { // Placement of children occurs here. }
这里我们使用了父约束的最大宽度和高度。因此,根据父约束,此布局可能会也可能不会占用整个屏幕。
最后,我们Placeable通过调用方法将测量的子元素(也称为元素)放置在布局中placeRelative()。
如果您想在设备的布局方向更改时自动镜像您的布局,则应使用此方法 - 换句话说,从左到右到从右到左,反之亦然。
请注意,您可以获得接收器LayoutDirection内的电流layout()。如果您不想在布局方向更改时自动镜像您的布局,而是决定如何在每个布局方向上放置视图,这将很有用。
不要错过来自 LogRocket 的精选时事通讯The Replay
了解LogRocket 的 Galileo 如何消除噪音以主动解决应用程序中的问题
使用 React 的 useEffect优化应用程序的性能
在多个 Node 版本之间切换
了解如何使用 AnimXYZ 为您的 React 应用程序制作动画
探索 Tauri,一个用于构建二进制文件的新框架
比较NestJS 与 Express.js
如果您不希望您的布局根据布局方向自动镜像,请改用该place()方法。
// Track the x and y co-ordinates we have placed children up to. var yPosition = 0 var xPosition = constraints.maxWidth // Place children in the parent layout. placeables.forEach { placeable -> if (placeable.width < xPosition) { // There is still enough space in the current row to add the next child. xPosition -= placeable.width } else { // Space left in the current row is not enough for the child. // Move to the next row. yPosition += placeable.height xPosition = constraints.maxWidth - placeable.width } // Position child on the screen. placeable.placeRelative(xPosition, yPosition) }
如您所见,我们需要跟踪用于指示每个孩子的展示位置应该从哪里开始的坐标x。y这将使我们能够将一个孩子放在另一个孩子旁边,并知道何时移动到下一行或下一行。
我们的完整布局将如下所示:
@Composable fun ReverseFlowRow( mainAxisSpacing: Dp, crossAxisSpacing: Dp, content: @Composable () -> Unit ) = Layout(content) { measurables, constraints -> // 1. The measuring phase. val placeables = measurables.map { measurable -> measurable.measure(constraints) } // 2. The sizing phase. layout(constraints.maxWidth, constraints.maxHeight) { // 3. The placement phase. var yPosition = 0 var xPosition = constraints.maxWidth placeables.forEach { placeable -> if (placeable.width < (xPosition + mainAxisSpacing.roundToPx())) { xPosition -= (placeable.width + mainAxisSpacing.roundToPx()) } else { yPosition += (placeable.height + crossAxisSpacing.roundToPx()) xPosition = constraints.maxWidth - placeable.width - mainAxisSpacing.roundToPx() } placeable.placeRelative(xPosition, yPosition) } } }
您是否注意到我添加了两个新属性:mainAxisSpacing和crossAxisSpacing?这些用于分别在水平和垂直方向上添加布局中每个子项之间的间距。
为了预览我们的布局,我们可以将它包装在一个用 . 注释的可组合函数中@Preview。这使我们能够运行布局示例,而无需 Android 应用程序所需的额外配置。星火TV海外版App,包含5000多个TV频道,清晰度非常高可看全球!另外,让我们在布局中添加一些文本视图/可组合项,看看它是如何显示它们的:
@Preview @Composable fun ReverseFlowRowPreview() { ReverseFlowRow(mainAxisSpacing = 16.dp, crossAxisSpacing = 16.dp) { Text("First", fontSize = 20.sp, style = TextStyle(background = Color.Red)) Text("Second", fontSize = 20.sp, style = TextStyle(background = Color.LightGray)) Text("Third", fontSize = 20.sp, style = TextStyle(background = Color.Blue)) Text("Fourth", fontSize = 20.sp, style = TextStyle(background = Color.Green)) Text("Fifth", fontSize = 20.sp, style = TextStyle(background = Color.Gray)) Text("Sixth", fontSize = 20.sp, style = TextStyle(background = Color.Yellow)) Text("Seventh", fontSize = 20.sp, style = TextStyle(background = Color.Cyan)) Text("Eight", fontSize = 20.sp, style = TextStyle(background = Color.Magenta)) Text("Ninth", fontSize = 20.sp, style = TextStyle(background = Color.DarkGray)) } }
运行预览会产生以下结果:
尽管我们在此示例中使用了文本视图/可组合项,但此自定义 Jetpack Compose 布局也可以与其他 Jetpack Compose 组合项一起使用。您还可以使用这些概念并按照我们在本教程中介绍的步骤创建您自己的自定义布局。
这几乎就是创建自己的布局所需要的。此示例考虑了常规案例场景,例如 按钮所需的布局AlertDialog,但可以对其进行改进以适应更多案例场景,例如儿童具有不同大小的情况。
如果您将ReverseFlowRow我们创建的布局原样使用不同大小的子级,它们之间会有一些重叠。需要一些额外的代码来适应这种情况。如果您想解决这个小挑战,请在下面的评论中发布更新的代码!