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

593

被折叠的 条评论
为什么被折叠?



