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

Android自定义View用Canvas绘制顶部Zigzag锯齿View路径不流畅

问题说明

Android自定义View实现顶部Zigzag锯齿白色View时,路径过渡不流畅、效果不符合预期。

  • 目标效果参考:
    目标锯齿样式效果
  • 当前错误实现效果:
    实际运行错误效果
当前问题代码
class TestCurvedPathView @JvmOverloads
    constructor(
    private val ctx: Context,
    private val attributeSet: AttributeSet? = null,
    private val defStyleAttr: Int = 0
) : View(ctx, attributeSet, defStyleAttr) {

    var path = Path()
    var pathPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.parseColor("#892cdc")
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        drawCurvedPath(canvas)
    }

    private fun drawCurvedPath(canvas: Canvas?) {

        path.moveTo(0f * width, 0.2f * height)
        path.quadTo(0.4f * width, 0.1f * height, 0.6f * width, 0.4f * height)
        path.quadTo(0.8f * width, 0.5f * height, 1f * width, 0.4f * height)
        path.lineTo(1f * width, 1f * height)
        path.lineTo(0f * width, 1f * height)

        canvas?.drawPath(path, pathPaint)

    }
}
问题原因

当前实现无法得到预期锯齿效果,核心问题有三点:

  • 采用quadTo二次贝塞尔曲线绘制平滑弧形过渡,本质是实现波浪效果,和锯齿需要的直线折角形态完全不匹配
  • 全局Path对象未在每次绘制前重置,多次触发onDraw时路径会重复叠加,最终出现形态错乱
  • 未调用path.close()闭合路径,填充时容易出现边缘渲染异常,且路径点位未做等距均分,过渡生硬不规整
正确实现方案

锯齿为等距三角折角形态,直接通过lineTo循环绘制直线段即可,不需要使用贝塞尔曲线,修改后可直接运行的完整代码如下:

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

    // 锯齿尺寸配置,可根据设计稿调整
    private val zigzagHeightDp = 12f // 锯齿突出高度
    private val zigzagSingleWidthDp = 24f // 单个锯齿总宽度
    private val fillPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.WHITE // 匹配需求的白色View
        style = Paint.Style.FILL
    }
    private val drawPath = Path()

    // dp转像素适配
    private val zigzagHeightPx: Float
        get() = zigzagHeightDp * resources.displayMetrics.density
    private val zigzagHalfWidthPx: Float
        get() = zigzagSingleWidthDp / 2 * resources.displayMetrics.density

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        drawPath.reset() // 每次绘制前清空旧路径,避免叠加错乱
        val viewWidth = width.toFloat()
        val viewHeight = height.toFloat()

        // 绘制起点:左上角偏移一个锯齿高度的位置
        drawPath.moveTo(0f, zigzagHeightPx)

        // 循环绘制顶部锯齿折线段
        var currentX = 0f
        var isPeakUp = true
        while (currentX < viewWidth) {
            val nextX = currentX + zigzagHalfWidthPx
            val nextY = if (isPeakUp) 0f else zigzagHeightPx
            drawPath.lineTo(nextX, nextY)
            currentX = nextX
            isPeakUp = !isPeakUp
        }

        // 补全View下方矩形区域
        drawPath.lineTo(viewWidth, viewHeight)
        drawPath.lineTo(0f, viewHeight)
        drawPath.close() // 闭合路径,保证填充边缘正常

        canvas.drawPath(drawPath, fillPaint)
    }
}
实现说明
  • 每次绘制前重置Path,避免页面重绘时路径重复叠加导致的形态错乱
  • 锯齿通过等距直线段绘制,折角锐利,完全匹配Zigzag形态,性能优于曲线绘制方案
  • 内置dp转像素适配,不同分辨率设备下锯齿尺寸显示一致
  • 锯齿高度、宽度参数可直接调整,适配不同设计需求
  • 路径最后主动闭合,避免填充时出现边缘缺口、渲染异常问题

如果需要实现平滑波浪效果而非硬边锯齿,再考虑使用贝塞尔曲线方案,锯齿场景下直线段绘制是效果最准确、性能最优的选择。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 00:12:28