You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose中如何用Canvas在两圆间画线并获取终点Offset

解决Jetpack Compose中步骤进度条的圆间连线问题

要实现两个圆之间的线条绘制且避免硬编码,核心思路是由父容器统一管理所有步骤的布局位置,通过自定义布局获取每个圆的准确坐标,再计算相邻圆的连线起点和终点。

重构组件结构

将单个圆的绘制与线条绘制分离:子组件负责绘制单个圆,父组件负责排列所有圆并绘制圆间线条。

1. 子组件:单个步骤圆

简化原组件,只处理单个圆的绘制,不涉及线条逻辑:

@Composable
fun StepItem(
    modifier: Modifier = Modifier,
    circleColor: Color,
    backGroundColor: Color,
) {
    val radius = 16.dp
    val strokeWidth = 2.dp.toPx()
    Canvas(modifier = modifier.size(radius * 2)) {
        // 绘制背景圆
        drawCircle(
            color = backGroundColor,
            radius = radius.toPx(),
            alpha = 0.1f,
            center = center
        )
        // 绘制边框圆
        drawCircle(
            color = circleColor,
            radius = radius.toPx(),
            style = Stroke(width = strokeWidth),
            center = center
        )
    }
}

2. 父组件:步骤容器与线条绘制

使用Layout自定义布局,获取每个步骤的位置后,在Canvas中绘制相邻圆之间的线条:

@Composable
fun StepProgressContainer(
    modifier: Modifier = Modifier,
    stepCount: Int,
    activeColor: Color,
    inactiveColor: Color,
    completedSteps: Int = 0,
    stepContent: @Composable (Int) -> Unit
) {
    val radius = 16.dp
    val strokeWidth = 2.dp.toPx()
    val radiusPx = radius.toPx()
    val stepSpacing = 40.dp.toPx()

    Layout(
        modifier = modifier,
        content = {
            repeat(stepCount) { index ->
                stepContent(index)
            }
        }
    ) { measurables, constraints ->
        // 测量所有步骤项
        val placeables = measurables.map { it.measure(constraints) }
        val stepWidth = placeables.firstOrNull()?.width ?: 0
        val totalWidth = stepWidth * stepCount + (stepCount - 1) * stepSpacing
        val totalHeight = placeables.maxOf { it.height }

        // 布局所有步骤项
        layout(totalWidth.toInt(), totalHeight) {
            var xPosition = 0f
            placeables.forEachIndexed { index, placeable ->
                placeable.placeRelative(xPosition.toInt(), 0)
                xPosition += placeable.width + stepSpacing
            }
        }
    }

    // 在Canvas中绘制圆间线条
    Canvas(modifier = modifier) {
        val stepWidth = (size.width - (stepCount - 1) * stepSpacing) / stepCount
        val centerY = size.height / 2

        repeat(stepCount - 1) { index ->
            // 当前圆的右侧边缘坐标
            val startX = (stepWidth * (index + 1)) + (stepSpacing * index) - radiusPx
            // 下一个圆的左侧边缘坐标
            val endX = (stepWidth * (index + 1)) + (stepSpacing * (index + 1)) + radiusPx

            // 根据步骤完成状态设置线条颜色
            val lineColor = if (index < completedSteps) activeColor else inactiveColor.copy(alpha = 0.1f)

            drawLine(
                color = lineColor,
                start = Offset(startX, centerY),
                end = Offset(endX, centerY),
                strokeWidth = strokeWidth
            )
        }
    }
}

3. 使用示例

@Composable
fun StepProgressDemo() {
    StepProgressContainer(
        modifier = Modifier.fillMaxWidth().padding(horizontal = 20.dp),
        stepCount = 3,
        activeColor = Color.Blue,
        inactiveColor = Color.Gray,
        completedSteps = 1
    ) { index ->
        StepItem(
            circleColor = if (index <= 1) Color.Blue else Color.Gray,
            backGroundColor = Color.Gray
        )
    }
}

关键逻辑说明

  • 父组件通过Layout测量并排列所有步骤项,动态计算每个步骤的位置,彻底避免硬编码偏移量。
  • 线条起点取当前圆的右侧边缘(圆心X + 半径),终点取下一个圆的左侧边缘(下一个圆心X - 半径),保证线条刚好连接两个圆的边缘。
  • 通过completedSteps参数控制已完成步骤的线条颜色,实现进度可视化效果。

内容的提问来源于stack exchange,提问作者melikaafrakhteh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 19:25:22