今天做一个自定义控件,给TV应用的item设置背景,但是UI给的背景图套在item背后,会有黑边在item内部,当item图片没显示出来的时候,会显示出这个背景图,
因为item是很多大小不一的,但都用了同一个背景,这个背景是个.9图,而我希望在不修改图的前提下,使得item显示效果都是一样的,即只显示item边以外的部分,item边内的自然不可见,于是只能在代码中实现,刚开始想到了canvas.clipRect(),后来发现不太好搞,它是圈住了个区域,在区域内绘图,与我需求不符,于是又想到了setXfermode这个强大的API,其中的混合模式正好符合需求,说干就干。
经过查阅相关资料,确定了如下简单代码:
/…这是在onDraw()里…/
canvas.drawBitmap(shadowImg,null,dstRect,null);
canvas.drawRoundRect(tmpRect,10,10,bitmapPaint);
bitmapPaint.setXfermode(xfermode);
canvas.drawRoundRect(tmpRect,10,10, bitmapPaint);
/……/
其中dstRect是目标显示区域,tmpRect是内部需要裁剪的区域,xfermode是我在外部定义好的:
xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT);
这个Mode很关键,SRC_OUT指的是保留非重叠的部分,而SRC_IN保留的是两个图重叠的部分,也就是交集。这个试一下就理解了。更多模式,参考百度的资料吧,太多了,介绍的也很详细,我这里只强调一个需要注意的地方。
由于我的代码涉及到onDraw重复执行,即获取焦点时绘制一次,失去焦点时再次绘制,而这时候,上面的代码会出现问题,就是第一次显示正常,内部阴影正常裁剪掉了,但是第二次获取焦点时,被裁剪掉的阴影又回来了,之后就一直都在显示。经查阅资料,原来这个涉及到canvas绘制的原理。而解决方法就是:
int sc = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null, Canvas.ALL_SAVE_FLAG);
canvas.drawBitmap(shadowImg,null,dstRect,null);
canvas.drawRoundRect(tmpRect,10,10,bitmapPaint);
bitmapPaint.setXfermode(xfermode);
canvas.drawRoundRect(tmpRect,10,10, bitmapPaint);
//还原
bitmapPaint.setXfermode(null);
canvas.restoreToCount(sc);
savelayer意思就是新建一个图层,之后的操作都在这个图层上进行,然后走完流程后,关键点是记得将xfermode设为null,也就是去掉,savelayer 和restoreToCount()测试发现可有可无,不过出于科学敲代码,我们还是养成好习惯吧。最后实现的效果如下:
参考文章:https://blog.youkuaiyun.com/u013085697/article/details/52096703