Jetpack Compose-水平进度条及圆形进度条绘制

自带的组件

效果1

效果

image-20250821092949770

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Preview
@Composable
fun ProgressBarPreview() {
Column(
Modifier
.width(120.dp)
.wrapContentHeight()
.padding(16.dp)
.background(Color.White)
) {
// 圆形不确定进度条
CircularProgressIndicator(
progress = { 0.75f },
modifier = Modifier.padding(16.dp),
color = ProgressIndicatorDefaults.circularColor,
strokeWidth = ProgressIndicatorDefaults.CircularStrokeWidth,
trackColor = Color(0xffCCE7FF),
strokeCap = ProgressIndicatorDefaults.CircularDeterminateStrokeCap,
gapSize = 8.dp
)


// 线性确定进度条
LinearProgressIndicator(
progress = { 0.75f },
modifier = Modifier.padding(16.dp),
color = ProgressIndicatorDefaults.linearColor,
trackColor = ProgressIndicatorDefaults.linearTrackColor,
strokeCap = ProgressIndicatorDefaults.LinearStrokeCap,
)
}
}

效果2

效果

image-20250821093544924

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Preview
@Composable
fun ProgressBarPreview() {
Column(
Modifier
.width(120.dp)
.wrapContentHeight()
.padding(16.dp)
.background(Color.White)
) {
// 圆形不确定进度条
CircularProgressIndicator(
progress = { 0.75f },
modifier = Modifier.padding(10.dp),
color = Color(0xFF0085F7),
strokeWidth = ProgressIndicatorDefaults.CircularStrokeWidth,
trackColor = Color(0xffCCE7FF),
strokeCap = ProgressIndicatorDefaults.CircularDeterminateStrokeCap,
gapSize = 0.dp
)


// 线性确定进度条
LinearProgressIndicator(
progress = { 0.75f },
modifier = Modifier.padding(10.dp),
color = Color(0xFF0085F7),
trackColor = Color(0xffCCE7FF),
strokeCap = ProgressIndicatorDefaults.LinearStrokeCap,
gapSize = 0.dp
)
}
}

去除结尾圆点

效果

image-20250821094444445

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Row(
verticalAlignment = Alignment.CenterVertically
) {
LinearProgressIndicator(
progress = { item.questionRate },
modifier = Modifier
.width(120.dp)
.height(8.dp),
color = Color(0xFF0085F7),
trackColor = Color(0xffCCE7FF),
gapSize = 0.dp,
drawStopIndicator = {}
)

Spacer(modifier = Modifier.width(10.dp))

ZTextColorSizeComp(text = "${item.questionRate * 100} %", fontSize = 12.sp)
}

自定义组件

圆形进度条

自带的效果不满足我们的要求,所以这里自定义组件

效果

image-20250821091851808

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp

/**
* 自定义圆形进度条组件
*
* @param progress 进度值,范围 0f-1f
* @param modifier 修饰符
* @param size 进度条大小
* @param strokeWidth 进度条线宽
* @param backgroundColor 背景圆环颜色
* @param progressColor 进度圆环颜色
* @param strokeCap 线帽样式
*/
@Composable
fun CircularProgressBar(
progress: Float,
modifier: Modifier = Modifier,
size: Dp = 48.dp,
backgroundWidth: Dp = 4.dp,
strokeWidth: Dp = 8.dp,
startAngle: Float = -90f,
backgroundColor: Color = Color.Transparent,
progressColor: Color = Color.Blue,
strokeCap: StrokeCap = StrokeCap.Round
) {
// 确保进度在 0f 到 1f 之间
val clampedProgress = progress.coerceIn(0f, 1f)

Canvas(
modifier = modifier.size(size)
) {
// 绘制背景圆环
drawArc(
color = backgroundColor,
startAngle = 0f,
sweepAngle = 360f,
useCenter = false,
style = Stroke(
width = backgroundWidth.toPx(),
cap = strokeCap
),
size = Size(size.toPx(), size.toPx()),
topLeft = Offset.Zero
)

// 绘制进度圆环 (360度 * 进度)
drawArc(
color = progressColor,
startAngle = startAngle, // 从顶部开始
sweepAngle = 360f * clampedProgress,
useCenter = false,
style = Stroke(
width = strokeWidth.toPx(),
cap = strokeCap
),
size = Size(size.toPx(), size.toPx()),
topLeft = Offset.Zero
)
}
}

