修饰符允许您装饰或增强可组合项。修饰符使您能够执行以下操作
- 更改可组合项的大小、布局、行为和外观
- 添加信息,例如辅助功能标签
- 处理用户输入
- 添加高级交互,例如使元素可点击、可滚动、可拖动或可缩放
修饰符是标准的 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
获取其大小,而父级又从最大的子级(本例中为 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)
请记住,修饰符的顺序很重要!
了解更多
我们提供了 修饰符的完整列表,包括它们的 parameters 和 scope。
要更深入地了解如何使用修饰符,您还可以浏览 Compose 代码实验室中的基本布局 或参考 Now in Android 代码库。
有关自定义修饰符以及如何创建自定义修饰符的更多信息,请查看有关 自定义布局 - 使用布局修饰符 的文档。
推荐给你
- 注意:当 JavaScript 关闭时,将显示链接文本
- Compose 布局基础
- 编辑器操作 {:#editor-actions}
- 自定义布局 {:#custom-layouts }