一、简介
布局系统的 Jetpack Compose 实现有两个主要目标:一是实现高性能,二是让开发者能够轻松编写自定义布局。在 Compose 中,通过避免多次测量布局子级可实现高性能。如果需要进行多次测量,Compose 具有一个特殊系统,即固有特性测量。
注意:使用 Android View 系统时,在嵌套某些 View(如 RelativeLayout
)时,可能会出现一些性能问题。由于 Compose 可以避免多次测量,因此可以根据需要进行深层次嵌套,而不会影响性能。
布局主要包括:布局基础知识、Material组件和布局、自定义布局、Compose中使用ConstraintLayout。
二、布局基础
可组合函数
可组合函数是 Compose 的基本构建块,返回值是 Unit 的函数,用于描述界面中的某一部分,该函数可接收参数。
组合函数中可包含多个界面元素。
其基本格式为:
@Composable
fun xxx():Unit{
....
}
标准布局元素
在Compose中标准的布局元素有三个:Box
、Column
、Row
。
标准布局元素特性示意图
Box
重叠布局,类似Android View系统中的FramLayout布局,元素会重叠显示,其原型定义如下:
@Composable
inline fun Box(
modifier: Modifier = Modifier, //修饰符
contentAlignment: Alignment = Alignment.TopStart,//内容的位置
propagateMinConstraints: Boolean = false, //是否应将传入的最小约束传递给内容
content: @Composable BoxScope.() -> Unit //内容,即界面元素
) {
......
}
例子
/**
* 标准布局 - Box
*/
@Composable
fun BoxExample(){
Box (
modifier = Modifier.size(width = 230.dp,height = 100.dp),
contentAlignment = Alignment.Center
){
//对应content参数 lambda表达上
Text(text = "Text 1")
Text(text = "Text 2")
}
}
预览结果
Column
垂直布局,在界面元素垂直方向依次排列。源码中定义如下:
@Composable
inline fun Column(
modifier: Modifier = Modifier, //修饰符
verticalArrangement: Arrangement.Vertical = Arrangement.Top, //内容元素垂直方向分布方式
horizontalAlignment: Alignment.Horizontal = Alignment.Start, //内容元素水平方向分布方式
content: @Composable ColumnScope.() -> Unit //内部元素
) {
......
}
实例
/**
* 标准布局 - Column
*/
@Composable
fun ColumnExample(){
Column (
modifier = Modifier.size(width = 230.dp,height = 100.dp),
verticalArrangement = Arrangement.Center,//垂直方向居中
horizontalAlignment = Alignment.End //水平方向靠右
){
//对应content参数 lambda表达上
Text(text = "Text 111")
Text(text = "Text 666666")
}
}
Column示例预览结果
Row
元素水平方向分布,源码中定义:
@Composable
inline fun Row(
modifier: Modifier = Modifier, //修饰符
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,//水平方向排列方式
verticalAlignment: Alignment.Vertical = Alignment.Top, //垂直方向元素排列方式
content: @Composable RowScope.() -> Unit //内部子元素
) {
......
}
示例
/**
* 标准布局 - Row
*/
@Composable
fun RowExample(){
Row (
modifier = Modifier.size(width = 230.dp,height = 100.dp),
horizontalArrangement = Arrangement.SpaceAround,
verticalAlignment = Alignment.Bottom
){
//对应content参数 lambda表达上
Text(text = "Text 111")
Text(text = "Text 666666")
}
}
Row示例预览
基本组件
Jetpack Compose中组件库包括:compose-ui和material。很多常用基本组件都是在material库中,Compose-ui中组件很少:Image、BasicTextField(输入框)。
示例:
//添加一个图片
Image(
//填充内容
painter = painterResource(id = message.iconId),
contentDescription = "logo",
//尺寸及形状
modifier= Modifier
.padding(top = 2.dp)
.size(40.dp) //图像尺寸
.clip(CircleShape) //形状
.border(1.5.dp, MaterialTheme.colors.secondary, CircleShape)//边框样式
)
布局模型
在对标准布局元素和组件有了一定了解后,我们来看下Compose的布局流程。
首先Compose布局是一个界面树,从树的根节点开始依次要求其子节点对自身进行测量,然后递归完成所有子节点的测量,并将约束条件沿着树向下传递给子节点,并将测量的尺寸和放置指令依次向根节点传递。
以下面可组合函数为示例:
@Composable
fun SearchResult(...) {
Row(...) {
Image(...)
Column(...) {
Text(...)
Text(..)
}
}
}
其布局过程如下图示:
image.png
修饰符
在Compose布局中修饰符至关重要,您可以使用修饰符来修饰或扩充可组合项。常用的修饰符如下:
background
可组合元素的背景色;clickable
使可组合项响应用户点击,并显示波纹效果;padding
设置元素周围留出空间;size
可组合元素的尺寸;clip
可组合元素的形状;border
可组合元素的边框及形状;fillMaxSize
可组合元素尺寸按父布局的最大尺寸显示;fillMaxWidth
可组合元素宽度按父布局的最大宽度显示;fillMaxHeight
可组合元素高度按父布局的最大高度显示;
除以上外,还有width
、height
、wrapConentHeight
等,用到的时候可以尝试看看效果。
下面展示一个圆形图像的示例
/**
* 修饰符使用示例
*/
@Composable
fun ModifierExample(){
//添加一个图片
Image(
//填充内容
painter = painterResource(id = R.mipmap.ic_girl),
contentDescription = "logo",
//尺寸及形状
modifier= Modifier
.padding(top = 2.dp)
.background(Color.Blue)//设置蓝色背景
.size(40.dp) //图像尺寸
.clip(CircleShape) //圆形形状
.border(1.5.dp, MaterialTheme.colors.secondary, CircleShape)//边框样式
)
}
效果如下:
修饰符示例
修饰符扩展
除了使用Compose提供的修饰符外,我们也可以扩展函数自定义修饰符,具体方式可以参考Jetpack Compose布局(三)一文中讲的扩展布局修饰符
。
强调:修饰符的使用是有顺序的
强调:修饰符的使用是有顺序的
强调:修饰符的使用是有顺序的
例如,将上例的background和padding顺序调换下,就会如下图效果,仔细看两图的顶部边距,会发现区别
调换background和padding顺序的效果图
槽位布局
Compose中提供了固定槽位的可组合项,以简化界面元素,这些主要在androidx.compose.material:material
库中,如:Drawer
、FloatingActionButton
、TopAppBar
等。
Material 组件大量使用槽位 API,这是 Compose 引入的一种模式,它在可组合项之上带来一层自定义设置。这种方法使组件变得更加灵活,因为它们接受可以自行配置的子元素,而不必公开子元素的每个配置参数。槽位会在界面中留出空白区域,让开发者按照自己的意愿来填充。如下图TopAppBar
的槽位。
三、Material 组件和布局
Jetpack Compose 提供了 Material Design 的实现,后者是一个用于创建数字化界面的综合设计系统。Material 组件(按钮、卡片、开关等)和布局(如 Scaffold
)可作为可组合函数提供。
Material 组件是用于创建界面的交互式构建块。Compose 提供了许多此类组件,开箱即可使用。如需了解提供了哪些组件,请参阅 Compose Material API 参考文档。
Material 组件会使用应用中 MaterialTheme
提供的值:
@Composable
fun MyApp() {
MaterialTheme {
// Material Components like Button, Card, Switch, etc.
}
}
如需详细了解主题,请参阅“Compose 中的主题”指南。
内容槽
支持内部内容(文本标签、图标等)的 Material 组件往往会提供“槽”(即接受可组合内容的通用 lambda),而且还会提供尺寸和内边距等公共常量,从而支持设置内部内容的布局,使之符合 Material 规范。
例如 Button
:
Button(
onClick = {
/* ... */ },
// Uses ButtonDefaults.ContentPadding by default
contentPadding = PaddingValues(
start = 20.dp,
top = 12.dp,
end = 20.dp,
bottom = 12.dp
)
) {
// Inner content including an icon and a text label
Icon(
Icons.Filled.Favorite,
contentDescription = "Favorite",
modifier = Modifier.size(ButtonDefaults.IconSize)
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text("Like")
}
图 1. 使用 content
槽和默认内边距的 Button
(左),以及使用提供自定义 contentPadding
的 content
槽的 Button
(右)。
Button
有一个通用 content
尾随 lambda 槽,该槽使用 RowScope
将内容可组合项的布局设为行。此外,它还有一个 contentPadding
参数,用于将内边距应用于内部内容。您可以使用通过 ButtonDefaults
提供的常量,也可以使用自定义值。
再比如 ExtendedFloatingActionButton
:
ExtendedFloatingActionButton(
onClick = {
/* ... */ },
icon = {
Icon(
Icons.Filled.Favorite,
contentDescription = "Favorite"
)
},
text = {
Text("Like") }
)
图 2. 使用 icon
槽和 text
槽的 ExtendedFloatingActionButton
。
ExtendedFloatingActionButton
有两个槽,分别针对 icon
和 text
标签,而没有通用 content
lambda。虽然每个槽都支持通用的可组合内容,但该组件会自行判断这些内部内容的布局方式。它会在内部处理内边距、对齐方式和大小。
Scaffold
Compose 提供了便捷的布局,用于将 Material 组件组合成常见的屏幕图案。可组合项(例如 Scaffold
)提供了适用于各种组件和其他屏幕元素的槽。
屏幕内容
Scaffold
有一个通用 content
尾随 lambda 槽。lambda 会收到应该应用于内容根目录(例如,通过 Modifier.padding
)的 PaddingValues
实例,以便偏移顶部栏和底部栏(如果存在的话)。
Scaffold(/* ... */) {
contentPadding ->
// Screen content
Box(modifier = Modifier.padding(contentPadding)) {
/* ... */ }
}
应用栏
Scaffold
为顶部应用栏或底部应用栏提供了槽。系统将在内部处理可组合项的放置位置。
您可以使用 topBar
槽和 TopAppBar
:
Scaffold(
topBar = {
TopAppBar {
/* Top app bar content */ }
}
) {
// Screen content
}
您可以使用 bottomBar
槽和 BottomAppBar
:
Scaffold(
bottomBar = {
BottomAppBar {
/* Bottom app bar content */ }
}
) {
// Screen content
}
这些槽可用于 BottomNavigation
等其他 Material 组件。 此外,您还可以使用自定义可组合项 - 例如,查看 Owl 示例中的初始配置屏幕。
悬浮操作按钮
Scaffold
为悬浮操作按钮提供了槽。
您可以使用 floatingActionButton
槽和 FloatingActionButton
:
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = {
/* ... */ }) {
/* FAB content */
}
}
) {
// Screen content
}
注意:floatingActionButton
槽接受任何可组合内容。例如,您可以将展开式悬浮操作按钮与 ExtendedFloatingActionButton
结合使用。
系统将在内部处理 FAB 可组合项的底部放置位置。您可以使用 floatingActionButtonPosition
参数来调整水平位置:
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = {
/* ... */ }) {
/* FAB content */
}
},
// Defaults to FabPosition.End
floatingActionButtonPosition = FabPosition.Center
) {
// Screen content
}
如果您使用的是 Scaffold
可组合项的 bottomBar
槽,则可以使用 isFloatingActionButtonDocked
参数将悬浮操作按钮与底部应用栏重叠:
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = {
/* ... */ }) {
/* FAB content */
}
},
// Defaults to false
isFloatingActionButtonDocked = true,
bottomBar = {
BottomAppBar {
/* Bottom app bar content */ }
}
) {
// Screen content
}
图 3. 使用 floatingActionButton
槽和 bottomBar
槽的 Scaffold
。isFloatingActionButtonDocked
参数设为 false
(顶部)和 true
(底部)。
BottomAppBar
支持带有 cutoutShape
参数的 FAB 刘海屏,它接受任何 Shape
。最好提供停靠组件所使用的同一 Shape
。例如,FloatingActionButton
使用 MaterialTheme.shapes.small
,并将 50% 的边角大小作为其 shape
参数的默认值:
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = {
/* ... */ }) {
/* FAB content */
}
},
isFloatingActionButtonDocked = true,
bottomBar = {
BottomAppBar(
// Defaults to null, that is, No cutout
cutoutShape = MaterialTheme.shapes.small.copy(
CornerSize(percent = 50)
)
) {
/* Bottom app bar content */
}
}
) {
// Screen content
}
图 4. 具有 BottomAppBar
和停靠 FloatingActionButton
的 Scaffold
。BottomAppBar
的自定义 cutoutShape
与 FloatingActionButton
所使用的 Shape
一致。