@Preview
@Composable
fun CircularProgressBarPreview() {
Box(Modifier.padding(16.dp)) {
CircularProgressBar(
progress = 0.75f,
size = 80.dp,
strokeWidth = 8.dp,
progressColor = Color(0xFF0085F7),
backgroundColor = Color(0xffCCE7FF),
)
}

}

半圆进度条

效果

image-20251218145329040

组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp

/**
* 自定义半圆进度条组件
*
* @param progress 进度值,范围 0f-1f
* @param modifier 修饰符
* @param size 进度条大小
* @param strokeWidth 进度条线宽
* @param backgroundColor 背景圆环颜色
* @param progressColor 进度圆环颜色
* @param strokeCap 线帽样式
*/
@Composable
fun ZCircularHalfProgressBar(
progress: Float,
modifier: Modifier = Modifier,
size: Dp = 48.dp,
backgroundWidth: Dp = 8.dp,
strokeWidth: Dp = 8.dp,
backgroundColor: Color = Color.Blue.copy(0.5f),
progressColor: Color = Color.Blue,
strokeCap: StrokeCap = StrokeCap.Round
) {
val startAngle: Float = -180f
// 确保进度在 0f 到 1f 之间
val clampedProgress = progress.coerceIn(0f, 1f) / 2

Canvas(
modifier = modifier
.width(size)
.height(size / 2)
) {
// 绘制背景圆环
drawArc(
color = backgroundColor,
startAngle = startAngle,
sweepAngle = 180f,
useCenter = false,
style = Stroke(
width = backgroundWidth.toPx(),
cap = strokeCap
),
size = Size(size.toPx(), size.toPx()),
topLeft = Offset.Zero
)

// 绘制进度圆环 (360度 * 进度)
drawArc(
color = progressColor,
startAngle = startAngle, // 从顶部开始
sweepAngle = 360f * clampedProgress,
useCenter = false,
style = Stroke(
width = strokeWidth.toPx(),
cap = strokeCap
),
size = Size(size.toPx(), size.toPx()),
topLeft = Offset.Zero
)
}
}


@Preview
@Composable
fun CircularHalfProgressBarPreview() {
Box(Modifier.padding(10.dp)) {
ZCircularHalfProgressBar(
progress = 0.75f,
size = 80.dp,
backgroundWidth = 8.dp,
progressColor = Color(0xFF0085F7),
backgroundColor = Color(0xffCCE7FF),
)
}
}

分段进度条

效果

image-20260104111714906

组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Fill
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp

@Composable
fun ZHorProgressBar(
progress: Int = 0,
total: Int = 5,
spaceWidth: Dp = 4.dp,
backgroundColor: Color = Color(0xFFE0E0E0),
progressColor: Color = Color(0xFF06D65A),
) {
val density = LocalDensity.current
val clampedProgress = progress.coerceIn(0, total)
var itemHeight by remember { mutableStateOf(20f) }
var itemWidth by remember { mutableStateOf(20f) }
var spaceWidthPx by remember { mutableStateOf(20f) }
Canvas(
modifier = Modifier
.fillMaxSize()
.onGloballyPositioned { coordinates ->
itemHeight = coordinates.size.height.toFloat()
val outerWidth = coordinates.size.width.toFloat()
spaceWidthPx = with(density) { spaceWidth.toPx() }
itemWidth = (outerWidth - (total - 1) * spaceWidthPx) / total
}
) {

for (i in 0 until total) {
val x = i * (itemWidth + spaceWidthPx)
drawRoundRect(
color = if (i < clampedProgress) progressColor else backgroundColor,
topLeft = Offset(x, 0f),
size = Size(itemWidth, itemHeight),
alpha = 1f,
style = Fill,
cornerRadius = CornerRadius(itemHeight / 2, itemHeight / 2)
)
}

}
}


@Preview
@Composable
fun ZHorProgressBarPreview() {
Box(
Modifier
.padding(16.dp)
.width(200.dp)
.height(6.dp)
) {
ZHorProgressBar(
progress = 2,
total = 5,
)
}
}