You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 11:54:04