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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.31 13:06:08