您的位置:首页 > 移动开发 > Android开发

安卓Canvas

2017-05-18 16:46 169 查看
首先canvas的drawXXX方法就不去细说了,本文只针对canvas状态的变换、保存和复位。

Canvas与View

这里要理解的是Canvas和View的关系,Canvas就是附在View的整个表面的(注意,View并不相当于整个屏幕,View有自己的大小,因此下面所有的图形都是基于View而不是整个屏幕)



Canvas理解:Canvas内部有Matrix和Clip的状态记录,当调用一次translate、rotate、skew、scale,Canvas都会让内部的矩阵Matrix做相应的变换(平移变换、旋转、斜切、缩放),当调用canvas.clipXXX方法时,会改变Canvas内部的Clip的状态。如果在canvas内部的Matrix或Clip的状态发生了改变之后,再调用canvas.drawXXX方法时就会将你要绘制的图形进行相应的Matrix转换。而Canvas还有save方法和restore方法,其实Canvas内部有一个私有的栈,用来存储Matrix和Clip的状态的,当你调用一次save,栈中就会插入一个当前的Matrix和Clip状态,调用restore就会弹出栈顶的Matrix和Clip状态。下面会用通俗点的方式来理解canvas的这些方法。

Canvas的translate、rotate、skew、scale方法

我们先要知道画布与图层的关系,我们可以这么理解,canvas就是View本身,canvas并不会平移、缩放等变换,当canvas每调用一次translate、rotate、skew、scale方法的时候,都会创建一个新的图层,进行变换的只是图层的平移、缩放等,之后的draw方法都会在这个新的图层上绘画。

1、translate方法

使用:

canvas.translate(float dx, float dy);//x轴平移dx,y轴平移dy。

理解:

当canvas调用一次translate方法时,会在平移后的位置创建一个图层,之后的draw方法都在这个图层上绘画,并按照此图层的坐标系。

例:

@Override
protected void onDraw(Canvas canvas) {
//onDraw中创建对象,仅做演示
Paint paint = createPaint(Color.BLUE);//创建一个蓝色的画笔
Rect rect = new Rect(0, 0, 200, 200);//创建一个长宽都是200的矩形,左上角的坐标为(0,0)
canvas.drawRect(rect, paint);//画矩形
canvas.translate(100,100);//按x轴平移100,y轴平移100
canvas.drawRect(rect, paint);//再画矩形
canvas.drawRect(new Rect(10,10,150,150), paint);//再画矩形
}

/**
* 创建一个画笔
*/
private Paint createPaint(int color) {
Paint paint = new Paint();
paint.setColor(color);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
return paint;
}


运行结果(这里我让该View占满整个屏幕的):





2、 rotate方法

使用:

canvas.rotate(float degrees);//以当前图层的原点为旋转中心旋转degrees角度。

canvas.rotate(float degrees, float px, float py);//以当前图层的(px,py)的点为旋转中心旋转degrees角度。

理解:

当canvas调用一次rotate方法时,会在旋转后的位置创建一个图层。之后的draw方法都在这个图层上绘画,并按照此图层的坐标系。

例:

@Override
protected void onDraw(Canvas canvas) {
//onDraw中创建对象,仅做演示
Rect rect = new Rect(0, 0, 200, 200);//创建一个长宽都是200的矩形,左上角的坐标为(0,0)
canvas.drawRect(rect, createPaint(Color.BLUE));//画矩形
canvas.rotate(30);//以图层原点旋转30°
canvas.drawRect(rect, createPaint(Color.RED));//再画矩形
canvas.drawRect(new Rect(10, 10, 150, 150), createPaint(Color.RED));//再画矩形
}

/**
* 创建一个画笔
*/
private Paint createPaint(int color) {
Paint paint = new Paint();
paint.setColor(color);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
return paint;
}






这次的旋转中心不是原图层的原点,而是原图层的坐标点(100,100);

@Override
protected void onDraw(Canvas canvas) {
//onDraw中创建对象,仅做演示
Rect rect = new Rect(0, 0, 200, 200);//创建一个长宽都是200的矩形,左上角的坐标为(0,0)
canvas.drawRect(rect, createPaint(Color.BLUE));//画矩形
canvas.rotate(30, 100, 100);//以原图层的点(100,100)为旋转中心旋转30°
canvas.drawRect(rect, createPaint(Color.RED));//再画矩形
canvas.drawRect(new Rect(10, 10, 150, 150), createPaint(Color.RED));//再画矩形
}

