Android Paint 浅坑—— Style 模式

本文通过一个业务场景探讨了Android中Paint的Style模式,特别是FILL_AND_STROKE模式的使用误区。在尝试绘制一个灰色圆内侧的黄色和蓝绿色圆弧时,出现了预期之外的深黄色重叠区域。通过对错误进行分析,揭示了Android将STROKE宽度平均分配到圆内外的原理,指出FILL_AND_STROKE并不等同于FILL加上完整的STROKE。解决问题的方法是调整圆弧的外接圆参数,加上STROKE宽度的一半。最终修复后的代码实现了预期的绘制效果。

业务场景

近期遇到一个需求,绘制一个如下的自定义 View ——

最初的想法是先绘制一个中心的灰圆,再绘制外圈的黄色圆弧,再绘制蓝绿色圆弧(此处仅为了讨论坑,不考虑最优绘制方法),于是写下如下代码:

@Override
protected void onDraw(Canvas canvas) {
    // 黄弧最外半径
    mMR = w * 4 / 21;
    // 灰圆半径
    mIR = w * 2 / 21;
    // stoke 宽度
    int width = mMR - mIR;

    // 移动画布
    canvas.translate(mCenterX, mCenterY);

    // 灰圆
    mPaint.setColor(ContextCompat.getColor(context, R.color.colorCenter));
    canvas.drawCircle(0, 0, mIR, mPaint);

    // 黄弧
    mPaint.setColor(ContextCompat.getColor(context, R.color.colorInnerCircle));
    mPaint.setStrokeWidth(width);
    mPaint.setStyle(Paint.Style.STROKE);
    canvas.drawArc(new RectF(-mIR, mIR, mIR, -mIR), 0, 360, false, mPaint);

    // 蓝绿弧
    mPaint.setColor(ContextCompat.getColor(context, R.color.colorOuterCircle));
    mPaint.setStrokeWidth(width);
    mPaint.setStyle(Paint.Style.STROKE);
    canvas.drawArc(new RectF(-mMR, mMR, mMR, -mMR), 0, 360, false, mPaint);
}

思路是这样的:

  • 移动画布到 mCenterX、mCenterY 位置,也就是 View 范围的最中心,方便绘制
  • 绘制最内灰色圆
  • 改变 Paint.Style 为 Paint.Style.STROKE 模式绘制灰色圆外围的黄色圆弧:众所周知,圆弧的绘制参一是外接矩形的坐标参数,既然是灰色圆的圆弧,那么这个外接矩形肯定就是灰色圆的外接矩形,坐标参数为 (-mIR, mIR, mIR, -mIR),STROKE 宽度应为蓝绿圆弧最外半径减去黄弧最外半径。
  • 绘制黄色圆弧外围的蓝绿色圆弧:原理同上,那么该外接矩形肯定就是黄色圆弧的外接矩形,也就是黄色圆的外接矩形,坐标参数为 (-mMR, mMR, mMR, -mMR),STROKE 宽度等同上。

理想绘制结果如下:

这里写图片描述

实际绘制结果如下:

请注意,我们的灰圆半径是等于 STROKE 的宽度的,所以是黄弧最外半径的一半,但是这里很明显的可以看出,灰圆的半径远不及黄弧最外半径的的一半。那么问题出在了那里呢?

我们不妨将黄弧的颜色改成半透明的试试,也就是将 mPaint.setColor(ContextCompat.getColor(context, R.color.colorInnerCircle)); 改成 mPaint.setColor(Color.argb(80, 254, 196, 0));。运行结果如下:

我们可以看到共有四种颜色出现在了我们的自定义 View 上,分别为蓝绿色、半透明黄色、深黄色、灰色。这里莫名其妙多出来的深黄色实际上是半透明黄色灰色叠加的效果,不仅如此,我们仔细观察一下的话,可以发现半透明黄色宽度与深黄色宽度相等,当然,这并不是偶然,而是必然。此时问题就一目了然了,黄弧与灰圆有重叠的部分导致了灰圆的许多部分被遮住了。那么问题出在了哪里,下面跟随笔者来解决——

Style 坑析

我们从头开始理一遍绘制这两个图形的思路 ——

1.绘制灰色内圆
2.绘制黄弧(绘制弧形实际上就是绘制一个圆形的外围,所以绘制弧形所需要的参数是这个圆形的外接圆)

绘制黄弧的时候,笔者所传入的外接圆参数实际上就是灰色内圆的外接圆参数,因为笔者起初认为 STROKE 所绘制的东西都在圆外 ——

换句话说,笔者认为 FILL_AND_STROKE = FILL + STROKE,而实际上并不是这样的,实际上 Android 会将 STROKE 的宽度均分两半,一半绘制在圆外,一半绘制在圆内 ——

这里写图片描述

也就是说实际上 FILL_AND_STROKE ≠ FILL + STROKE 或者说 FILL_AND_STROKE = FILL + STROKE / 2。问题解决到这里了,那么实际上解决方法就是将黄弧的外接圆的值再添上 STROKE 宽度 / 2 ,当然,蓝绿圆的外接圆的值也同样的要添上 STROKE 宽度 / 2,修改后代码如下:

@Override
protected void onDraw(Canvas canvas) {
    // 黄弧最外半径
    mMR = w * 4 / 21;
    // 灰圆半径
    mIR = w * 2 / 21;
    // stoke 宽度
    int width = mMR - mIR;

    // 移动画布
    canvas.translate(mCenterX, mCenterY);

    // 灰圆
    mPaint.setColor(ContextCompat.getColor(context, R.color.colorCenter));
    canvas.drawCircle(0, 0, mIR, mPaint);

    // 黄弧
    mPaint.setColor(ContextCompat.getColor(context, R.color.colorInnerCircle));
    mPaint.setStrokeWidth(width);
    mPaint.setStyle(Paint.Style.STROKE);
    canvas.drawArc(new RectF(-(mIR + width / 2), (mIR + width / 2), (mIR + width / 2), -(mIR + width
            / 2)), 0, 360, false, mPaint);

    // 蓝绿弧
    mPaint.setColor(ContextCompat.getColor(context, R.color.colorOuterCircle));
    mPaint.setStrokeWidth(width);
    mPaint.setStyle(Paint.Style.STROKE);
    canvas.drawArc(new RectF(-(mMR + width / 2), (mMR + width / 2), (mMR + width / 2), -(mMR + width
            / 2)), 0, 360, false, mPaint);
}

运行结果如下:

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值