文章摘要
混合是模式是Android自定义View中的重要的一个环节,也是最难掌握的一个特性。本文将分以下三个内容来介绍:第一章简要介绍用处,第二章介绍简单的代码实现,第三章将详细介绍各个模式的作用,第四章将介绍使用过程中的一些注意点(坑点)。
第一章 例子
## 以前吃饭的时候,一个同事拿出了刚买的华为荣耀手机,指着上面的时钟说,彬帅,你看这动效不错吧。然后问我有什么思路,当时我看了一下,说了一种最简单的方案——使用蒙版。这个动效最核心的就是刻度的起伏,我给出的方案可以用图2简单的解释。
详情可以前往这里:https://github.com/Ajian-studio/GAHonorClock 查看,此外还有其他两个实现方案。
第二章 代码实现
上面的为例,怎么用代码实现蒙版的效果?
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 保存图层
int layerCount = canvas.saveLayer(mClockViewRectF, mPaint, Canvas.ALL_SAVE_FLAG);
mPaint.setColor(mClockScaleLineColor);
// 绘制等长度的时钟刻度线
float clockScaleLineStartY = mAdjustClockScaleLineStartX + mClockViewRectF.top;
float clockScaleLineEndY = clockScaleLineStartY + mClockScaleLineHeight;
for (int i = 0; i < DEFAULT_TOTAL_CLOCK_SCALE_LINE_NUM; i++) {
canvas.drawLine(mClockViewCenterX, clockScaleLineStartY,
mClockViewCenterX, clockScaleLineEndY, mPaint);
canvas.rotate(ANGLE_PER_SCALE, mClockViewCenterX, mClockViewCenterY);
}
// 设置混合模式
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
// 旋转蒙版,实现移动的效果
canvas.rotate(mNowClockAngle, mClockViewCenterX, mClockViewCenterY);
// 绘制蒙版,切除外边缘和蒙版重叠的部分刻度线
canvas.drawBitmap(mClockMaskBitmap, null, mClockViewRectF, mPaint);
// 关闭混合模式
mPaint.setXfermode(null);
// 其他无关操作
mPaint.setColor(mClockPointColor);
canvas.drawCircle(mClockPointCenterX, mClockPointCenterY, mClockPointRadius, mPaint);
// 恢复图层
canvas.restoreToCount(layerCount);
updateTimeText(canvas);
}
将上面的代码提炼一下,混合模式的实现简化代码如下:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 保存图层
int layerCount = canvas.saveLayer(mClockViewRectF, mPaint, Canvas.ALL_SAVE_FLAG);
// 绘制 DST 图像
canvas draw dst img
// 设置混合模式
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
// 旋转蒙版,实现移动的效果
canvas.rotate(mNowClockAngle, mClockViewCenterX, mClockViewCenterY);
// 绘制 SRC 图像
canvas draw src img
// 关闭混合模式
mPaint.setXfermode(null);
// 恢复图层
canvas.restoreToCount(layerCount);
}
注: 混合模式中,DST 和 SRC 区别如下:
DST,是指开启混合模式之前的绘制内容, 也就是先前的内容;
SRC, 是指当开启混合模式之后绘制的内容,也就是后绘制的内容。
第三章 混合模式介绍
在Android的源码 android.graphics.PorterDuff.java 中如下的源码:
public enum Mode {
/** [0, 0] */
CLEAR (0),
/** [Sa, Sc] */
SRC (1),
/** [Da, Dc] */
DST (2),
/** [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] */
SRC_OVER (3),
/** [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc] */
DST_OVER (4),
/** [Sa * Da, Sc * Da] */
SRC_IN (5),
/** [Sa * Da, Sa * Dc] */
DST_IN (6),
/** [Sa * (1 - Da), Sc * (1 - Da)] */
SRC_OUT (7),
/** [Da * (1 - Sa), Dc * (1 - Sa)] */
DST_OUT (8),
/** [Da, Sc * Da + (1 - Sa) * Dc] */
SRC_ATOP (9),
/** [Sa, Sa * Dc + Sc * (1 - Da)] */
DST_ATOP (10),
/** [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] */
XOR (11),
/** [Sa + Da - Sa*Da,
Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] */
DARKEN (16),
/** [Sa + Da - Sa*Da,
Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] */
LIGHTEN (17),
/** [Sa * Da, Sc * Dc] */
MULTIPLY (13),
/** [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] */
SCREEN (14),
/** Saturate(S + D) */
ADD (12),
OVERLAY (15);
Mode(int nativeInt) {
this.nativeInt = nativeInt;
}
}
上面的代码中的注释,解释了各个模式的最终的颜色的组成。但是看代码不直观,那么就看看下面这种 google 官方的图。
第四章 混合模式实际的坑点
大致坑点有两个:
1)硬件加速在个别机型上会影响最终的结果
2) 谷歌官方Demo的图有一定的误导
下面将就 谷歌官方Demo 做一个说明。官方的图的颜色比较淡,为了更好的说明,将替换更鲜艳的颜色,如下图:
但是我们直接试验的会得到如下的图6中右边,而得不到图4或图5的样子。
为什么会出现这样的偏差呢?这个问题不是我一个会遇到吧?应该会有很多人遇到。
通过上图7,左边为官方demo的绘制模式,虽然可见区域仅仅是蓝色方块,但是实际绘制的范围为红色框框出来的;而右图是我们实际操作过程中出现的,绘制范围仅仅是蓝色方块的范围。
这里我们得出一个结论:
混合模式发生作用的范围是,实际绘制的区域(不管相应的区域是否为可见),而不在绘制区域将不会受到任何影响。
此外现在都是讲的是不透明的情况下,那么透明情况如何?请看下图。
总结
到此为止,本文已经结束了。本文的示例代码可以在这里 https://github.com/linxuebin1990/TestMixedMode查看。