/**
* 创建一个画笔
*/
private Paint createPaint(int color) {
Paint paint = new Paint();
paint.setColor(color);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
return paint;
}


效果图:





3、skew方法

使用:

canvas.skew(float sx, float sy);//图层错切,其中sx表示y轴倾斜x轴的错切角度的正切值,sy表示x轴倾斜y轴的错切角度的正切值。

理解:

当canvas调用一次skew方法时,会在错切后的位置创建一个图层。之后的draw方法都在这个图层上绘画,并按照此错切图层的坐标系(注意,该坐标系已经不是直角坐标系了)。

例:

@Override
protected void onDraw(Canvas canvas) {
//onDraw中创建对象,仅做演示
Rect rect = new Rect(0, 0, 200, 200);//创建一个长宽都是200的矩形,左上角的坐标为(0,0)
canvas.drawRect(rect, createPaint(Color.BLUE));//画矩形
canvas.skew(1, 0f);//参数为倾斜角度的tan值(这里x传的tan(45°)=1,因此y轴向x轴倾斜45°)
canvas.drawRect(rect, createPaint(Color.RED));//再画矩形
canvas.drawRect(new Rect(10, 10, 150, 150), createPaint(Color.RED));//再画矩形
}

/**
* 创建一个画笔
*/
private Paint createPaint(int color) {
Paint paint = new Paint();
paint.setColor(color);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
return paint;
}


效果图:





当然,你也可以给sy赋一个值,canvas.skew(1, 0.5f);就表示y轴向x轴偏移45°,同时x轴也向y轴偏移一个角度,此时新创建的图层和坐标系为:





4、scale方法

使用:

canvas.scale(float sx, float sy);//比例放缩,其中sx表示x轴方向上的数值放缩比例(所有数值大小的放缩),sy表示y轴方向上的数值放缩比例。

理解:

当canvas调用一次scale方法时,会按照横纵坐标刻度的缩放比例来创建一个新的图层。之后的draw方法都在这个图层上绘画,并按照此缩放的图层的坐标系。

注意,此时的放缩指的其实是图层的坐标刻度被缩放了,因此后面的draw方法参数中关于x、y大小的数值都会被缩放影响,以及后面的canvas.translate等关于x、y大小的数值都会被缩放比例影响,但是canvas.rotate旋转不会被影响,因为旋转的是角度,而不是x、y大小的数值(因此先调用scale再调用translate,和先调用translate再调用scale,得到的效果是不同的)

例:

@Override
protected void onDraw(Canvas canvas) {
//onDraw中创建对象,仅做演示
canvas.drawRect(new Rect(0, 0, 100, 100), createPaint(Color.BLUE));//画矩形
canvas.scale(0.5f, 0.5f);//缩放比例为1/2,即坐标刻度的长度变为原来的1/2
canvas.drawRect(new Rect(0, 0, 100, 100), createPaint(Color.RED));//再画矩形
canvas.drawRect(new Rect(0, 0, 400, 400), createPaint(Color.RED));//再画矩形
}

/**
* 创建一个画笔
*/
private Paint createPaint(int color) {
Paint paint = new Paint();
paint.setColor(color);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
return paint;
}


效果图:



分析图:

在没有缩放之前所画的矩形为左边图形,缩放后画了两个矩形为右侧的图形



合在一起(红色坐标刻度表示缩放后的图层坐标刻度):



这里讲一下scale还有一个四个参数的方法:

scale(float sx, float sy, float px, float py);

它和scale(float sx, float sy)有什么区别呢?源码如下:

public final void scale(float sx, float sy, float px, float py) {
translate(px, py);
scale(sx, sy);
translate(-px, -py);
}


