安卓Canvas
2017-05-18 16:46
169 查看
首先canvas的drawXXX方法就不去细说了,本文只针对canvas状态的变换、保存和复位。
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的这些方法。
1、translate方法
使用:
canvas.translate(float dx, float dy);//x轴平移dx,y轴平移dy。
理解:
当canvas调用一次translate方法时,会在平移后的位置创建一个图层,之后的draw方法都在这个图层上绘画,并按照此图层的坐标系。
例:
运行结果(这里我让该View占满整个屏幕的):
2、 rotate方法
使用:
canvas.rotate(float degrees);//以当前图层的原点为旋转中心旋转degrees角度。
canvas.rotate(float degrees, float px, float py);//以当前图层的(px,py)的点为旋转中心旋转degrees角度。
理解:
当canvas调用一次rotate方法时,会在旋转后的位置创建一个图层。之后的draw方法都在这个图层上绘画,并按照此图层的坐标系。
例:
这次的旋转中心不是原图层的原点,而是原图层的坐标点(100,100);
效果图:
3、skew方法
使用:
canvas.skew(float sx, float sy);//图层错切,其中sx表示y轴倾斜x轴的错切角度的正切值,sy表示x轴倾斜y轴的错切角度的正切值。
理解:
当canvas调用一次skew方法时,会在错切后的位置创建一个图层。之后的draw方法都在这个图层上绘画,并按照此错切图层的坐标系(注意,该坐标系已经不是直角坐标系了)。
例:
效果图:
当然,你也可以给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,得到的效果是不同的)
例:
效果图:
分析图:
在没有缩放之前所画的矩形为左边图形,缩放后画了两个矩形为右侧的图形
合在一起(红色坐标刻度表示缩放后的图层坐标刻度):
这里讲一下scale还有一个四个参数的方法:
scale(float sx, float sy, float px, float py);
它和scale(float sx, float sy)有什么区别呢?源码如下:
其实就是在未放缩之前先平移了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);用来填充背景色。
看一段代码:
先注释掉平移之后再给canvas填充颜色的代码,效果图为:
打开注释代码:
效果图:
这里有两个地方很奇怪:
1、在translate之后调用drawColor(Color.GREEN),绘制的背景并没有跟着偏移,而仅仅是图层偏移了(导致之后的drawRect偏移了),这就是图层和画布的不同导致的,canvas.drawColor是给整个画布填充颜色,而不是填充图层,画布不会进行变换,变换的是图层。
2、在translate之后调用drawColor(Color.GREEN)之后,前面绘制的背景和绘制的矩形都被背景遮住了,这个其实好理解,就是绘制是按照从下到上的顺序的。背景没有透明度,因此之前绘制的都会被新绘制的背景遮盖。
其实canvas.clipRect方法很简单,就是裁剪画布的一块区域来绘画。此时裁剪的是画布,而不单单是图层。
例如:
如果裁剪出来的画布很小,然后调用的drawRect方法画的矩形很大,超出了裁剪后的画布会怎么样呢?答案是,如果绘制超出了裁剪之后的画布,那么就不会去绘制它了。(其实不光只是针对裁剪后的画布,只要是画布,只要是绘制超出了画布,就不会显示)
当canvas调用一次translate、rotate、skew、scale方法时,就会重新去创建一个图层,当调用一次clipRect方法时,同样也会创建一个和裁剪后的画布同样大小的图层,这些图层就类似于Photoshop里面的图层,最终会叠加在一起,按照从下到上的层级去显示绘制的内容。
在Canvas中,有一个私有的栈去储存这些图层,当你调用一次canvas.save()方法时,canvas就会向栈中放入当前的图层,依次调用canvas.save()就会向栈中依次放入图层,canvas.save()方法就相当于进栈。相反的canvas.restore(),会弹出栈顶的图层出来,调用一次restore方法,就会弹出一个图层,之后的drawXXX方法就会在最新弹出的图层上去绘制。
看个例子:
效果图:
以上代码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()就是将栈顶的图层弹出栈
再来看一个稍微复杂点的:
效果图:
此时执行的顺序为(分析图):
上面都是用“图层”的概念来代替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);传入来复位到指定的图层(即一次弹出多个图层出栈,直接复位到指定的图层)
例如:
效果图:
分析图就不画了!
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)); }
效果图:
分析图就不画了!
相关文章推荐
- 安卓使用Canvas绘制工作日程表
- 安卓用canvas画曲线图
- animate cc在ios上点击canvas闪黑以及在安卓微信里巨卡的解决方案
- 安卓自定义View进阶-Canvas之绘制图形
- 安卓开发——Paint、Canvas、Matrix简单类比
- 安卓中Canvas使用方法
- 安卓使用Canvas画图分析
- 安卓canvas
- 安卓Canvas绘图之setXfermode一个需要注意的点
- 安卓自定义view_GDI绘图 _2d绘图_canvas绘图
- 安卓自定义View进阶-Canvas之绘制图形
- 安卓 canvas
- 用画布canvas画安卓logo
- 安卓自定义View进阶-Canvas之图片文字
- 安卓自定义View进阶-Canvas之绘制图形
- 安卓API指南——Canvas和Drawable
- 安卓中Canvas实现清屏效果
- 安卓使用Canvas绘制工作日程表
- 安卓开发应该知道的Drawable、Bitmap、Canvas和Paint的关系
- 安卓自定义View基础05-Canvas之基础图形绘制,点,线,矩形,圆,椭圆,弧形等