5.1 Modifier 的本质
在 Compose 中,Modifier 是一个装饰器模式的函数式实现------它是一个包装元素,通过链式调用"包裹"组件并为其添加行为、样式或布局调整。
kotlin
// Modifier 的本质
interface Modifier {
// 组合两个 Modifier
infix fun then(other: Modifier): Modifier
}
每个 Modifier 内部实际上是一个有序链表。A.then(B).then(C) 相当于:
css
A → B → C → Element
当 UI 渲染时,从外到内依次应用:首先 A 修改元素,然后 B,然后 C,最后到达目标组件。
5.2 常见 Modifier 分类
布局类
scss
Modifier
.fillMaxWidth() // 填充父容器最大宽度
.fillMaxHeight() // 填充父容器最大高度
.fillMaxSize() // 等价于上两个一起
.wrapContentSize() // 自适应内容尺寸
.size(48.dp) // 固定 48x48
.size(width = 100.dp, height = 50.dp)
.width(100.dp) // 固定宽度
.height(50.dp) // 固定高度
.requiredWidth(100.dp) // 强制宽度(不可被父约束覆盖)
.defaultMinSize(40.dp) // 默认最小尺寸
注意 size 与 requiredSize 的区别:
scss
Box(Modifier.size(100.dp)) {
// 子组件可用最大尺寸 = 100x100
Text("Hi", Modifier.fillMaxSize()) // ✅ 100x100
}
Box(Modifier.requiredSize(100.dp)) {
// 子组件必须 100x100
Text("Hi", Modifier.size(200.dp)) // ❌ 会被裁剪为 100x100
}
外观类
scss
Modifier
.padding(16.dp) // 四边内边距
.padding(start = 8.dp, end = 8.dp) // 特定方向内边距
.background(Color.Blue) // 背景色(支持 shape)
.background(
brush = Brush.verticalGradient(listOf(Color.Blue, Color.Cyan)),
shape = RoundedCornerShape(8.dp)
)
.border(2.dp, Color.Red, CircleShape) // 边框
.alpha(0.5f) // 透明度
.clip(CircleShape) // 剪裁为特定形状
.shadow(elevation = 4.dp) // 阴影
.offset(x = 10.dp, y = 20.dp) // 偏移
事件与交互
ini
Modifier
.clickable { /* 点击回调 */ }
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = ripple(bounded = false) // 无边界波纹
) { /* 点击 */ }
.combinedClickable(
onClick = { /* 单击 */ },
onLongClick = { /* 长按 */ },
onDoubleClick = { /* 双击 */ }
)
.draggable(
orientation = Orientation.Horizontal,
state = rememberDraggableState { delta -> offset += delta }
)
.swipeable(...)
滚动与测量
scss
Modifier
.verticalScroll(rememberScrollState())
.horizontalScroll(rememberScrollState())
.scrollable(orientation = Orientation.Vertical, state = ...)
.pointerInput(Unit) {
detectTapGestures { /* 手势检测 */ }
detectDragGestures { change, dragAmount -> ... }
detectTransformGestures { _, pan, zoom, rotation -> ... }
}
生命周期
scss
Modifier
.onGloballyPositioned { coordinates ->
// 当组件在全局坐标系中的位置变化时回调
Log.d("Pos", coordinates.positionInRoot().toString())
}
.onSizeChanged { size ->
// 当组件尺寸变化时回调
}
.drawWithContent {
// 在绘制之前/之后添加自定义绘制
drawCircle(Color.Red, radius = 10f)
drawContent() // 绘制原内容
}
5.3 Modifier 顺序的深入理解
这是 Compose 新手最容易犯的错误之一。Modifier 的执行顺序从外向内:
less
// 场景 1:padding → clickable → background
Text(
"Hello",
Modifier
.padding(16.dp) // 1. 先添加内边距
.clickable { } // 2. 再设置点击区域
.background(Color.Blue) // 3. 最后设置背景
)
// 结果:可点击区域包括了 padding 区域,背景也覆盖 padding 区域
// 场景 2:background → padding → clickable
Text(
"World",
Modifier
.background(Color.Red) // 1. 先设置背景
.padding(16.dp) // 2. 再加内边距
.clickable { } // 3. 最后点按
)
// 结果:背景只在 padding 内部,padding 区域无背景色
实用记忆法则
| 需求 | 推荐的 Modifier 顺序 |
|---|---|
| 设置尺寸 + 背景 + 圆角 | .size(x).background(color, shape) |
| 内边距 + 点击 | .padding(x).clickable {} |
| 点击时不超出圆角 | .clip(shape).clickable {} |
| 外部阴影 + 圆角内容 | .shadow(x).clip(shape) |
核心规律 :越"底层"的修饰(尺寸、背景)越靠外;越"上层"的修饰(间距、事件)越靠内------但实际上不同场景顺序不同,建议通过
@Preview实时查看效果。
5.4 Modifier 的组合与复用
提取公共 Modifier
scss
// 方式一:val 存储
val cardModifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.clip(RoundedCornerShape(12.dp))
.background(MaterialTheme.colorScheme.surfaceVariant)
Surface(modifier = cardModifier) { ... }
Surface(modifier = cardModifier) { ... }
// 方式二:扩展函数
fun Modifier.cardStyle(): Modifier = this
.fillMaxWidth()
.padding(horizontal = 16.dp)
.clip(RoundedCornerShape(12.dp))
.background(MaterialTheme.colorScheme.surfaceVariant)
Surface(modifier = Modifier.cardStyle()) { ... }
条件 Modifier
kotlin
// ❌ 不优雅
@Composable
fun UserCard(isHighlighted: Boolean) {
val modifier = if (isHighlighted) {
Modifier.background(Color.Yellow).border(2.dp, Color.Red)
} else {
Modifier
}
Text("Content", modifier)
}
// ✅ 优雅:使用 Modifier.then() + takeIf
@Composable
fun UserCard(isHighlighted: Boolean) {
Text(
"Content",
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.then(
if (isHighlighted) {
Modifier.background(Color.Yellow).border(2.dp, Color.Red)
} else {
Modifier
}
)
)
}
// ✅ 更简洁的写法
@Composable
fun UserCard(isHighlighted: Boolean) {
Text(
"Content",
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.background(if (isHighlighted) Color.Yellow else Color.Transparent)
.border(if (isHighlighted) 2.dp else 0.dp, Color.Red)
)
}
5.5 Modifier 的绘制原理(进阶)
每个 Modifier 在底层对应一个 Modifier.Element,其中一些实现了 DrawModifier 或 LayoutModifier 接口。
kotlin
// 一个简化的背景 Modifier 实现逻辑
class BackgroundModifier(
val color: Color,
val shape: Shape
) : DrawModifier {
override fun ContentDrawScope.draw() {
drawRoundRect(color, style = shape.toPx(size, density))
drawContent() // 绘制子内容
}
}
理解这个机制有助于你编写自定义 Modifier:
kotlin
// 自定义 Modifier ------ 在子元素下方绘制一条线
fun Modifier.bottomLine(color: Color, thickness: Float = 1f): Modifier = this.then(
object : DrawModifier {
override fun ContentDrawScope.draw() {
drawContent()
// 在内容下方绘制分隔线
drawLine(
color = color,
start = Offset(0f, size.height),
end = Offset(size.width, size.height),
strokeWidth = thickness
)
}
}
)
// 使用
Text("Item", Modifier.bottomLine(Color.Gray))
5.6 Modifier 与 Composable 参数选择
很多场景既可以用 Modifier 实现,也可以用 Composable 参数实现:
scss
// 方式一:Modifier
Text("Hello", modifier = Modifier.background(Color.Red))
// 方式二:Composable 属性
Surface(color = Color.Red) {
Text("Hello")
}
选择原则:偏向 Modifier 更灵活、组合性强;偏向 Composable 属性更语义化、更直观。实际开发中二者并无绝对边界,团队约定一致即可。