Android自定义View Canvas绘制彩色渐变圆弧颜色渲染异常问题
Android Canvas绘制渐变轮盘颜色错乱问题
问题背景
开发自定义View时需要在Canvas上绘制带渐变色彩的等分轮盘,目前角度计算、环形位置定位的基础逻辑已完成,但实际运行时渐变颜色显示错乱,渲染效果和预期不符。
预期实现效果

当前异常实现代码
class CustomWheel : View { constructor(context: Context) : super(context, null) constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet, 0) constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int) : super( context, attributeSet, defStyleAttr ) val section1Paint = Paint().apply { val gradient = LinearGradient(0f,0f,0f,20f, intArrayOf(resources.getColor(R.color.paint1start),resources.getColor(R.color.paint1end)), floatArrayOf(0f, 0.5f), Shader.TileMode.MIRROR) this.shader = gradient } val section2Paint = Paint().apply { val gradient = LinearGradient(0f,0f,0f,20f, intArrayOf(resources.getColor(R.color.paint2start),resources.getColor(R.color.paint2end)), floatArrayOf(0f, 1f), Shader.TileMode.MIRROR) this.shader = gradient } val section3Paint = Paint().apply { val gradient = LinearGradient(0f,0f,0f,20f, intArrayOf(resources.getColor(R.color.paint3start),resources.getColor(R.color.paint3end)), floatArrayOf(0f, 1f), Shader.TileMode.MIRROR) this.shader = gradient } val section4Paint = Paint().apply { val gradient = LinearGradient(0f,0f,0f,20f, intArrayOf(resources.getColor(R.color.paint4start),resources.getColor(R.color.paint4end)), floatArrayOf(0f, 1f), Shader.TileMode.MIRROR) this.shader = gradient } val section5Paint = Paint().apply { val gradient = LinearGradient(0f,0f,0f,20f, intArrayOf(resources.getColor(R.color.paint5start),resources.getColor(R.color.paint5end)), floatArrayOf(0f, 1f), Shader.TileMode.MIRROR) this.shader = gradient } override fun onDraw(canvas: Canvas?) { super.onDraw(canvas) val x = ((width - height / 2) / 2).toFloat() val y = (height / 4).toFloat() val oval = RectF(x, y, width - x, height - y) canvas?.drawArc(oval, 0F, 72F, false, section1Paint) canvas?.drawArc(oval, 72F, 144F, false, section2Paint) canvas?.drawArc(oval, 144F, 216F, false, section3Paint) canvas?.drawArc(oval, 216F, 288F, false, section4Paint) canvas?.drawArc(oval, 288F, 360F, false, section5Paint) } }
实际运行异常效果
问题根因
drawArc参数使用错误:第三个参数代表扇形的扫过角度,不是结束角度。5等分轮盘每个扇形扫过角度固定为72度,现有代码从72度位置开始扫144度、从144度开始扫216度,会导致多个扇形绘制范围重叠,是颜色错乱的核心原因之一。另外原代码useCenter参数传了false,只能绘制弧线,无法形成封闭的扇形区块。LinearGradient配置完全不符合需求:- 渐变坐标写死为(0,0)到(0,20f),是固定垂直方向的短渐变,既没有匹配轮盘实际的环形宽度,也没有跟随扇形角度调整渐变方向,无法实现每个扇形从外沿到内圈的径向渐变效果。
- 第一个扇形的渐变位置数组只配置到0.5位置,搭配
MIRROR重复模式,会在剩余绘制区域镜像重复渐变,出现颜色断层。
- Paint初始化时机错误:在View构造阶段初始化Shader时,View还未完成测量,无法获取实际宽高、轮盘半径等参数,写死的渐变尺寸无法适配最终绘制大小。
修复方案
- 修正
drawArc参数:每个扇形固定传入72度的扫过值,useCenter参数改为true绘制封闭扇形,避免绘制重叠、形状不符问题。 - 把Shader初始化逻辑移到
onSizeChanged回调中,等View完成测量、拿到实际宽高后,计算轮盘的外半径、内半径,以此确定渐变的起止长度。 - 针对每个扇形单独计算径向渐变坐标:渐变方向沿扇形中线从外沿指向圆心,将TileMode改为
CLAMP避免重复渲染。 - 所有渐变的位置数组统一配置为0f到1f,完整覆盖渐变区间,不要中途截断。
核心修复代码参考
class CustomWheel : View { constructor(context: Context) : super(context, null) constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet, 0) constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int) : super( context, attributeSet, defStyleAttr ) // 提前定义5组渐变颜色,构造阶段不初始化Shader private val sectionColors = listOf( intArrayOf(R.color.paint1start, R.color.paint1end), intArrayOf(R.color.paint2start, R.color.paint2end), intArrayOf(R.color.paint3start, R.color.paint3end), intArrayOf(R.color.paint4start, R.color.paint4end), intArrayOf(R.color.paint5start, R.color.paint5end) ) private val paintList = MutableList(5) { Paint(Paint.ANTI_ALIAS_FLAG) } private val sectionSweep = 72f private var outerRadius = 0f private var centerX = 0f private var centerY = 0f override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) centerX = w / 2f centerY = h / 2f outerRadius = w.coerceAtMost(h) / 2f * 0.8f // 外半径留20%边距 val innerRadius = outerRadius * 0.4f // 内圈半径,可按需调整 // 逐个配置扇形渐变 paintList.forEachIndexed { index, paint -> val colorStart = resources.getColor(sectionColors[index][0]) val colorEnd = resources.getColor(sectionColors[index][1]) val centerAngle = index * sectionSweep // 计算扇形中线上的渐变起止点:外沿 -> 内圈 val startX = centerX + outerRadius * cos(Math.toRadians(centerAngle.toDouble())).toFloat() val startY = centerY + outerRadius * sin(Math.toRadians(centerAngle.toDouble())).toFloat() val endX = centerX + innerRadius * cos(Math.toRadians(centerAngle.toDouble())).toFloat() val endY = centerY + innerRadius * sin(Math.toRadians(centerAngle.toDouble())).toFloat() paint.shader = LinearGradient( startX, startY, endX, endY, colorStart, colorEnd, Shader.TileMode.CLAMP ) } } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) val oval = RectF( centerX - outerRadius, centerY - outerRadius, centerX + outerRadius, centerY + outerRadius ) // 逐个绘制扇形 paintList.forEachIndexed { index, paint -> canvas.drawArc(oval, index * sectionSweep, sectionSweep, true, paint) } } }
内容的提问来源于stack exchange,提问作者Kerim Memmedzade
相关产品推荐
相关产品推荐

