Jetpack Compose自定义进度条ProgressIndicator

4,041 阅读2分钟

「这是我参与2022首次更文挑战的第14天,活动详情查看:2022首次更文挑战

当不满足官方的ProgressIndicator的时候,作为一名优秀的安卓开发者,肯定要自定义一个啦

ec66e16b382372309ace26b330df337.jpg

自定义ProgressIndicator

compose的Canvas提供了丰富的Api足以满足我们打造各种炫酷效果,我们只管发挥想象力,之前我有写过用Compose画太阳和月亮,感兴趣的可以去我的文章里面看。

传统写法Android自定义加载进度条+自定义Dialog简洁弹窗 - 掘金 (juejin.cn)

首先用Canvas的drawLine简单画一条直线

Canvas(modifier.padding(16.dp).fillMaxWidth()) {

        val canvasWidth = size.width  // 画布的宽
        val canvasHeight = size.height  // 画布的高

        drawLine(
            color = Color.Green,
            start = Offset(0f, 0f),
            end = Offset(canvasWidth, 0f),
            strokeWidth = 40f         
        )
    }

33c50930e04d170d8ca79744b00e531.jpg

用StrokeCap设置成圆角

cap = StrokeCap.Round

这绿太刺眼换一个 3d46166b590f4515cdec1dd8c0f1663.jpg

接着传值让它动起来
定义一个接收参数value的范围 0~100

那么进度条的宽度就是画布的总宽度× (value/100)

@Composable
private fun progressbar(modifier: Modifier = Modifier, value: Float) {


    Canvas(modifier.padding(16.dp).fillMaxWidth()) {

        val canvasWidth = size.width  // 画布的宽
        val canvasHeight = size.height  // 画布的高

        drawLine(
            color = Color.Cyan,
            start = Offset(0f, 0f),
            end = Offset(canvasWidth*(value/100), 0f),
            strokeWidth = 40f ,
            cap = StrokeCap.Round
        )

    }
}

使用-模拟传值

var progressValue by remember { mutableStateOf(0F) }
for (i in 1..100) { 
    progressValue = i.toFloat()
}
progressbar(value = progressValue)

不对呀,这太快看不清了呀,那怎么办呢?
好办用delay()延迟一下

compose中使用携程就用到LaunchedEffect接着把for循环放到launch里面。因为delay()是个挂起函数

var progressValue by remember { mutableStateOf(0F) }

LaunchedEffect(rememberScaffoldState()) {
    for (i in 1..100) {
        delay(30)
        progressValue = i.toFloat()
    }
}
progressbar(value = progressValue)

video_220131_165844.gif

大致效果出来了继续优化

加个进度文本

Column() {

    Canvas(modifier.fillMaxWidth().padding(16.dp)) {
      ...
    }
    Row {
        Spacer(Modifier.weight(1f))
        Text("$value%",modifier.padding(end =16.dp))
    }

}

video_220131_173838.gif 裁剪时手抖了一下所以边缘略微闪烁

完整代码

@Composable
private fun progressbar(modifier: Modifier = Modifier, value: Float) {

   val color = Brush.verticalGradient(
        listOf(
            CustomTheme.colors.statusBarColor,
            CustomTheme.colors.background
        )
    )
    Column() {

        Canvas(modifier.fillMaxWidth().padding(16.dp)) {

            val canvasWidth = size.width  // 画布的宽
            val canvasHeight = size.height  // 画布的高

            drawLine(
                color = Color.Cyan,
                start = Offset(0f, 0f),
                end = Offset(canvasWidth*(value/100), 0f),
                strokeWidth = 40f ,
                cap = StrokeCap.Round
            )
        }
        Row {
            Spacer(Modifier.weight(1f))
            Text("$value%",modifier.padding(end =16.dp))
        }

    }

}

把进度条换成渐变色

这个就要用到Path和drawPath了

video_220131_181604.gif

完整代码

@Composable
private fun progressbar(modifier: Modifier = Modifier, value: Float) {

    Column() {

        Canvas(modifier.fillMaxWidth().padding(16.dp)) {

            val canvasWidth = size.width  // 画布的宽
            val canvasHeight = size.height  // 画布的高
            val strokeWidth = canvasHeight / 20
            val path = Path()
            path.lineTo(canvasWidth*(value/100), 0f)

            drawPath(
                path = path,
                style = Stroke(
                    width = strokeWidth,
                    cap = StrokeCap.Round
                ),
                brush = Brush.horizontalGradient(
                    colors = listOf(
                        Color.Transparent,
                        Color.Cyan
                    )
                )

            )
        }
        Row {
            Spacer(Modifier.weight(1f))
            Text("$value%",modifier.padding(end =16.dp))
        }

    }

}