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
相关产品推荐
相关产品推荐

