借助修饰符,您可以修饰或扩充可组合项。您可以使用修饰符来执行以下操作:
- 更改可组合项的大小、布局、行为和外观
- 添加信息,如无障碍标签
- 处理用户输入
- 添加高级互动,如使元素可点击、可滚动、可拖动或可缩放
修饰符是标准的 Kotlin 对象。您可以通过调用某个 Modifier
类函数来创建修饰符:
import androidx.compose.ui.Modifier
@Composable
private fun Greeting(name: String) {
Column(modifier = Modifier.padding(24.dp)) {
Text(text = "Hello,")
Text(text = name)
}
}
您可以将以下函数连在一起以将其组合起来:
@Composable
private fun Greeting(name: String) {
Column(modifier = Modifier
.padding(24.dp)
.fillMaxWidth()
) {
Text(text = "Hello,")
Text(text = name)
}
}
请注意,在上面的代码中,结合使用了不同的修饰符函数。
padding
在元素周围留出空间。fillMaxWidth
使可组合项填充其父项为它提供的最大宽度。
最佳实践是让所有可组合项接受 modifier
参数,并将该修饰符传递给其发出界面元素的第一个子项。这样做可以提高代码的可重用性,使其行为更可预测且更直观。如需了解详情,请参阅 Compose API 指南:接受元素和遵循修饰符参数。
修饰符顺序很重要
修饰符函数的顺序非常重要。由于每个函数都会对上一个函数返回的 Modifier
进行更改,因此顺序会影响最终结果。让我们来看看这方面的一个示例:
@Composable
fun ArtistCard(/*...*/) {
val padding = 16.dp
Column(
Modifier
.clickable(onClick = onClick)
.padding(padding)
.fillMaxWidth()
) {
// rest of the implementation
}
}
在上面的代码中,整个区域(包括周围的内边距)都是可点击的,因为 padding
修饰符应用在 clickable
修饰符后面。如果修饰符顺序相反,由 padding
添加的空间就不会响应用户输入:
@Composable
fun ArtistCard(/*...*/) {
val padding = 16.dp
Column(
Modifier
.padding(padding)
.clickable(onClick = onClick)
.fillMaxWidth()
) {
// rest of the implementation
}
}
内置修饰符
Jetpack Compose 提供了一个内置修饰符列表,可帮助您修饰或扩充可组合项。以下是一些用于调整布局的常见修饰符。
内边距和尺寸
默认情况下,Compose 中提供的布局会封装其子项。但是,您可以使用 size
修饰符设置尺寸:
@Composable
fun ArtistCard(/*...*/) {
Row(
modifier = Modifier.size(width = 400.dp, height = 100.dp)
) {
Image(/*...*/)
Column { /*...*/ }
}
}
请注意,如果指定的尺寸不符合来自布局父项的约束条件,则可能不会采用该尺寸。如果您希望可组合项的尺寸固定不变,而不考虑传入的约束条件,请使用 requiredSize
修饰符:
@Composable
fun ArtistCard(/*...*/) {
Row(
modifier = Modifier.size(width = 400.dp, height = 100.dp)
) {
Image(
/*...*/
modifier = Modifier.requiredSize(150.dp)
)
Column { /*...*/ }
}
}
在此示例中,即使父项的 height
设置为 100.dp
,Image
的高度还是 150.dp
,因为 requiredSize
修饰符优先级较高。
如果您希望子布局填充父项允许的所有可用高度,请添加 fillMaxHeight
修饰符(Compose 还提供了 fillMaxSize
和 fillMaxWidth
):
@Composable
fun ArtistCard(/*...*/) {
Row(
modifier = Modifier.size(width = 400.dp, height = 100.dp)
) {
Image(
/*...*/
modifier = Modifier.fillMaxHeight()
)
Column { /*...*/ }
}
}
如需在整个元素周围全添加内边距,请设置 padding
修饰符。
如需在文本基线上方添加内边距,以实现从布局顶部到基线保持特定距离,请使用 paddingFromBaseline
修饰符:
@Composable
fun ArtistCard(artist: Artist) {
Row(/*...*/) {
Column {
Text(
text = artist.name,
modifier = Modifier.paddingFromBaseline(top = 50.dp)
)
Text(artist.lastSeenOnline)
}
}
}
偏移量
要相对于原始位置放置布局,请添加 offset
修饰符,并在 x 轴和 y 轴中设置偏移量。偏移量可以是正数,也可以是非正数。padding
和 offset
之间的区别在于,向可组合项添加 offset
不会改变其测量结果:
@Composable
fun ArtistCard(artist: Artist) {
Row(/*...*/) {
Column {
Text(artist.name)
Text(
text = artist.lastSeenOnline,
modifier = Modifier.offset(x = 4.dp)
)
}
}
}
offset
修饰符根据布局方向水平应用。在从左到右的上下文中,正 offset
会将元素向右移,而在从右到左的上下文中,它会将元素向左移。如果您需要设置偏移量,而不考虑布局方向,请参阅 absoluteOffset
修饰符,该修饰符中的正偏移值一律会将元素向右移。
offset
修饰符提供了两种重载 - 将偏移量作为参数的 offset
以及采用 lambda 的 offset
。如需深入了解何时使用这些修饰符以及如何针对性能进行优化,请仔细阅读 Compose 性能 - 尽可能延迟读取部分。
Compose 中的作用域安全
在 Compose 中,有一些修饰符只能应用于某些可组合项的子项。Compose 通过自定义作用域强制实施此安全机制。
例如,如果您希望使某个子项与父项 Box
同样大,而不影响 Box
尺寸,请使用 matchParentSize
修饰符。matchParentSize
仅适用于 BoxScope
,因此只能对 Box
父级中的子级使用。
作用域安全机制可防止您添加在其他可组合项和作用域中不起作用的修饰符,还可节省试错的时间。
限定作用域的修饰符会将父项应知晓的关于子项的一些信息告知父项。这些修饰符通常称为“父项数据修饰符”。它们的内部构件与通用修饰符不同,但从使用角度来看,这些差异并不重要。
Box 中的 matchParentSize
如上所述,如果您希望子布局与父项 Box
尺寸相同而不影响 Box
的尺寸,请使用 matchParentSize
修饰符。
请注意,matchParentSize
仅在 Box
作用域内可用,这意味着它仅适用于 Box
可组合项的直接子项。
在以下示例中,子项 Spacer
从其父项 Box
获取自己的尺寸,在这种情况下,后者又会从其最大的子项 ArtistCard
中获取自己的尺寸。
@Composable
fun MatchParentSizeComposable() {
Box {
Spacer(Modifier.matchParentSize().background(Color.LightGray))
ArtistCard()
}
}
如果使用 fillMaxSize
代替 matchParentSize
,Spacer
将占用父项允许的所有可用空间,反过来使父项展开并填满所有可用空间。
Row 和 Column 中的 weight
如前文的内边距和尺寸部分所述,默认情况下,可组合项的尺寸由其封装的内容定义。您可以使用仅可在 RowScope
和 ColumnScope
中使用的 weight
修饰符,将可组合项的尺寸设置为可在其父项内灵活调整。
让我们以包含两个 Box
可组合项的 Row
为例。第一个框的 weight
是第二个框的两倍,因此其宽度也相差两倍。由于 Row
的宽度为 210.dp
,因此第一个 Box
的宽度为 140.dp
,第二个的宽度为 70.dp
:
@Composable
fun ArtistCard(/*...*/) {
Row(
modifier = Modifier.fillMaxWidth()
) {
Image(
/*...*/
modifier = Modifier.weight(2f)
)
Column(
modifier = Modifier.weight(1f)
) {
/*...*/
}
}
}
提取和重复使用修饰符
您可以将多个修饰符链接起来,以修饰或扩充可组合项。此链是通过 Modifier
接口创建的,代表单个 Modifier.Elements
的不可变有序列表。
每个 Modifier.Element
代表一个单独的行为(例如布局、绘图和图形行为,所有与手势相关的焦点和语义行为,以及设备输入事件)。该顺序很重要:系统会首先应用最先添加的修饰符元素。
有时,将修饰符链提取到变量中并将其提升到更高的作用域有助于在多个可组合项中重复使用相同的修饰符链实例:这样做有助于提高代码可读性,还有助于提升应用性能,原因如下:
- 对使用修饰符的可组合项进行重组时,不会重新分配修饰符
- 修饰符链可能很长并且非常复杂,因此重复使用相同的链实例可以减轻 Compose 运行时在对比时所需的工作量
- 这种提取方式可提高整个代码库中的代码简洁性、一致性和可维护性
重复使用修饰符的最佳实践
您可以创建自己的 Modifier
链并将其提取出来,以便在多个可组合组件中重复使用。修饰符是一种类似数据的对象,因此完全可以直接进行保存:
val reusableModifier = Modifier
.fillMaxWidth()
.background(Color.Red)
.padding(12.dp)
在观察频繁变化的状态时提取和重复使用修饰符
在可组合项中观察频繁变化的状态(如动画状态或 scrollState
)时,可能会发生大量重组。在这种情况下,修饰符会在每次重组时获得分配,并可能分配给每一帧。
@Composable
fun LoadingWheelAnimation() {
val animatedState = animateFloatAsState(...)
LoadingWheel(
// Creation and allocation of this modifier will happen on every frame of the animation!
modifier = Modifier
.padding(12.dp)
.background(Color.Gray),
animatedState = animatedState.value
)
}
不过,您可以创建、提取和重复使用同一修饰符实例,并将其传递给可组合项,如下所示:
// Now, the allocation of the modifier happens here:
val reusableModifier = Modifier
.padding(12.dp)
.background(Color.Gray)
@Composable
fun LoadingWheelAnimation() {
val animatedState = animateFloatAsState(...)
LoadingWheel(
// No allocation, as we're just reusing the same instance
modifier = reusableModifier,
animatedState = animatedState.value
)
}
提取和重复使用未限定作用域的修饰符
修饰符可以不限定作用域,也可以将作用域限定为特定可组合项。对于未限定作用域的修饰符,您可以轻松地从任何可组合项之外提取它们作为简单变量:
val reusableModifier = Modifier
.fillMaxWidth()
.background(Color.Red)
.padding(12.dp)
@Composable
fun AuthorField() {
HeaderText(
// ...
modifier = reusableModifier
)
SubtitleText(
// ...
modifier = reusableModifier
)
}
与延迟布局结合使用时,这尤为有用。在大多数情况下,建议对所有潜在的重要项目使用完全相同的修饰符:
val reusableItemModifier = Modifier
.padding(bottom = 12.dp)
.size(216.dp)
.clip(CircleShape)
@Composable
private fun AuthorList(authors: List<Author>) {
LazyColumn {
items(authors) {
AsyncImage(
// ...
modifier = reusableItemModifier,
)
}
}
}
提取和重复使用限定作用域的修饰符
在处理作用域限定为特定可组合项的修饰符时,您可以将其提取到尽可能高的级别,并在适当的情况下重复使用:
Column(...) {
val reusableItemModifier = Modifier
.padding(bottom = 12.dp)
// Align Modifier.Element requires a ColumnScope
.align(Alignment.CenterHorizontally)
.weight(1f)
Text1(
modifier = reusableItemModifier,
// ...
)
Text2(
modifier = reusableItemModifier
// ...
)
// ...
}
您只能将提取的限定作用域的修饰符传递给限定相同作用域的直接子项。请参阅 Compose 中的作用域安全部分,详细了解其重要性:
Column(modifier = Modifier.fillMaxWidth()) {
// Weight modifier is scoped to the Column composable
val reusableItemModifier = Modifier.weight(1f)
// Weight will be properly assigned here since this Text is a direct child of Column
Text(
modifier = reusableItemModifier
// ...
)
Box {
Text(
// Weight won't do anything here since the Text composable is not a direct child of Column
modifier = reusableItemModifier
// ...
)
}
}
进一步链接提取的修饰符
您可以通过调用 .then()
函数进一步链接或附加提取的修饰符链:
val reusableModifier = Modifier
.fillMaxWidth()
.background(Color.Red)
.padding(12.dp)
// Append to your reusableModifier
reusableModifier.clickable { … }
// Append your reusableModifier
otherModifier.then(reusableModifier)
请注意,修饰符的顺序很重要!
了解详情
我们提供了修饰符的完整列表及其参数和范围。
如需有关修饰符使用方法的更多实践,您还可以浏览“Compose 中的基本布局”Codelab,或参阅 Now in Android 代码库。
如需详细了解自定义修饰符以及如何创建这类修饰符,请参阅介绍自定义布局 - 使用布局修饰符的文档。