canvas变换
概述
要运用好canvas绘图,不仅需要使用前面说到的canvas的基本绘制,也必须使用好canvas的变换。Canvas的变换有 平移(translate),旋转(rotate),缩放(scale),错切(skew),裁剪(clip),保存(save),保存图层(saveLayer),恢复(restore),恢复到指定状态(restoreToCount)等。
以上的变换操作与屏幕的显示有一定关系,首先,想象屏幕是固定的,固定摆放,屏幕下方有一张很大的画布(超出了屏幕显示区域),但是我们只能看到屏幕对应区域的内容。
图中可以看到,红色边框框住的是canvas也就是画布,实际上,画布可以想象成无限大的,这里只是表明他比屏幕大,作为一个相对关系。黑色的框是显示屏幕。红线绘制的左边系,是以手机初始状态下的手机屏幕左上角为原点,也就是canvas还未做任何变换前,它的坐标原点和屏幕左上角重合。
注意,坐标是在cavas上的,屏幕只是一个显示区域,好比人通过屏幕来观察画布,屏幕只是限定了一个可见区域,并不保存任何内容,好比从飞机的车窗往外面看,车窗就是屏幕,外面就是画布。
那么如果canvas进行了平移变换,会发生什么,可以想到的是,如果canvas向右移动100,向下移动100,那么坐标轴会跟着移动,因为坐标在画布上。好比飞机又向左飞了100,向上飞了100。那么会变成如下情况:
如上预测一样,由于canvas无限大,所以还是超过屏幕大小,所以最终体现出来是坐标系的变动。这时候如果在坐标0点绘制一个点,那么这个点将不再是在屏幕左上方,因为坐标圆点已经移动了。
平移(translate)
void translate(float dx, float dy)
方法平移canvas,以下示例将canvas向右平移500像素,向下平移500像素,在坐标原点绘制一个矩形。看如下效果图:
红色矩形是平移后绘制的。代码如下
Rect rect = new Rect(0, 0, 100, 100); mPaint.setColor(Color.BLUE); canvas.drawRect(rect, mPaint); canvas.translate(500, 500); mPaint.setColor(Color.RED); canvas.drawRect(rect,mPaint);
旋转(rotate)
void rotate(float degrees) void rotate (float degrees, float px, float py)
旋转使用如上两种方法,第一个指定度数,第二个方法除了度数还可以指定对应的中心点。
蓝色是旋转之前,红色是旋转了30度以后的
代码如下:
Rect rect = new Rect(300, 0, 400, 100); mPaint.setColor(Color.BLUE); canvas.drawRect(rect, mPaint); canvas.rotate(30); mPaint.setColor(Color.RED); canvas.drawRect(rect,mPaint);
缩放(scale)
public void scale (float sx, float sy) public final void scale (float sx, float sy, float px, float py)
缩放的方法类似于旋转,也可以指定中心,以及x,y轴上的缩放比例,小于1大于0代表缩小,大于1代表放大
蓝色方块是未缩放的,红色是缩小之后绘制的。
代码如下:
Rect rect = new Rect(0, 0, 400, 400); mPaint.setColor(Color.BLUE); canvas.drawRect(rect, mPaint); canvas.scale(0.5f, 0.5f); mPaint.setColor(Color.RED); canvas.drawRect(rect, mPaint);
错切(skew)
错切是在某方向上,按照一定的比例对图形的每个点到某条平行于该方向的直线的有向距离做放缩得到的平面图形。水平错切(或平行于X轴的错切)是一个将任一点(x,y)映射到点(x+my,y)的操作,m是固定参数,称为错切因子
使用方法void skew (float sx, float sy)
sx和sy分就是错切因子,为倾斜角度tan值,这里倾斜45度值为1
蓝色为原始图,红色为水平错切以后的图形。
代码如下
mPaint.setStyle(Paint.Style.STROKE); Rect rect = new Rect(0, 0, 400, 400); mPaint.setColor(Color.BLUE); canvas.drawRect(rect, mPaint); canvas.skew(1f, 0); mPaint.setColor(Color.RED); canvas.drawRect(rect, mPaint);
裁剪(clip)
裁剪对应的方法很多,都是以clip开头的,传入的参数有path,region,rect等等,这里不一一列举。裁剪是将cavas上截取下来一块,进行绘制处理,其余的部分将不受影响。
图中看出,只有一小块区域被绘制成蓝色,其他地方没有改变。
Rect rect = new Rect(0, 0, 200, 200); canvas.save(); canvas.clipRect(rect); canvas.drawColor(Color.BLUE); canvas.restore();
由于使用了clip,裁剪了一个矩形区域,因此绘制成为蓝色的时候,矩形外部没有受影响。
保存和恢复
int save () void restore() void rest restoreToCount(int saveCount)
save和restore一般成对的出现,save可以保存canvas当前的状态,随后进行平移,裁剪等一系列改变canvas的操作,最后使用restore将canvas还原成save时候的状态。
上面两张图中,左边代表clip出一块小区域后,着色为蓝色,右边图形代表restore之后,着色红色,可见整个cavase都被着色为红色,说明canvas恢复到了裁剪之前的状态。
Rect rect = new Rect(0, 0, 200, 200); canvas.save(); canvas.clipRect(rect); canvas.drawColor(Color.BLUE); canvas.restore(); canvas.drawColor(Color.RED);
save方法返回一个int值,该值是这次保存的标志,这个值有什么作用呢,这个值可以作为restoreToCount方法的参数,从而指定canvas返回到某个指定的save状态。
saveLayer
saveLayer可以为canvas创建一个新的透明图层,在新的图层上绘制,并不会直接绘制到屏幕上,而会在restore之后,绘制到上一个图层或者屏幕上(如果没有上一个图层)。为什么会需要一个新的图层,例如在处理xfermode的时候,原canvas上的图(包括背景)会影响src和dst的合成,这个时候,使用一个新的透明图层是一个很好的选择。又例如需要当前绘制的图形都带有一定的透明度,那么创建一个带有透明度的图层,也是一个方便的选择。
当然,图层使用比较方便,但是代价昂贵,一个新的图层带来的可能不止多一倍的渲染,特别是图层比较大的时候,所以要谨慎使用。
上图中,绿色是canvas背景,dst是一个黄色圆形,src为一个蓝色正方形,xfermode为xor,可以看到,左边图形合成并不正常,原因就在于没有新开一个图层,而是直接在canvas上合成,受了背景颜色的影响,背景和dst这个黄色圆形一并被当作了dst,所以xor就成了这个图像,中间的正方形被除去了。
而右边的图中,新开了一个透明图层,因此,dst和src和合成没有受到其他干扰。合并之后正常显示。
代码如下:
canvas.drawColor(Color.GREEN); // canvas.saveLayer(0, 0, 1000, 1000, paint, Canvas.ALL_SAVE_FLAG); canvas.translate(x, y); canvas.drawBitmap(mDstB, 0, 0, paint); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR)); canvas.drawBitmap(mSrcB, 0, 0, paint); paint.setXfermode(null); canvas.restore();
其中左图没有打开注释代码,右图打开了注释代码。