忙里偷闲,增进一下自己的做UI功底,最近进行一段时间学习自定义View。
在Android中的Canvas进行绘图时,可以通过使用PorterDuffXfermode将所绘制的图形的像素与Canvas中对应位置的像素按照一定规则进行混合,形成新的像素值,从而更新Canvas中最终的像素颜色值。
PorterDuffXfermode支持以下十几种像素颜色的混合模式,分别为:
CLEAR、SRC、DST、SRC_OVER、DST_OVER、SRC_IN、DST_IN、SRC_OUT、DST_OUT、SRC_ATOP、DST_ATOP、XOR、DARKEN、LIGHTEN、MULTIPLY、SCREEN。
1.重写了View的onDraw方法,首先将View的背景色设置为绿色,然后绘制了一个黄色的圆形,然后再绘制一个蓝色的矩形:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//设置背景色
canvas.drawARGB(255, 139, 197, 186);
int canvasWidth = canvas.getWidth();
int r = canvasWidth / 3;
//绘制黄色的圆形
paint.setColor(0xFFFFCC44);
canvas.drawCircle(r, r, r, paint);
//绘制蓝色的矩形
paint.setColor(0xFF66AAFF);
canvas.drawRect(r, r, r * 2.7f, r * 2.7f, paint);
}
效果图:
2.使用PorterDuffXfermode对上面的代码进行一下修改:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//设置背景色
canvas.drawARGB(255, 139, 197, 186);
int canvasWidth = canvas.getWidth();
int r = canvasWidth / 3;
//正常绘制黄色的圆形
paint.setColor(0xFFFFCC44);
canvas.drawCircle(r, r, r, paint);
//使用CLEAR作为PorterDuffXfermode绘制蓝色的矩形
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
paint.setColor(0xFF66AAFF);
canvas.drawRect(r, r, r * 2.7f, r * 2.7f, paint);
//最后将画笔去除Xfermode
paint.setXfermode(null);
}
效果图:
以上代码分析:
- 首先,我们调用了canvas.drawARGB(255, 139, 197, 186)方法将整个Canvas都绘制成一个颜色,此时所有像素都不透明。
- 然后我们通过调用canvas.drawCircle(r, r, r, paint)绘制了一个黄色的圆形到Canvas上面。
- 然后我们执行代码paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)),将画笔的PorterDuff模式设置为CLEAR。
- 然后调用canvas.drawRect(r, r, r * 2.7f, r * 2.7f, paint)方法绘制蓝色的矩形,但是最终界面上出现了一个白色的矩形。
- 在绘制完成后,我们调用paint.setXfermode(null)将画笔去除Xfermode。
当调用canvas.drawXXX()方法时传入一个画笔Paint对象,在绘图时会先检查该画笔Paint对象有没有设置Xfermode。若没有设置Xfermode,那么直接将绘制的图形覆盖Canvas对应位置原有的像素;如果设置了Xfermode,那么会按照Xfermode具体的规则来更新Canvas中对应位置的像素颜色。本例中的Xfermode是PorterDuff.Mode.CLEAR,通过canvas.drawRect()在Canvas上绘制了一个透明的矩形,由于Activity本身屏幕的背景时白色的,所以此处就显示了一个白色的矩形。
3.将绘制圆形和绘制矩形相关的代码放到canvas.saveLayer()和canvas.restoreToCount()之间。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//设置背景色
canvas.drawARGB(255, 139, 197, 186);
int canvasWidth = canvas.getWidth();
int canvasHeight = canvas.getHeight();
//在默认layer的上部新建一个layer
int layerId = canvas.saveLayer(0, 0, canvasWidth, canvasHeight, null, Canvas.ALL_SAVE_FLAG);
int r = canvasWidth / 3;
//正常绘制黄色的圆形
paint.setColor(0xFFFFCC44);
canvas.drawCircle(r, r, r, paint);
//使用CLEAR作为PorterDuffXfermode绘制蓝色的矩形
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
paint.setColor(0xFF66AAFF);
canvas.drawRect(r, r, r * 2.7f, r * 2.7f, paint);
//最后将画笔去除Xfermode
paint.setXfermode(null);
//新的layer绘制到canvas默认的layer上去
canvas.restoreToCount(layerId);
}
效果图:
以上代码分析:
- 首先,调用canvas.drawARGB()方法将整个Canvas都绘制成一个颜色,此时所有像素都不透明。
- 然后我们将主要的代码都放到了canvas.saveLayer()以及canvas.restoreToCount()之间。
canvas支持图层layer渲染,调用canvas的各种drawXXX()方法,都是绘制到canvas默认的layer上面。通过canvas.saveLayer()新建一个layer放置在默认layer的上部。执行canvas.saveLayer()后,所有绘制操作都在新建的layer上,且新的layer是完全透明的。新的layer绘制完成后调用canvas.restoreToCount(layer)或者canvas.restore()把这个layer绘制到canvas默认的layer上去。
不同混合模式的计算规则:
像素颜色四个分量:ARGB,A表示Alpha值,RGB表示颜色。
用S代表源像素,源像素的颜色值可表示为[Sa, Sc],Sa表示源像素的Alpha值,Sc表示源像素的RGB。
用D代表目标像素,目标像素的颜色值可表示为[Da, Dc],Da表示目标像素的Alpha值,Dc表示目标像素的RGB。
源像素与目标像素在不同混合模式下计算颜色的规则:
- CLEAR:[0, 0]
- SRC:[Sa, Sc]
- DST:[Da, Dc]
- SRC_OVER:[Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc]
- DST_OVER:[Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc]
- SRC_IN:[Sa * Da, Sc * Da]
- DST_IN:[Sa * Da, Sa * Dc]
- SRC_OUT:[Sa * (1 - Da), Sc * (1 - Da)]
- DST_OUT:[Da * (1 - Sa), Dc * (1 - Sa)]
- SRC_ATOP:[Da, Sc * Da + (1 - Sa) * Dc]
- DST_ATOP:[Sa, Sa * Dc + Sc * (1 - Da)]
- XOR:[Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc]
- DARKEN:[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)]
- LIGHTEN:[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)]
- MULTIPLY:[Sa * Da, Sc * Dc]
- SCREEN:[Sa + Da - Sa * Da, Sc + Dc - Sc * Dc]
- ADD:Saturate(S + D)
- OVERLAY:Saturate(S + D)
效果图:
注:DARKEN、LIGHTEN、OVERLAY等几种混合规则在GPU硬件加速下不起效。
若混合模式没有正确使用,可以调用:
//View禁用掉GPU硬件加速,切换到软件渲染模式
View.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
这样混合模式就能正常使用。