其实就是在未放缩之前先平移了px,py,接着放缩sx,sy,然后再向回平移px,py(注意,这里先平移px,py与缩放之后的平移-px,-py不能抵消,因为平移-px,-py是在缩放后的坐标刻度去做的平移

总结:

1、canvas的四个变换方法translate、rotate、skew、scale都会新建一个“图层”,只要执行一次变换,那之后的drawXXX方法都会在最新的图层上去绘画,scale方法放缩的是坐标刻度长度。

2、scale方法放缩的是坐标刻度长度,坐标刻度长度的缩放不仅会对drawXXX方法有影响,同时只要是关于x,y长度大小的都会受到影响,比如在scale之后再调用translate(dx,dy)方法,这是的dx,dy都是缩放后的坐标刻度长度。

为什么我会把画布和图层的概念分开呢?其实canvas有一个方法canvas.drawColor(int color);用来填充背景色。

看一段代码:

@Override
protected void onDraw(Canvas canvas) {
//onDraw中创建对象,仅做演示
canvas.drawColor(Color.BLACK);
canvas.drawRect(new Rect(0, 0, 100, 100), createPaint(Color.BLUE));//画矩形
canvas.translate(100, 100);
//        canvas.drawColor(Color.GREEN);
canvas.drawRect(new Rect(0, 0, 100, 100), createPaint(Color.RED));//再画矩形
}


先注释掉平移之后再给canvas填充颜色的代码,效果图为:



打开注释代码:

@Override
protected void onDraw(Canvas canvas) {
//onDraw中创建对象,仅做演示
canvas.drawColor(Color.BLACK);
canvas.drawRect(new Rect(0, 0, 100, 100), createPaint(Color.BLUE));//画矩形
canvas.translate(100, 100);
canvas.drawColor(Color.GREEN);
canvas.drawRect(new Rect(0, 0, 100, 100), createPaint(Color.RED));//再画矩形
}


效果图:



这里有两个地方很奇怪:

1、在translate之后调用drawColor(Color.GREEN),绘制的背景并没有跟着偏移,而仅仅是图层偏移了(导致之后的drawRect偏移了),这就是图层和画布的不同导致的,canvas.drawColor是给整个画布填充颜色,而不是填充图层,画布不会进行变换,变换的是图层。

2、在translate之后调用drawColor(Color.GREEN)之后,前面绘制的背景和绘制的矩形都被背景遮住了,这个其实好理解,就是绘制是按照从下到上的顺序的。背景没有透明度,因此之前绘制的都会被新绘制的背景遮盖。

Canvas的clip方法

上面所说的都是变换的方法,它们的实质都是改变的Matrix的状态来做的矩阵变换。Canvas除了Matrix状态之外还有Clip状态。canvas有一系列的clipRect方法,字面意思是裁剪,当调用一次clipRect方法之后,就表示裁剪一块区域出来作为新的画布,并且canvas会改变了自己的Clip状态。

其实canvas.clipRect方法很简单,就是裁剪画布的一块区域来绘画。此时裁剪的是画布,而不单单是图层。

例如:

@Override
protected void onDraw(Canvas canvas) {
//onDraw中创建对象,仅做演示
canvas.clipRect(100, 100, 200, 200);
canvas.drawColor(Color.GREEN);
canvas.drawRect(new Rect(150, 150, 180, 180), createPaint(Color.RED));//在新画布上画矩形
}




如果裁剪出来的画布很小,然后调用的drawRect方法画的矩形很大,超出了裁剪后的画布会怎么样呢?答案是,如果绘制超出了裁剪之后的画布,那么就不会去绘制它了。(其实不光只是针对裁剪后的画布,只要是画布,只要是绘制超出了画布,就不会显示)

@Override
protected void onDraw(Canvas canvas) {
//onDraw中创建对象,仅做演示
canvas.clipRect(100, 100, 200, 200);
canvas.drawColor(Color.GREEN);
canvas.drawRect(new Rect(150, 150, 300, 300), createPaint(Color.RED));//再画矩形
}




Canvas的save与restore方法

以上面的通俗的意思去理解就是:

当canvas调用一次translate、rotate、skew、scale方法时,就会重新去创建一个图层,当调用一次clipRect方法时,同样也会创建一个和裁剪后的画布同样大小的图层,这些图层就类似于Photoshop里面的图层,最终会叠加在一起,按照从下到上的层级去显示绘制的内容。

在Canvas中,有一个私有的栈去储存这些图层,当你调用一次canvas.save()方法时,canvas就会向栈中放入当前的图层,依次调用canvas.save()就会向栈中依次放入图层,canvas.save()方法就相当于进栈。相反的canvas.restore(),会弹出栈顶的图层出来,调用一次restore方法,就会弹出一个图层,之后的drawXXX方法就会在最新弹出的图层上去绘制。

看个例子:

@Override
protected void onDraw(Canvas canvas) {
//onDraw中创建对象,仅做演示
canvas.translate(100, 100);
canvas.drawRect(new Rect(0, 0, 50, 50), createPaint(Color.RED));
canvas.save();//此时栈中保存了translate(100, 100)平移之后的图层
canvas.rotate(45);
canvas.drawRect(new Rect(0, 0, 100, 100), createPaint(Color.BLUE));
canvas.restore();//将栈中保存在栈顶的图层释放出来(栈顶就是translate(100, 100)的图层)
//当前的图层为translate(100, 100)的图层
canvas.drawRect(new Rect(0, 0, 150, 150), createPaint(Color.GREEN));
}


效果图:



以上代码canvas只创建了两个新的图层,第一个是“translate(100,100)”的图层,第二个是“rotate(45)”的图层,但是canvas.save()保存的是当前的图层,由于canvas.save()是在“translate(100,100)”的图层上存储的,所以此时只会把“translate(100,100)”图层放入栈中,而第二个“rotate(45)”的图层,并没有放入到栈中,因此后面的canvas.restore()弹出的就是“translate(100,100)”的图层,最后的矩形就是在“translate(100,100)”的图层上绘制的。

分析图:



栈:

代码中canvas.save()进行的是将“translate(100,100)”放入栈中



后面的canvas.restore()就是将栈顶的图层弹出栈



再来看一个稍微复杂点的:

@Override
protected void onDraw(Canvas canvas) {
//onDraw中创建对象,仅做演示
canvas.translate(100, 100);
canvas.save();//此时栈中保存了translate(100, 100)平移之后的图层
canvas.drawRect(new Rect(0, 0, 50, 50), createPaint(Color.RED));
canvas.rotate(45);
canvas.save();//此时栈中保存了rotate(45)旋转之后的图层
canvas.drawRect(new Rect(0, 0, 100, 100), createPaint(Color.BLUE));
canvas.restore();//将栈中保存在栈顶的图层“rotate(45)图层”释放出来)
canvas.restore();//再次调用restore,将此时栈顶的“translate(100, 100)图层”释放出来)
//当前的图层为translate(100, 100)的图层
canvas.drawRect(new Rect(0, 0, 150, 150), createPaint(Color.GREEN));
}


效果图:



此时执行的顺序为(分析图):



上面都是用“图层”的概念来代替Matrix和Clip状态的,canvas的原理还是在于变换与裁剪的画布状态,保存与复位也是针对于状态的栈。

canvas除了canvas.save()还有canvas.save(int saveFlags);

其中的saveFlags允许传入:

Canvas.CLIP_SAVE_FLAG:只保存Clip状态

Canvas.MATRIX_SAVE_FLAG:只保存Matrix状态

Canvas.ALL_SAVE_FLAG:Matrix和Clip状态都保存

还有只保存画布部分区域的状态等。

canvas.save()方法会返回一个int值,该值类似表示该图层在栈中存的下标值。

可以通过canvas.restoreToCount(int saveCount);传入来复位到指定的图层(即一次弹出多个图层出栈,直接复位到指定的图层)

例如:

@Override
protected void onDraw(Canvas canvas) {
//onDraw中创建对象,仅做演示
canvas.translate(100, 100);
canvas.save();//此时栈中保存了translate(100, 100)平移之后的图层
canvas.drawRect(new Rect(0, 0, 50, 50), createPaint(Color.RED));
canvas.rotate(45);
int rotateCount = canvas.save();//此时栈中保存了rotate(45)旋转之后的图层
canvas.drawRect(new Rect(0, 0, 100, 100), createPaint(Color.BLUE));
canvas.translate(200, 0);
canvas.save();//向栈中保存一个图层
canvas.drawRect(new Rect(0, 0, 100, 100), createPaint(Color.GRAY));
canvas.translate(200, 0);
canvas.save();//向栈中保存一个图层
canvas.drawRect(new Rect(0, 0, 100, 100), createPaint(Color.BLACK));

canvas.restoreToCount(rotateCount);//直接复位到rotate(45)的图层
//当前的图层为translate(100, 100)的图层
canvas.drawRect(new Rect(0, 0, 150, 150), createPaint(Color.YELLOW));
}


效果图:



分析图就不画了!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android canvas matrix 图形