如何在Jetpack Compose中使用Canvas绘制钟形曲线
Jetpack Compose Canvas 实现钟形(正态分布)曲线组件方案
核心实现逻辑
- 钟形曲线本质是正态分布概率密度函数,对应公式为
f(x) = (1/(σ√(2π))) * e^(-(x-μ)²/(2σ²)),适配Canvas场景时可简化参数:μ取绘制区间中点,σ调整曲线陡峭程度,最终把计算出的y值映射到Canvas的高度范围内即可 - 提前计算曲线上的所有采样点,用
Path连接所有点形成连续曲线 - 要填充曲线下方区域的话,把Path闭合到画布底部即可
- 可根据需求额外添加高亮区间、坐标轴、刻度等元素匹配目标效果
完整可运行代码示例
import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.unit.dp import kotlin.math.exp import kotlin.math.pow import kotlin.math.sqrt @Composable fun BellCurveChart( modifier: Modifier = Modifier, curveColor: Color = Color(0xFF2196F3), fillColor: Color = Color(0x662196F3), strokeWidth: Float = 2.dp.toPx(), // 正态分布参数:均值μ、标准差σ mean: Float = 0f, standardDeviation: Float = 1f, // 采样点数量,数值越高曲线越平滑 sampleCount: Int = 200 ) { Canvas( modifier = modifier .fillMaxWidth() .height(200.dp) ) { val canvasWidth = size.width val canvasHeight = size.height // 把x轴映射到 [-3σ, 3σ] 区间,覆盖99.7%的分布数据 val xRangeStart = mean - 3 * standardDeviation val xRangeEnd = mean + 3 * standardDeviation val xRange = xRangeEnd - xRangeStart // 先计算所有采样点的y值,找到最大y值做归一化 val points = mutableListOf<Offset>() var maxY = 0f for (i in 0 until sampleCount) { val xInRange = xRangeStart + (i / (sampleCount - 1f)) * xRange // 正态分布概率密度计算 val y = (1 / (standardDeviation * sqrt(2 * Math.PI))) * exp(-(xInRange - mean).pow(2) / (2 * standardDeviation.pow(2))) maxY = maxOf(maxY, y.toFloat()) points.add(Offset(xInRange, y.toFloat())) } // 把坐标映射到Canvas实际像素 val path = Path() points.forEachIndexed { index, point -> val canvasX = (point.x - xRangeStart) / xRange * canvasWidth // Canvas坐标原点在左上角,所以y轴要翻转,顶部留10dp边距 val canvasY = canvasHeight - (point.y / maxY) * (canvasHeight - 20.dp.toPx()) - 10.dp.toPx() if (index == 0) { path.moveTo(canvasX, canvasY) } else { path.lineTo(canvasX, canvasY) } } // 绘制曲线下填充区域 val fillPath = path.deepCopy() fillPath.lineTo(canvasWidth, canvasHeight) fillPath.lineTo(0f, canvasHeight) fillPath.close() drawPath(fillPath, color = fillColor) // 绘制曲线描边 drawPath(path, color = curveColor, style = Stroke(width = strokeWidth)) // 可选:绘制中间基准线 drawLine( color = Color.Gray.copy(alpha = 0.5f), start = Offset(canvasWidth / 2, 10.dp.toPx()), end = Offset(canvasWidth / 2, canvasHeight), strokeWidth = 1.dp.toPx() ) } }
组件调用方式
直接在你的Composable布局中调用即可,可自定义颜色、标准差等参数:
// 基础调用 BellCurveChart() // 自定义参数示例 BellCurveChart( curveColor = Color(0xFF4CAF50), fillColor = Color(0x664CAF50), standardDeviation = 0.8f, modifier = Modifier.padding(16.dp) )
效果调整说明
- 调整
standardDeviation参数可以改变曲线的陡峭程度:数值越小曲线越尖,越大曲线越平缓 - 调整
sampleCount可以平衡绘制性能和曲线平滑度:普通场景100-300足够 - 可以额外扩展添加区间高亮、数值标注、坐标轴刻度等功能,只需要在Canvas代码块中追加对应的绘制逻辑即可
内容的提问来源于stack exchange,提问作者Shura
相关产品推荐
相关产品推荐

