Android Jetpack Compose开发纯自定义表盘【可用于体重,温度计等项目】

嗯,几天没写博客,又来了分享知识咯,博主也是菜鸡一个,有错误大家评论提出来,手下留情。
先上图:
Android Jetpack Compose开发纯自定义表盘【可用于体重,温度计等项目】_第1张图片

开发环境:
Mac OS 15.0.1
Android Studio Jellyfish | 2023.3.1 Patch 2
当前程序环境

compileOptions {
        sourceCompatibility JavaVersion.VERSION_11
        targetCompatibility JavaVersion.VERSION_11
    }
    kotlinOptions {
        jvmTarget = '11'
    }
    packaging {
        resources {
            excludes += '/META-INF/{AL2.0,LGPL2.1}'
        }
    }

    // 跟下面俩条件挂钩 
    buildFeatures {
        compose true
        buildConfig = true
    }

    // 可选 开启 viewBinding 视图绑定
    viewBinding {
        enabled = true
    }

    // 可选 开启 dataBinding
    dataBinding {
        enabled = true
    }

    composeOptions {
        kotlinCompilerExtensionVersion '1.5.1'
    }

废话不讲,直接给代码大家。

@Composable
fun WeightGauge(
    weight: Float,
    modifier: Modifier = Modifier
) {
    // 状态
    var centerX by remember { mutableStateOf(0f) }
    var centerY by remember { mutableStateOf(0f) }
    var radius by remember { mutableStateOf(0f) }

    // 计算角度 (0-180度)
    val angle = (weight / 100f) * 180f

    Box(modifier = modifier) {
        Canvas(
            modifier = Modifier.fillMaxSize()
        ) {
            // 保存中心点和半径
            centerX = size.width / 2f
            centerY = size.height
            radius = min(size.width / 2f, size.height) * 0.9f

            // 绘制灰色背景弧
            drawArc(
                color = Color(0xFFE1E6EA),
                startAngle = 180f,
                sweepAngle = 180f,
                useCenter = false,
                topLeft = Offset(centerX - radius, centerY - radius),
                size = Size(radius * 2, radius * 2),
                style = Stroke(width = 30f, cap = StrokeCap.Round)
            )

            // 绘制彩色进度弧【渐变颜色】
            val gradientBrush = Brush.horizontalGradient(
                colors = listOf(
                    Color(0xFF7832FD), // 深紫色
                    Color(0xFFF293FA), // 淡粉色
                    // Color(0xFF81C784)  // 可选更多颜色
                )
            )
            // 绘制彩色进度弧
            drawArc(
                brush = gradientBrush,
                startAngle = 180f,
                sweepAngle = angle,
                useCenter = false,
                topLeft = Offset(centerX - radius, centerY - radius),
                size = Size(radius * 2, radius * 2),
                style = Stroke(width = 30f, cap = StrokeCap.Round)
            )

            // 绘制刻度线 - 钟表式刻度
            // 总共101个刻度(0-100)
            for (i in 0..100) {
                val tickAngle = 180f - (i.toFloat() / 100f) * 180f

                // 确定刻度类型: 长刻度(10的倍数)、中等刻度(5的倍数)、短刻度(其他)
                val tickType = when {
                    i % 10 == 0 -> 0 // 长刻度
                    i % 5 == 0 -> 1  // 中等刻度
                    else -> 2         // 短刻度
                }

                // 根据刻度类型设置长度和宽度
                val tickLength = when (tickType) {
                    0 -> 20f  // 长刻度
                    1 -> 15f  // 中等刻度
                    else -> 8f // 短刻度

                }

                val strokeWidth = when (tickType) {
                    0 -> 2.5f  // 长刻度
                    1 -> 1.8f  // 中等刻度
                    else -> 1f // 短刻度
                }

                val startX =
                    centerX + (radius - 40f) * cos(Math.toRadians(tickAngle.toDouble())).toFloat()
                val startY =
                    centerY - (radius - 40f) * sin(Math.toRadians(tickAngle.toDouble())).toFloat()
                val endX =
                    centerX + (radius - 40f - tickLength) * cos(Math.toRadians(tickAngle.toDouble())).toFloat()
                val endY =
                    centerY - (radius - 40f - tickLength) * sin(Math.toRadians(tickAngle.toDouble())).toFloat()

                drawLine(
                    color = Color(0xFFFBCEFF),  // 线条颜色
                    start = Offset(startX, startY),
                    end = Offset(endX, endY),
                    strokeWidth = strokeWidth
                )

                // 只为10的倍数的刻度添加数值【 绘制文本数值 】
                if (i % 10 == 0) {
                    val textX =
                        centerX + (radius - 70f) * cos(Math.toRadians(tickAngle.toDouble())).toFloat()
                    val textY =
                        centerY - (radius - 70f) * sin(Math.toRadians(tickAngle.toDouble())).toFloat()

                    rotate(180f - tickAngle, Offset(textX, textY)) {
                        drawContext.canvas.nativeCanvas.drawText(
                            (i*2).toString(),  // 这个值是表盘数值
                            textX,
                            textY,
                            android.graphics.Paint().apply {
                                color = android.graphics.Color.parseColor("#FFFBCEFF")
                                textSize = 12.sp.toPx()
                                textAlign = android.graphics.Paint.Align.CENTER
                            }
                        )
                    }
                }
            }

            // 计算指针角度和末端点位置
            val pointerAngle = 180f - angle
            val radians = Math.toRadians(pointerAngle.toDouble())
            val pointerArcEndX = centerX + radius * cos(radians).toFloat()
            val pointerArcEndY = centerY - radius * sin(radians).toFloat()

            // 在指针末端绘制一个小圆点
            drawCircle(
                color = Color(0xFFFBCEFF),
                radius = 15f,
                center = Offset(pointerArcEndX, pointerArcEndY)
            )

            // 绘制固定宽度的菱形指针
            // 计算指针的四个关键点
            val pointerLength = radius * 0.8f // 指针长度
            val pointerWidth = 35f // 指针最宽处宽度

            // 指针末端(靠近弧线的尖端)
            val tipX = centerX + pointerLength * cos(radians).toFloat()
            val tipY = centerY - pointerLength * sin(radians).toFloat()

            // 计算垂直于指针方向的单位向量
            val perpRadians = radians + PI / 2
            val perpCos = cos(perpRadians).toFloat()
            val perpSin = sin(perpRadians).toFloat()

            // 指针中部(最宽处)距离中心的比例
            val midRatio = 0.4f
            val midX = centerX + pointerLength * midRatio * cos(radians).toFloat()
            val midY = centerY - pointerLength * midRatio * sin(radians).toFloat()

            // 指针两侧的宽度点
            val halfWidth = pointerWidth / 2
            val side1X = midX + halfWidth * perpCos
            val side1Y = midY - halfWidth * perpSin
            val side2X = midX - halfWidth * perpCos
            val side2Y = midY + halfWidth * perpSin

            // 指针起点(中心圆处)
            val baseX = centerX
            val baseY = centerY

            // 创建菱形路径
            val diamondPath = Path().apply {
                moveTo(tipX, tipY) // 弧线端尖点
                lineTo(side1X, side1Y) // 一侧宽度点
                lineTo(baseX, baseY) // 中心端点
                lineTo(side2X, side2Y) // 另一侧宽度点
                close()
            }

            // 绘制菱形指针填充
            drawPath(
                path = diamondPath,
                color = Color(0xFFFEE7AC), // 浅粉色填充
                style = Fill
            )

            // 绘制中心圆
            drawCircle(
                color = Color(0xFFFBCEFF), // 浅粉色
                radius = 40f,
                center = Offset(centerX, centerY)
            )
            // 绘制指针边框
            drawPath(
                path = diamondPath,
                color = Color(0xFFFEE7AC), // 边框色
                style = Stroke(width = 2f)
            )
        }
    }
}

这个代码可以直接放到Jetpack Compose项目运行,大家根据需求更改,代码注释写的很清楚了,比如改变指针颜色,指针轮廓颜色,大中小刻度颜色、宽度,半圆弧度的渐变颜色,刻度文字,半圆最大值【(i*2).toString(), // 这个值是表盘数值】等

@Preview
@Composable
fun BodyWeightSelectionSetPagePreview() {
    // 创建可变的 weight 状态,初始值为 50f
    
    Column(modifier = Modifier.fillMaxSize()) {
    
        WeightGauge(weight = 0f,  modifier = Modifier.height(200.dp))
        
        Spacer(modifier = Modifier.height(21.dp))

        WeightGauge(weight = 50f,  modifier = Modifier.height(200.dp))

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

        WeightGauge(weight = 100f,  modifier = Modifier.height(200.dp))
    }
}

要是使用了mvvm模式,就这样// 获取状态
val weightValue by viewModel.weightValue.observeAsState(50f)

博主幸劳,转载记得标注原出处链接,支持原创。

你可能感兴趣的:(Android Jetpack Compose开发纯自定义表盘【可用于体重,温度计等项目】)