修饰符可让您修饰或增强可组合项。修饰符可以帮助您完成以下这些操作:
- 更改可组合项的大小、布局、行为和外观
- 添加信息,例如无障碍标签
- 处理用户输入
- 添加高级交互,例如使元素可点击、可滚动、可拖动或可缩放
修饰符是标准的 Kotlin 对象。通过调用 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
参数,并将该修饰符传递给其第一个发出 UI 的子项。这样做可使您的代码更具可重用性,并使其行为更可预测、更直观。如需了解详情,请参阅 Compose API 准则中的“元素接受并遵循 Modifier 参数”。
修饰符的顺序很重要
修饰符函数的顺序非常重要。由于每个函数都会更改上一个函数返回的 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 提供了一系列内置修饰符,可帮助您修饰或增强可组合项。以下是一些您将用于调整布局的常见修饰符。
padding
和 size
默认情况下,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
,它将偏移作为参数;以及 offset
,它接受一个 lambda。如需详细了解何时使用这些重载以及如何优化性能,请阅读 “Compose 性能 - 尽可能推迟读取”部分。
Compose 中的范围安全性
在 Compose 中,有些修饰符只能应用于特定可组合项的子项。Compose 通过自定义范围强制执行此操作。
例如,如果您想让子项与父级 Box
一样大,而不影响 Box
的大小,请使用 matchParentSize
修饰符。matchParentSize
仅在 BoxScope
中可用。因此,它只能用于 Box
父级中的子项。
范围安全性可防止您添加在其他可组合项和范围中无法使用的修饰符,并节省了试错时间。
范围限定修饰符会向父级通知一些父级应了解的有关子项的信息。这些修饰符通常也称为父级数据修饰符。它们的内部机制与通用修饰符不同,但从使用角度来看,这些差异并不重要。
matchParentSize
在 Box
中
如上所述,如果您希望子布局与父级 Box
的大小相同,而不影响 Box
的大小,请使用 matchParentSize
修饰符。
请注意,matchParentSize
仅在 Box
范围内可用,这意味着它仅适用于 Box
可组合项的直接子项。
在以下示例中,子项 Spacer
的大小取自其父级 Box
,而父级 Box
的大小又取自最大的子项,在本例中是 ArtistCard
。
@Composable fun MatchParentSizeComposable() { Box { Spacer( Modifier .matchParentSize() .background(Color.LightGray) ) ArtistCard() } }
如果使用 fillMaxSize
而不是 matchParentSize
,则 Spacer
将占用父级允许的所有可用空间,从而导致父级扩展并填充所有可用空间。
weight
在 Row
和 Column
中
如您在关于“内边距和大小”的上一部分中所见,默认情况下,可组合项的大小由其包装的内容定义。您可以使用 weight
修饰符(仅在 RowScope
和 ColumnScope
中可用)来设置可组合项的大小以在其父级中灵活。
我们来看一个包含两个 Box
可组合项的 Row
。第一个框的 weight
是第二个框的两倍,因此它的宽度是第二个框的两倍。由于 Row
的宽度为 210.dp
,因此第一个 Box
的宽度为 140.dp
,第二个 Box
的宽度为 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 ) }
相反,您可以创建、提取和重用修饰符的相同实例,并将其传递给可组合项,如下所示:
// 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 ) }
提取和重用非范围修饰符
修饰符可以是无范围的,也可以是范围限定到特定可组合项的。对于无范围修饰符,您可以轻松地将它们提取到任何可组合项之外作为简单变量
val reusableModifier = Modifier .fillMaxWidth() .background(Color.Red) .padding(12.dp) @Composable fun AuthorField() { HeaderText( // ... modifier = reusableModifier ) SubtitleText( // ... modifier = reusableModifier ) }
这与 Lazy 布局结合使用时尤其有利。在大多数情况下,您会希望所有(可能数量巨大)项目都具有完全相同的修饰符
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 Text1( modifier = reusableItemModifier // ... ) Box { Text2( // 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 代码库。
如需了解有关自定义修饰符以及如何创建它们的更多信息,请查阅“自定义布局 - 使用 layout 修饰符”文档。
为您推荐
- 注意:在 JavaScript 关闭时显示链接文本
- Compose 布局基础知识
- 编辑器操作 {:#editor-actions}
- 自定义布局 {:#custom-layouts }