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
相关产品推荐
相关产品推荐

