Jetpack Compose【五】 高级布局与绘制技巧

395 阅读3分钟

前言

在 Jetpack Compose 中,灵活的 UI 构建能力允许开发者以直观、声明式的方式创建界面。不仅如此,Compose 还提供了多个强大的 API 以支持自定义绘制和布局。本文将通过几个示例,展示如何在 Compose 中实现常见的自定义绘制与布局需求。

一、 使用 Canvas 自定义绘制

Canvas 是 Compose 中提供的低层次绘图 API,类似于传统的 onDraw() 方法。通过 drawRect()drawCircle()drawPath() 等方法,你可以绘制各种图形,满足自定义需求。

示例:绘制一个自定义圆形进度条

@Composable
fun CoolProgressBar(
    progress: Float, // 进度 0f - 1f
    modifier: Modifier = Modifier,
    strokeWidth: Float = 20f, // 进度条宽度
    startColor: Color = Color(0xFFFF5722), // 渐变起始色
    endColor: Color = Color(0xFF2196F3) // 渐变结束色
) {
    Canvas(modifier = modifier) {
        val size = size.minDimension
        val radius = size / 2f
        val center = Offset(size / 2f, size / 2f)

        // 绘制背景圆环
        drawCircle(
            color = Color.LightGray.copy(alpha = 0.3f),
            radius = radius - strokeWidth / 2,
            style = Stroke(width = strokeWidth, cap = StrokeCap.Round)
        )

        // 计算渐变颜色
        val brush = Brush.linearGradient(
            colors = listOf(startColor, endColor),
            start = Offset(0f, 0f),
            end = Offset(size, size)
        )

        // 计算进度角度
        val sweepAngle = progress * 360f

        // 绘制进度条
        drawArc(
            brush = brush,
            startAngle = -90f,
            sweepAngle = sweepAngle,
            useCenter = false,
            style = Stroke(width = strokeWidth, cap = StrokeCap.Round)
        )
    }
}

@Composable
fun CoolProgressBarDemo() {
    var progress by remember { mutableFloatStateOf(0.3f) }

    // 进度平滑动画
    val animatedProgress by animateFloatAsState(
        targetValue = progress,
        animationSpec = tween(durationMillis = 1000, easing = FastOutSlowInEasing), label = ""
    )

    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        CoolProgressBar(
            progress = animatedProgress,
            modifier = Modifier
                .size(150.dp)
                .clip(CircleShape)
        )

        Spacer(modifier = Modifier.height(20.dp))

        Button(onClick = { progress = Random.nextFloat().coerceIn(0.1f, 1f)}) {
            Text("随机进度")
      

通过 Canvas,我们可以绘制一个带有背景圆环和动态进度的圆形进度条。drawArc() 让进度条根据传入的 progress 绘制。

二、 使用 Layout 自定义布局

Compose 中,RowColumnBox 可能无法满足所有布局需求。此时,可以使用 Layout API 创建复杂的自定义布局。Layout 像是传统View中onMeasure()+onLayout()的结合。

示例:实现一个流式布局

@Composable
fun FlowLayout(
    modifier: Modifier = Modifier,
    maxWidth: Dp = 300.dp, // 限制整体宽度
    content: @Composable () -> Unit
) {
    Layout(
        content = content,
        modifier = modifier
    ) { measurables, constraints ->

        // 将 maxWidth 转换为 Px,并与父布局宽度取最小值,确保不超出
        val layoutMaxWidth = minOf(maxWidth.roundToPx(), constraints.maxWidth)

        // 测量所有子项
        val placeables = measurables.map { it.measure(constraints.copy(maxWidth = layoutMaxWidth)) }

        var currentX = 0
        var currentY = 0
        var maxHeightInRow = 0

        layout(layoutMaxWidth, constraints.maxHeight) {
            placeables.forEach { placeable ->
                // 超出 maxWidth 时换行
                if (currentX + placeable.width > layoutMaxWidth) {
                    currentX = 0
                    currentY += maxHeightInRow
                    maxHeightInRow = 0
                }

                placeable.placeRelative(currentX, currentY)
                currentX += placeable.width
                maxHeightInRow = maxOf(maxHeightInRow, placeable.height)
            }
        }
    }
}

@Composable
fun TestFlowLayout() {
    FlowLayout(maxWidth = 320.dp) {
        FlowText("MyOwnColumn")
        FlowText("places items")
        FlowText("vertically.")
        FlowText("We've done it by hand!")
        FlowText("final")
    }
}

@Composable
fun FlowText(text: String) {
    Text(
        text = text,
        modifier = Modifier
            .padding(8.dp)
            .border(1.dp, Color.Gray)
            .padding(8.dp)
    )
}

通过 Layout API,可以控制每个元素的尺寸和位置。这里的布局会将每个标签垂直排列,形成一个简单的标签列表。

三、 使用 Modifier.drawWithContent 定制绘制

如果你需要在现有组件上添加额外的绘制效果(如边框或渐变效果),可以使用 Modifier.drawWithContent

示例:为文本添加下划线

@Composable
fun UnderlinedText(text: String) {
    Text(
        text = text,
        modifier = Modifier.drawWithContent {
            drawContent() // 先绘制内容
            drawLine(
                color = Color.Red,
                start = Offset(0f, size.height),
                end = Offset(size.width, size.height),
                strokeWidth = 2f
            )
        }
    )
}

@Preview
@Composable
fun UnderlinedTextPreview() {
    UnderlinedText("带下划线的文本")
}

drawWithContent 用来在绘制文本的基础上,为其添加一条红色的下划线。


四、 使用 AndroidView 复用传统 View

通过 AndroidView,你可以将传统的 View 嵌入 Compose 布局中,复用 XML 布局中已有的自定义视图。

示例:在 Compose 中嵌入 传统View

@Composable
fun WebViewComponent(url: String) {
    AndroidView(
        modifier = Modifier.fillMaxSize(),
        factory = { context ->
            //WebView、SurfaceView、其它自定义View
        }
    )
}

五、总结

自定义方式适用场景核心 API
Canvas自定义图形、控件drawCircle()drawPath()
Layout自定义布局LayoutMeasureScope
drawWithContent叠加效果drawLine()drawRect()
AndroidView复用传统 ViewAndroidView

Compose 提供了灵活的自定义扩展能力,可以满足大多数 UI 设计需求。根据实际情况选择合适的自定义方式,能够帮助你轻松实现多种复杂的 UI 效果。