修饰符允许您装饰或增强可组合项。修饰符允许您执行以下操作
- 更改可组合项的大小、布局、行为和外观
- 添加信息,例如辅助功能标签
- 处理用户输入
- 添加高级交互,例如使元素可点击、可滚动、可拖动或可缩放
修饰符是标准的 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
,第二个框的宽度为 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 代码实验室中的基本布局 或参考 Now in Android 代码库。
有关自定义修饰符以及如何创建它们的更多信息,请查看有关 自定义布局 - 使用布局修饰符 的文档。
为您推荐
- 注意:当 JavaScript 关闭时,将显示链接文本
- Compose 布局基础
- 编辑器操作 {:#editor-actions}
- 自定义布局 {:#custom-layouts }