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

如何使用Android Canvas绘制这种波浪圆形?

Android Canvas 实现圆形波浪效果

核心思路

不用硬调贝塞尔曲线控制点,直接用正弦函数计算波浪顶点坐标,结合Canvas的裁剪功能把绘制区域限制在圆形内,能得到更自然的波浪形态。

具体实现代码

自定义View类

class WaveCircleView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.parseColor("#4CAF50")
        style = Paint.Style.FILL
    }
    private val path = Path()
    private var waveOffset = 0f
    private val waveHeight = 20f // 波浪高度
    private val waveWidth = 100f // 波浪波长
    private var progress = 0.5f // 进度范围0-1

    // 波浪滚动动画
    private val valueAnimator = ValueAnimator.ofFloat(0f, waveWidth).apply {
        duration = 1500
        repeatCount = ValueAnimator.INFINITE
        interpolator = LinearInterpolator()
        addUpdateListener {
            waveOffset = it.animatedValue as Float
            invalidate()
        }
    }

    init {
        valueAnimator.start()
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val centerX = width / 2f
        val centerY = height / 2f
        val radius = min(width, height) / 2f

        // 裁剪出圆形绘制区域
        canvas.save()
        val clipPath = Path()
        clipPath.addCircle(centerX, centerY, radius, Path.Direction.CW)
        canvas.clipPath(clipPath)

        // 根据进度计算波浪基线位置
        val baseY = centerY + radius * (1 - progress * 2)

        // 构建波浪路径
        path.reset()
        path.moveTo(-waveWidth + waveOffset, baseY)
        var x = -waveWidth + waveOffset
        while (x <= width + waveWidth) {
            // 正弦函数计算波浪顶点Y坐标
            val y = baseY + Math.sin((x / waveWidth) * 2 * Math.PI) * waveHeight
            path.lineTo(x, y.toFloat())
            x += 20f // 步长越小波浪越平滑
        }
        // 闭合路径,填充到圆形底部
        path.lineTo(width.toFloat(), height.toFloat())
        path.lineTo(0f, height.toFloat())
        path.close()

        canvas.drawPath(path, paint)
        canvas.restore()

        // 绘制圆形边框(可选)
        val borderPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
            color = Color.parseColor("#333333")
            style = Paint.Style.STROKE
            strokeWidth = 4f
        }
        canvas.drawCircle(centerX, centerY, radius, borderPaint)
    }

    // 对外设置进度的方法
    fun setProgress(progress: Float) {
        this.progress = progress.coerceIn(0f, 1f)
        invalidate()
    }
}

布局与代码调用

  1. 布局中添加自定义View:
<com.yourpackage.WaveCircleView
    android:id="@+id/waveCircle"
    android:layout_width="200dp"
    android:layout_height="200dp" />
  1. 代码中设置进度:
val waveCircle = findViewById<WaveCircleView>(R.id.waveCircle)
waveCircle.setProgress(0.7f) // 设置70%填充进度

关键细节说明

  • 正弦曲线生成波浪:相比贝塞尔曲线,正弦函数能生成均匀自然的波浪,避免手动调整控制点的繁琐。
  • 圆形裁剪:通过clipPath限制绘制区域,不用处理波浪超出圆形的部分。
  • 滚动动画:偏移正弦曲线X轴实现波浪流动效果,动画流畅无卡顿。
  • 进度控制:通过调整波浪基线位置,轻松实现不同填充比例的效果。

之前用贝塞尔曲线效果不佳,大多是因为控制点位置计算不精准,用正弦曲线的方式能从根源解决这个问题,匹配示例图的预期效果。

内容的提问来源于stack exchange,提问作者mkki

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 22:52:33