1.Canvas
Canvas指画布,表现在屏幕上就是一块区域,可以在上面使用各种API绘制想要的东西。
canvas内部维持了一个mutable Bitmap,所以它可以使用颜色值去填充整个Bitmap,此外canvas也可以使用画笔去填充整个Bitmap。这两种填充方式都会受限于clip的范围。
canvas虽然内部保持了一个Bitmap,但是它本身并不代表那个Bitmap,而更像是一个图层。我们对这个图层的平移、旋转和缩放等操作并不影响内部的Bitmap,仅仅是改变了该图层相对于内部Bitmap的坐标位置、比例和方向而已。
在Android中,获得Canvas对象主要有三种方法:
①继承View,并重写onDraw()方法。View的Canvas对象会被当做参数传递过来,在这个Canvas上进行的操作会直接反映在View中。
②调用SurfaceHolder.lockCanvas()返回一个Canvas对象。
③通过构造方法创建一个Canvas对象。
Bitmap bitmap = Bitmap.createBitmap(100f, 100f, Config.ARGB_8888); //得到一个Bitmap对象,也可以使用别的方式得到。但是要注意,该bitmap一定要是mutable(异变的)
Canvas canvas = new Canvas(bitmap);
Canvas的坐标系:
画布以左上角为原点(0,0),向右为X轴的正方向,向下为Y轴的正方向:
Canvas的绘图操作:
绘制颜色 drawColor、drawRGB、drawARGB
绘制圆 drawCircle
绘制点 drawPoint
绘制直线 drawLine
绘制矩形 drawRect
绘制圆角矩形 drawRoundRect
绘制椭圆 drawOval
绘制弧形 drawArc
绘制文本 drawText
沿Path路径绘制文本 drawTextOnPath
绘制位图 drawBitmap
使用canvas.drawXXX时,系统会在一个新的透明区域绘制内容,然后迅速与屏幕当前显示内容进行重叠,这个重叠的过程也会受xfermode或blendmode的影响。
2.PorterDuffXfermode
android.graphics.PorterDuffXfermode类继承自android.graphics.Xfermode。在Android中用Canvas进行绘图时,可以通过使用PorterDuffXfermode将所绘制的图形的像素与Canvas中对应位置的像素按照一定规则进行混合,形成新的像素值,从而更新Canvas中最终的像素颜色值,这样会创建很多有趣的效果。当使用PorterDuffXfermode时,需要将其作为参数传给Paint.setXfermode(Xfermode xfermode)方法,这样在用该画笔paint进行绘图时,Android就会使用传入的PorterDuffXfermode,如果不想再使用Xfermode,那么可以执行Paint.setXfermode(null)。
举个例子:
①不使用Xfermode:
@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(Color.YELLOW);
canvas.drawCircle(r, r, r, paint);
//绘制蓝色的矩形
paint.setColor(Color.BLUE);
canvas.drawRect(r, r, r * 2.7f, r * 2.7f, paint);
}
重写View的onDraw方法,首先将View的背景色设置为绿色,然后绘制了一个黄色的圆形,然后再绘制一个蓝色的矩形,效果如下所示:
上面演示就是Canvas正常的绘图流程,没有使用PorterDuffXfermode。简单分析一下上面这段代码:
1)首先调用了canvas.drawARGB(255, 139, 197, 186)方法将整个Canvas都绘制成一个颜色,在执行完这句代码后,canvas上所有像素的颜色值的ARGB颜色都是(255,139,197,186),由于像素的alpha分量是255而不是0,所以此时所有像素都不透明。
2)当执行了canvas.drawCircle(r, r, r, paint)之后,Android会在所画圆的位置用黄颜色的画笔绘制一个黄色的圆形,此时整个圆形内部所有的像素颜色值的ARGB颜色都是0xFFFFFF00(YELLOW),然后用这些黄色的像素替换掉Canvas中对应的同一位置中颜色值ARGB为(255,139,197,186)的像素,这样就将黄色圆形绘制到Canvas上了。
3)当执行了canvas.drawRect(r, r, r * 2.7f, r * 2.7f, paint)之后,Android会在所画矩形的位置用蓝色的画笔绘制一个蓝色的矩形,此时整个矩形内部所有的像素颜色值的ARGB颜色都是0xFF0000FF(BLUE),然后用这些蓝色的像素替换掉Canvas中对应的同一位置中的像素,这样黄色的圆中的右下角部分的像素与其他一些背景色像素就被蓝色像素替换了,这样就将蓝色矩形绘制到Canvas上了。
这个过程虽然简单,但是了解Canvas绘图时具体的像素更新过程是真正理解PorterDuffXfermode的工作原理的基础。
②接下来,使用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(Color.YELLOW);
canvas.drawCircle(r, r, r, paint);
//使用CLEAR作为PorterDuffXfermode绘制蓝色的矩形
paint.setXfermode(new PorterDuffXfermode( PorterDuff.Mode.CLEAR));
paint.setColor(Color.BLUE);
canvas.drawRect(r, r, r * 2.7f, r * 2.7f, paint);
//最后将画笔去除Xfermode
paint.setXfermode(null);
}
最终效果还取决于是否关闭了硬件加速,因为PorterDuff.Mode.CLEAR不支持硬件加速:
//关闭硬件加速
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
关闭硬件加速的效果:
不关闭硬件加速的效果:
同样对以上代码进行一下分析:
1)首先调用了canvas.drawARGB(255, 139, 197, 186)方法将整个Canvas都绘制成一个颜色,此时所有像素都不透明。
2)然后通过调用canvas.drawCircle(r, r, r, paint)绘制了一个黄色的圆形到Canvas上面。
3)然后执行代码paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)),将画笔的PorterDuff模式设置为CLEAR。
4)然后调用canvas.drawRect(r, r, r * 2.7f, r * 2.7f, paint)方法绘制蓝色的矩形,但是最终界面上出现了一个白色/黑色的矩形。
5)在绘制完成后,调用paint.setXfermode(null)将画笔去除Xfermode。
具体分析一下白色/黑色矩形出现的原因:一般在调用canvas.drawXXX()方法时都会传入一个画笔Paint对象,Android在绘图时会先检查该画笔Paint对象有没有设置Xfermode,如果没有设置Xfermode,那么直接将绘制的图形覆盖Canvas对应位置原有的像素;如果设置了Xfermode,那么会按照Xfermode具体的规则来更新Canvas中对应位置的像素颜色。就本例来说,在执行canvas.drawCirlce()方法时,画笔Paint没有设置Xfermode对象,所以绘制的黄色圆形直接覆盖了Canvas上的像素。当调用canvas.drawRect()绘制矩形时,画笔Paint已经设置Xfermode的值为PorterDuff.Mode.CLEAR,此时Android首先是在内存中绘制了这么一个矩形,所绘制的图形中的像素称作源像素(source,简称src),所绘制的矩形在Canvas中对应位置的矩形内的像素称作目标像素(destination,简称dst)。源像素的ARGB四个分量会和Canvas上同一位置处的目标像素的ARGB四个分量按照Xfermode定义的规则进行计算,形成最终的ARGB值,然后用该最终的AR