这个标题口气是真大,没办法,讲解这个的博客太多了,而且好多有误导性,有些自己都不知道为啥,还写博客,抄来抄去的。当然了,很多人写的还是很好的,但是感觉还是没有理解PorterDuffXfermode的用法以及为啥和官方demo的不一样的原因,所以写了该博客。
主要参考博客以及反驳博客是
http://blog.youkuaiyun.com/iispring/article/details/50472485,
http://blog.youkuaiyun.com/wingichoy/article/details/50534175。
通过该篇博客你可以学到
porterduffXfermode到底是怎么一回事,怎么样写才会保证不出错
当然预备知识是对自定义view的简单认识。
那么,接下来我们开始说问题。
网上官方demo真的有错吗,为什么我们写出来的跟demo不一样?
A:确实有错,但是错误在CLEAR,Daeken,Lighten,而不是错在设置SRC后,官方只显示了正方形而我们却显示正方形和圆形。
先看一下网上流传的图:
再看一下我运行的最新代码 安卓6.0自带demo,api版本是19;
运行平台,夜神模拟器,安卓版本4.4,在荣耀6 安卓6.0版本上运行结果一致。所以截取了模拟器截图。
可以看到后续谷歌已经修改过这个demo了,显示效果确实不一样的。我估计是网上的版本是2.3之前的版本了吧。但是博客写完后又没人后续维护,导致大家人云亦云,说白了就是互相抄来抄去,瞎扯淡。
有人在看到最新的demo的Clear属性后,大舒一口气,这不是和我写的demo
一样了吗,我设置了Clear属性后就是这么显示的,哈哈,洋洋飒飒几行代码,一运行,嗯,一致,很开心。
int sc = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null, Canvas.ALL_SAVE_FLAG);//增加一个缓冲层
canvas.drawCircle(100, 100, 50, mDSTpaint);
mSrcBluePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawRect(100, 100, 200, 200, mSrcBluePaint);
mSrcBluePaint.setXfermode(null);
canvas.restoreToCount(sc);
可是在设置了SRC属性后,显示为这个效果了,懵逼了。
哎哟我去,不是显示上部吗,怎么连下层的圆也显示出来了,和官方不一致啊,可是明明第一个正确的,为啥呢?
于是,第一篇博客诞生了。http://blog.youkuaiyun.com/iispring/article/details/50472485,被大家认知为神作~
并贴了所谓自己正确的贴图
当然这幅图对吗,确实是对的,(除了Daeken,Lighten,这个并没有产生相交部位的加深和变浅,官方也是这样,难道和api版本有关系?)。
有些人可能懵逼了,明明好多和官方不一样,为什么这篇也是对的,你的道理在哪里?为什么会有两种不同的版本,还都是对的?
这篇博客的博主出发点是好的,就是为了解疑为什么我们自己写的代码和官方不一致。但是他和初学者都犯了同一个错误,就是“你以为的以为就是你以为的以为吗?”哈哈,有点绕口。说白了,就是没有解释到点上。
总结一下初学者可能犯的最大错误,也是造成自己写的和demo不一样的根本原因:
以为黄圆是DST ,蓝方是SRC..。错错错!(以官方demo来看)
虽然上面的贴图和我们自己运行代码一致,但是他还是没有走出这个错误,否则写出来的代码就和官方的一样啦~
安卓源码里是这么写的(可以忽视)
@Override protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
Paint labelP = new Paint(Paint.ANTI_ALIAS_FLAG);
labelP.setTextAlign(Paint.Align.CENTER);
Paint paint = new Paint();
paint.setFilterBitmap(false);
canvas.translate(15, 35);
int x = 0;
int y = 0;
for (int i = 0; i < sModes.length; i++) {
// draw the border
paint.setStyle(Paint.Style.STROKE);
paint.setShader(null);
canvas.drawRect(x - 0.5f, y - 0.5f,
x + W + 0.5f, y + H + 0.5f, paint);
// draw the checker-board pattern
paint.setStyle(Paint.Style.FILL);
paint.setShader(mBG);
canvas.drawRect(x, y, x + W, y + H, paint);
// draw the src/dst example into our offscreen bitmap
int sc = canvas.saveLayer(x, y, x + W, y + H, null,
Canvas.MATRIX_SAVE_FLAG |
Canvas.CLIP_SAVE_FLAG |
Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
Canvas.FULL_COLOR_LAYER_SAVE_FLAG |
Canvas.CLIP_TO_LAYER_SAVE_FLAG);
canvas.translate(x, y);
canvas.drawBitmap(mDstB, 0, 0, paint);
paint.setXfermode(sModes[i]);
canvas.drawBitmap(mSrcB, 0, 0, paint);
paint.setXfermode(null);
canvas.restoreToCount(sc);
// draw the label
canvas.drawText(sLabels[i],
x + W/2, y - labelP.getTextSize()/2, labelP);
x += W + 10;
// wrap around when we've drawn enough for one row
if ((i % ROW_MAX) == ROW_MAX - 1) {
x = 0;
y += H + 30;
}
}
注意这段代码:
canvas.drawBitmap(mDstB, 0, 0, paint);
paint.setXfermode(sModes[i]);
canvas.drawBitmap(mSrcB, 0, 0, paint);
paint.setXfermode(null);
再看看博客里及我们自己写的代码,通常都是这么写的:(引用自第一篇博客)
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//设置背景色
canvas.drawARGB(255, 139, 197, 186);
int canvasWidth = canvas.getWidth();
int canvasHeight = canvas.getHeight();
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);
canvas.restoreToCount(layerId);
}
注意这段代码:
canvas.drawCircle(r, r, r, paint);
//使用CLEAR作为PorterDuffXfermode绘制蓝色的矩形
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawRect(r, r, r * 2.7f, r * 2.7f, paint);
看到区别了吗?
放到一起对比一下:
//官方demo
canvas.drawBitmap(mDstB, 0, 0, paint);
paint.setXfermode(sModes[i]);
canvas.drawBitmap(mSrcB, 0, 0, paint);
paint.setXfermode(null);
//很多人的写法:
canvas.drawCircle(r, r, r, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawRect(r, r, r * 2.7f, r * 2.7f, paint);
这两段代码的区别就在与Xfermode的作用域。
官方的作用域在整个bitmap,而很多人的写法仅仅在于蓝色方形。。。注意,这是重点,重点!!!!!!!!!!!!!!
反驳观点1:官方在图的大小上做了手脚了吗?—( Xfermode的作用域)
很明显,没有!!!!但是为什么显示效果不一样?
第一篇博客认为是官方在图的大小上做了手脚,其实根本不是
官方的做法是先把图形画在bitmap上,然后再对bitmap做了相交绘制
而大多数人的做法只是把圆和方进行相交绘制。所以产生了所谓的正确的图。这幅图是正确的,因为作用域的原因,但是用这幅图来反驳官方的图,那就是错上加错了~
作用域是我自己创造的名词。
以SRC_IN为例,表示展示上方相交的图,也就一个蓝色扇形。安卓源码里,是将圆和方画在两个bitmap里,两个图一样大小,然后相交绘制,注意,相交区域是bitmap的大小,bitmap默认是透明的。
那么就很容易知道为什么源码只显示蓝色扇形。相交区域通过某种算法,透明色和底部圆DST后变成透明,蓝方与黄圆相交部分,显示上部相交部分,也就是显示蓝色扇形。
为什么我们写的还存在底部黄圆呢?
因为我们设置的Xfermode 作用域只在蓝方。蓝方与黄圆相交显示上部,展示扇形,没问题,但是仅此而已,没相交的地方Xfermode 没有效果,该咋展示就咋展示,于是产生了和官方不一样的效果。
其他同理。都能解释的通。。。。。。。。
反驳观点2:和硬件加速有关吗?应该没关系
新开一个layer层同样可以解决设置Clear属性后黑圆的效果,调用canvas.saveLayer方法即可。
所以和硬件加速有关不成立,建议以后所有的操作新开图层进行处理。
反驳观点3:必须两个bitmap吗?还必须大小一致吗?
不是的,没必要两个bitmap。一个就完全够了,继续以SRC_IN为例,
代码如下
//只有一个bitmap形式
private void test3(Canvas canvas) {
int sc = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null, Canvas.ALL_SAVE_FLAG);//增加一个缓冲层
canvas.drawCircle(100,100,50,mDSTpaint);
Bitmap bitmap = Bitmap.createBitmap(500, 500, Bitmap.Config.ARGB_8888);
Canvas ca = new Canvas(bitmap);
ca.drawRect(100, 100, 200, 200, mSrcBluePaint);
//注意一下作用域~
mSrcBluePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(bitmap, 0, 0, mSrcBluePaint);
mSrcBluePaint.setXfermode(null);
canvas.restoreToCount(sc);
}
可以看到至创建了一个bitmap,因为我们要的到的是蓝色扇形,我们的DST是圆,选用的模式是SRC_IN,只要保证画的蓝方和黄圆相交部位是个扇形即可,无所谓一个两个bitmap的,既然一个bitmap都能满足效果,那么就更没有必要要求两个必须一致了,毕竟另一个可以认为bitmap的大小是0 嘛~ 况且创建bitmap那么耗费内存~能省则省,看效果行事~
总结
1.源码是两个bitmap相交绘制的,不是黄圆和和蓝方,搞清楚这个你就不会画错了~
2.还是要多思考,源码比博客保险,不要人云亦云~
3.有些博客讲的都是么比~贴张demo图就去实现效果,虽然有的效果和预想的一样,但是我深刻怀疑是歪打正着而已