在界面树中布置每个节点的过程分为三个步骤,每个节点必须:
- 测量所有子项
- 确定自己的尺寸
- 放置其子项
在Compose中“自定义复杂View”是一个思维转换:不再通过继承View类并重写onDraw、onMeasure等方法,而是通过组合基础组件、使用Modifier系统、或直接调用Canvas和DrawScope API来构建。
1️⃣ 路径一:组合现有组件 (最常见)
这是Compose最主要的思想。通过将内置的基础组件(Box, Column, Row, Text, Icon等)像搭积木一样组合起来,并施以一系列Modifier(修饰符),你就能创造出全新的、功能复杂的组件。
示例:自定义一个带图标和文字的统计卡片
@Composable
fun StatCard(title: String, value: String, icon: ImageVector) {
Card(
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
) {
Row(
modifier = Modifier.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(40.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Column {
Text(
text = title,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Text(
text = value,
style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.Bold
)
}
}
}
}
// 使用
StatCard(title = "总点击量", value = "1,234", icon = Icons.Filled.TouchApp)
核心:Card、Row、Icon、Column、Text都是系统提供的可组合项,通过Modifier.padding、.size等调整外观和布局,封装成一个新的StatCard。
2️⃣ 路径二:使用 Canvas 进行自定义绘制
当你需要绘制自定义图形(如折线图、圆形进度条、不规则形状)时,应使用 Canvas。
示例:绘制一个简单的自定义进度指示器
@Composable
fun CustomProgressIndicator(progress: Float) { // progress 范围 0f-1f
Canvas(
modifier = Modifier
.size(100.dp)
.padding(8.dp)
) {
// 1. 绘制背景圆环
drawCircle(
color = Color.LightGray,
style = Stroke(width = 12.dp.toPx(), cap = StrokeCap.Round)
)
// 2. 根据进度绘制前景圆弧
drawArc(
color = MaterialTheme.colorScheme.primary,
startAngle = -90f, // 从12点钟方向开始
sweepAngle = 360 * progress, // 扫过的角度
useCenter = false,
style = Stroke(width = 12.dp.toPx(), cap = StrokeCap.Round)
)
// 3. 在中心绘制进度文本
drawContext.canvas.nativeCanvas.apply {
drawText(
"${(progress * 100).toInt()}%",
center.x,
center.y + 15.dp.toPx() / 2,
android.graphics.Paint().apply {
textSize = 14.sp.toPx()
textAlign = android.graphics.Paint.Align.CENTER
color = android.graphics.Color.BLACK
}
)
}
}
}
核心:在 DrawScope 作用域内(drawArc、drawCircle 等函数的接收者),使用其提供的绘图API。toPx() 用于将 dp 转换为像素。
3️⃣ 路径三:实现自定义布局
当子组件需要特殊的测量或摆放逻辑时(如瀑布流、环形布局),需要使用 Layout 可组合项。
示例:实现一个基础的竖向堆叠但居中对其的布局
@Composable
fun VerticalCenteredLayout(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
// 1. 测量每个子项
// 注意 constraints 表示父元素允许子元素的尺寸范围,如果子元素从该范围内选择尺寸
// 那么父元素必须接受并处理子元素。尺寸范围包含最大最小高度和最大最小宽度,当最大最小高度/宽度相等的时候表示高度/宽度为确定值
// minWidth,MaxWidth = 1440
// minHeight,MaxHeight = 200px
//modifier.height = 50.dp = 200px
//modifier.fillMaxWidth = 1440px
val placeables = measurables.map { it.measure(constraints) }
val placeables = measurables.map {
it.measure(Constrains(0,constraints.width,0,constraints.height))
}
// 2. 计算总高度和最大宽度
val totalHeight = placeables.sumOf { it.height }
val maxWidth = placeables.maxOfOrNull { it.width } ?: 0
// 3. 决定当前布局的尺寸
layout(maxWidth, totalHeight) {
var yPosition = 0
// 4. 摆放每个子项:水平居中,垂直依次排列
placeables.forEach { placeable ->
val xPosition = (maxWidth - placeable.width) / 2
placeable.placeRelative(x = xPosition, y = yPosition)
yPosition += placeable.height
}
}
}
}
// 使用
VerticalCenteredLayout(Modifier.fillMaxSize()) {
Text("第一行")
Text("第二行稍长的文字")
Button(onClick = {}) { Text("按钮") }
}
核心:在 Layout 的 measure 块中,手动测量 (measurables[i].measure(constraints)) 和摆放 (placeable.placeRelative(x, y)) 每一个子组件。
💡 将“修饰”行为 抽象:自定义 Modifier
如果你想创建一个可复用的行为或样式(如统一的点击涟漪、边框),可以自定义 Modifier。
示例:创建一个添加统一阴影和圆角的Modifier
fun Modifier.customCardStyle() = this
.shadow(elevation = 8.dp, shape = RoundedCornerShape(12.dp))
.background(
color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(12.dp)
)
.clip(RoundedCornerShape(12.dp))
// 使用
Box(modifier = Modifier.customCardStyle().size(100.dp)) {
// 内容...
}
🎯 总结:从传统View到Compose思维的转变
| 传统 View 系统 | Jetpack Compose |
|---|---|
继承 View 类,重写 onDraw, onMeasure | 创建 @Composable 函数,组合或绘制 |
使用 Canvas 和 Paint 对象 | 在 DrawScope 作用域内调用绘图指令 |
在 onLayout 中计算子View位置 | 在 Layout 可组合项的测量块中摆放子项 |
通过 setOnClickListener 添加交互 | 使用 Modifier.clickable 等 |
| 自定义属性通过XML或构造函数设置 | 通过函数参数传递,支持默认值和状态提升 |
简单来说,在Compose中,一切自定义UI都是可组合函数。你的核心工具是:
- 组合:用现有组件搭积木。
- 绘制:用
Canvas画自定义图形。 - 布局:用
Layout定义摆放规则。 - 修饰:用
Modifier添加行为和样式。
如果你的“复杂View”有更具体的需求(比如需要处理复杂手势、实现动画效果,或对性能有极高要求),我们可以进一步深入探讨。