android Graphics(四):canvas变换与操作

前言:前几篇讲解了有关canvas绘图的一些操作,今天更深入一些,讲讲对画布的操作,这篇文章不像前几篇那么容易理解,如果以前没有接触过画布的童鞋可能比较难以理解,为什么会这样。我尽量多画图,让大家更清晰明白。

前几天偶然看到一篇文章,写的朴实无华,充满正能量,我非常喜欢里面的一句话,很像我现在的状态,分享给大家。

人生最纠结的事情不是你甘于平淡,而是你明明不希望平凡却不知道未来应该怎么办。 ----摘自《三十岁那年,我的梦想是年薪十万

相关文章:

1、《android Graphics(一):概述及基本几何图形绘制》
2、《android Graphics(二):路径及文字》
3、《android Graphics(三):区域(Range)》
4、《android Graphics(四):canvas变换与操作》

一、平移(translate)

canvas中有一个函数translate()是用来实现画布平移的,画布的原状是以左上角为原点,向左是X轴正方向,向下是Y轴正方向,如下图所示

translate函数其实实现的相当于平移坐标系,即平移坐标系的原点的位置。translate()函数的原型如下:

void translate(float dx, float dy)

参数说明:
float dx:水平方向平移的距离,正数指向正方向(向右)平移的量,负数为向负方向(向左)平移的量
flaot dy:垂直方向平移的距离,正数指向正方向(向下)平移的量,负数为向负方向(向上)平移的量

[java] view plaincopy

  1. protected void onDraw(Canvas canvas) {
  2. // TODO Auto-generated method stub
  3. super.onDraw(canvas);
  4. //translate  平移,即改变坐标系原点位置
  5. Paint paint = new Paint();
  6. paint.setColor(Color.GREEN);
  7. paint.setStyle(Style.FILL);
  8. //  canvas.translate(100, 100);
  9. Rect rect1 = new Rect(0,0,400,220);
  10. canvas.drawRect(rect1, paint);
  11. }

1、上面这段代码,先把canvas.translate(100, 100);注释掉,看原来矩形的位置,然后打开注释,看平移后的位置,对比如下图:

未平移                                                                            平移后

  

二、屏幕显示与Canvas的关系

很多童鞋一直以为显示所画东西的改屏幕就是Canvas,其实这是一个非常错误的理解,比如下面我们这段代码:

这段代码中,同一个矩形,在画布平移前画一次,平移后再画一次,大家会觉得结果会怎样?

[java] view plaincopy

  1. protected void onDraw(Canvas canvas) {
  2. // TODO Auto-generated method stub
  3. super.onDraw(canvas);
  4. //构造两个画笔,一个红色,一个绿色
  5. Paint paint_green = generatePaint(Color.GREEN, Style.STROKE, 3);
  6. Paint paint_red   = generatePaint(Color.RED, Style.STROKE, 3);
  7. //构造一个矩形
  8. Rect rect1 = new Rect(0,0,400,220);
  9. //在平移画布前用绿色画下边框
  10. canvas.drawRect(rect1, paint_green);
  11. //平移画布后,再用红色边框重新画下这个矩形
  12. canvas.translate(100, 100);
  13. canvas.drawRect(rect1, paint_red);
  14. }
  15. private Paint generatePaint(int color,Paint.Style style,int width)
  16. {
  17. Paint paint = new Paint();
  18. paint.setColor(color);
  19. paint.setStyle(style);
  20. paint.setStrokeWidth(width);
  21. return paint;
  22. }

代码分析:
这段代码中,对于同一个矩形,在平移画布前利用绿色画下矩形边框,在平移后,再用红色画下矩形边框。大家是不是会觉得这两个边框会重合?实际结果是这样的。

从到这个结果大家可能会狠蛋疼,我第一次看到这个结果的时候蛋都碎一地了要。淡定……
这个结果的关键问题在于,为什么绿色框并没有移动?

这是由于屏幕显示与Canvas根本不是一个概念!Canvas是一个很虚幻的概念,相当于一个透明图层(用过PS的同学应该都知道),每次Canvas画图时(即调用Draw系列函数),都会产生一个透明图层,然后在这个图层上画图,画完之后覆盖在屏幕上显示。所以上面的两个结果是由下面几个步骤形成的:

1、调用canvas.drawRect(rect1, paint_green);时,产生一个Canvas透明图层,由于当时还没有对坐标系平移,所以坐标原点是(0,0);再在系统在Canvas上画好之后,覆盖到屏幕上显示出来,过程如下图:

2、然后再第二次调用canvas.drawRect(rect1, paint_red);时,又会重新产生一个全新的Canvas画布,但此时画布坐标已经改变了,即向右和向下分别移动了100像素,所以此时的绘图方式为:(合成视图,从上往下看的合成方式)

上图展示了,上层的Canvas图层与底部的屏幕的合成过程,由于Canvas画布已经平移了100像素,所以在画图时是以新原点来产生视图的,然后合成到屏幕上,这就是我们上面最终看到的结果了。我们看到屏幕移动之后,有一部分超出了屏幕的范围,那超出范围的图像显不显示呢,当然不显示了!也就是说,Canvas上虽然能画上,但超出了屏幕的范围,是不会显示的。当然,我们这里也没有超出显示范围,两框框而已。

下面对上面的知识做一下总结:
1、每次调用canvas.drawXXXX系列函数来绘图进,都会产生一个全新的Canvas画布。
2、如果在DrawXXX前,调用平移、旋转等函数来对Canvas进行了操作,那么这个操作是不可逆的!每次产生的画布的最新位置都是这些操作后的位置。(关于Save()、Restore()的画布可逆问题的画布再讲)
3、在Canvas与屏幕合成时,超出屏幕范围的图像是不会显示出来的。

三、旋转(Rotate)

画布的旋转是默认是围绕坐标原点来旋转的,这里容易产生错觉,看起来觉得是图片旋转了,其实我们旋转的是画布,以后在此画布上画的东西显示出来的时候全部看起来都是旋转的。其实Roate函数有两个构造函数:

void rotate(float degrees)
void rotate (float degrees, float px, float py)

第一个构造函数直接输入旋转的度数,正数是顺时针旋转,负数指逆时针旋转,它的旋转中心点是原点(0,0)
第二个构造函数除了度数以外,还可以指定旋转的中心点坐标(px,py)

下面以第一个构造函数为例,旋转一个矩形,先画出未旋转前的图形,然后再画出旋转后的图形;

[java] view plaincopy

  1. protected void onDraw(Canvas canvas) {
  2. // TODO Auto-generated method stub
  3. super.onDraw(canvas);
  4. Paint paint_green = generatePaint(Color.GREEN, Style.FILL, 5);
  5. Paint paint_red   = generatePaint(Color.RED, Style.STROKE, 5);
  6. Rect rect1 = new Rect(300,10,500,100);
  7. canvas.drawRect(rect1, paint_red); //画出原轮廓
  8. canvas.rotate(30);//顺时针旋转画布
  9. canvas.drawRect(rect1, paint_green);//画出旋转后的矩形
  10. }

效果图是这样的:


这个最终屏幕显示的构造过程是这样的:

下图显示的是第一次画图合成过程,此时仅仅调用canvas.drawRect(rect1, paint_red); 画出原轮廓

然后是先将Canvas正方向依原点旋转30度,然后再与上面的屏幕合成,最后显示出我们的复合效果。

有关Canvas与屏幕的合成关系我觉得我已经讲的够详细了,后面的几个操作Canvas的函数,我就不再一一讲它的合成过程了。

四、缩放(scale )

public void scale (float sx, float sy) 
public final void scale (float sx, float sy, float px, float py)

其实我也没弄懂第二个构造函数是怎么使用的,我就先讲讲第一个构造函数的参数吧
float sx:水平方向伸缩的比例,假设原坐标轴的比例为n,不变时为1,在变更的X轴密度为n*sx;所以,sx为小数为缩小,sx为整数为放大
float sy:垂直方向伸缩的比例,同样,小数为缩小,整数为放大

注意:这里有X、Y轴的密度的改变,显示到图形上就会正好相同,比如X轴缩小,那么显示的图形也会缩小。一样的。

[java] view plaincopy

  1. protected void onDraw(Canvas canvas) {
  2. // TODO Auto-generated method stub
  3. super.onDraw(canvas);
  4. //  //scale 缩放坐标系密度
  5. Paint paint_green = generatePaint(Color.GREEN, Style.STROKE, 5);
  6. Paint paint_red   = generatePaint(Color.RED, Style.STROKE, 5);
  7. Rect rect1 = new Rect(10,10,200,100);
  8. canvas.drawRect(rect1, paint_green);
  9. canvas.scale(0.5f, 1);
  10. canvas.drawRect(rect1, paint_red);
  11. }

五、扭曲(skew)

其实我觉得译成斜切更合适,在PS中的这个功能就差不多叫斜切。但这里还是直译吧,大家都是这个名字。看下它的构造函数:
void skew (float sx, float sy)

参数说明:
float sx:将画布在x方向上倾斜相应的角度,sx倾斜角度的tan值,
float sy:将画布在y轴方向上倾斜相应的角度,sy为倾斜角度的tan值,

注意,这里全是倾斜角度的tan值哦,比如我们打算在X轴方向上倾斜60度,tan60=根号3,小数对应1.732

[java] view plaincopy

  1. protected void onDraw(Canvas canvas) {
  2. // TODO Auto-generated method stub
  3. super.onDraw(canvas);
  4. //skew 扭曲
  5. Paint paint_green = generatePaint(Color.GREEN, Style.STROKE, 5);
  6. Paint paint_red   = generatePaint(Color.RED, Style.STROKE, 5);
  7. Rect rect1 = new Rect(10,10,200,100);
  8. canvas.drawRect(rect1, paint_green);
  9. canvas.skew(1.732f,0);//X轴倾斜60度,Y轴不变
  10. canvas.drawRect(rect1, paint_red);
  11. }

五、裁剪画布(clip系列函数)

裁剪画布是利用Clip系列函数,通过与Rect、Path、Region取交、并、差等集合运算来获得最新的画布形状。除了调用Save、Restore函数以外,这个操作是不可逆的,一但Canvas画布被裁剪,就不能再被恢复!
Clip系列函数如下:
boolean clipPath(Path path)
boolean clipPath(Path path, Region.Op op)
boolean clipRect(Rect rect, Region.Op op)
boolean clipRect(RectF rect, Region.Op op)
boolean clipRect(int left, int top, int right, int bottom)
boolean clipRect(float left, float top, float right, float bottom)
boolean clipRect(RectF rect)
boolean clipRect(float left, float top, float right, float bottom, Region.Op op)
boolean clipRect(Rect rect)
boolean clipRegion(Region region)
boolean clipRegion(Region region, Region.Op op)

以上就是根据Rect、Path、Region来取得最新画布的函数,难度都不大,就不再一一讲述。利用ClipRect()来稍微一讲。

[java] view plaincopy

  1. protected void onDraw(Canvas canvas) {
  2. // TODO Auto-generated method stub
  3. super.onDraw(canvas);
  4. canvas.drawColor(Color.RED);
  5. canvas.clipRect(new Rect(100, 100, 200, 200));
  6. canvas.drawColor(Color.GREEN);
  7. }

先把背景色整个涂成红色。显示在屏幕上
然后裁切画布,最后最新的画布整个涂成绿色。可见绿色部分,只有一小块,而不再是整个屏幕了。
关于两个画布与屏幕合成,我就不再画图了,跟上面的合成过程是一样的。

六、画布的保存与恢复(save()、restore())

前面我们讲的所有对画布的操作都是不可逆的,这会造成很多麻烦,比如,我们为了实现一些效果不得不对画布进行操作,但操作完了,画布状态也改变了,这会严重影响到后面的画图操作。如果我们能对画布的大小和状态(旋转角度、扭曲等)进行实时保存和恢复就最好了。
这小节就给大家讲讲画布的保存与恢复相关的函数——Save()、Restore()。
int save ()
void restore()

这两个函数没有任何的参数,很简单。
Save():每次调用Save()函数,都会把当前的画布的状态进行保存,然后放入特定的栈中;
restore():每当调用Restore()函数,就会把栈中最顶层的画布状态取出来,并按照这个状态恢复当前的画布,并在这个画布上做画。
为了更清晰的显示这两个函数的作用,下面举个例子:

[java] view plaincopy

  1. protected void onDraw(Canvas canvas) {
  2. // TODO Auto-generated method stub
  3. super.onDraw(canvas);
  4. canvas.drawColor(Color.RED);
  5. //保存当前画布大小即整屏
  6. canvas.save();
  7. canvas.clipRect(new Rect(100, 100, 800, 800));
  8. canvas.drawColor(Color.GREEN);
  9. //恢复整屏画布
  10. canvas.restore();
  11. canvas.drawColor(Color.BLUE);
  12. }

他图像的合成过程为:(最终显示为全屏幕蓝色)

下面我通过一个多次利用Save()、Restore()来讲述有关保存Canvas画布状态的栈的概念:代码如下:

[java] view plaincopy

  1. protected void onDraw(Canvas canvas) {
  2. // TODO Auto-generated method stub
  3. super.onDraw(canvas);
  4. canvas.drawColor(Color.RED);
  5. //保存的画布大小为全屏幕大小
  6. canvas.save();
  7. canvas.clipRect(new Rect(100, 100, 800, 800));
  8. canvas.drawColor(Color.GREEN);
  9. //保存画布大小为Rect(100, 100, 800, 800)
  10. canvas.save();
  11. canvas.clipRect(new Rect(200, 200, 700, 700));
  12. canvas.drawColor(Color.BLUE);
  13. //保存画布大小为Rect(200, 200, 700, 700)
  14. canvas.save();
  15. canvas.clipRect(new Rect(300, 300, 600, 600));
  16. canvas.drawColor(Color.BLACK);
  17. //保存画布大小为Rect(300, 300, 600, 600)
  18. canvas.save();
  19. canvas.clipRect(new Rect(400, 400, 500, 500));
  20. canvas.drawColor(Color.WHITE);
  21. }

显示效果为:

在这段代码中,总共调用了四次Save操作。上面提到过,每调用一次Save()操作就会将当前的画布状态保存到栈中,所以这四次Save()所保存的状态的栈的状态如下:


注意在,第四次Save()之后,我们还对画布进行了canvas.clipRect(new Rect(400, 400, 500, 500));操作,并将当前画布画成白色背景。也就是上图中最小块的白色部分,是最后的当前的画布。

如果,现在使用Restor(),会怎样呢,会把栈顶的画布取出来,当做当前画布的画图,试一下:

[java] view plaincopy

  1. protected void onDraw(Canvas canvas) {
  2. // TODO Auto-generated method stub
  3. super.onDraw(canvas);
  4. canvas.drawColor(Color.RED);
  5. //保存的画布大小为全屏幕大小
  6. canvas.save();
  7. canvas.clipRect(new Rect(100, 100, 800, 800));
  8. canvas.drawColor(Color.GREEN);
  9. //保存画布大小为Rect(100, 100, 800, 800)
  10. canvas.save();
  11. canvas.clipRect(new Rect(200, 200, 700, 700));
  12. canvas.drawColor(Color.BLUE);
  13. //保存画布大小为Rect(200, 200, 700, 700)
  14. canvas.save();
  15. canvas.clipRect(new Rect(300, 300, 600, 600));
  16. canvas.drawColor(Color.BLACK);
  17. //保存画布大小为Rect(300, 300, 600, 600)
  18. canvas.save();
  19. canvas.clipRect(new Rect(400, 400, 500, 500));
  20. canvas.drawColor(Color.WHITE);
  21. //将栈顶的画布状态取出来,作为当前画布,并画成黄色背景
  22. canvas.restore();
  23. canvas.drawColor(Color.YELLOW);
  24. }

上段代码中,把栈顶的画布状态取出来,作为当前画布,然后把当前画布的背景色填充为黄色

那如果我连续Restore()三次,会怎样呢?
我们先分析一下,然后再看效果:Restore()三次的话,会连续出栈三次,然后把第三次出来的Canvas状态当做当前画布,也就是Rect(100, 100, 800, 800),所以如下代码:

[java] view plaincopy

  1. protected void onDraw(Canvas canvas) {
  2. // TODO Auto-generated method stub
  3. super.onDraw(canvas);
  4. canvas.drawColor(Color.RED);
  5. //保存的画布大小为全屏幕大小
  6. canvas.save();
  7. canvas.clipRect(new Rect(100, 100, 800, 800));
  8. canvas.drawColor(Color.GREEN);
  9. //保存画布大小为Rect(100, 100, 800, 800)
  10. canvas.save();
  11. canvas.clipRect(new Rect(200, 200, 700, 700));
  12. canvas.drawColor(Color.BLUE);
  13. //保存画布大小为Rect(200, 200, 700, 700)
  14. canvas.save();
  15. canvas.clipRect(new Rect(300, 300, 600, 600));
  16. canvas.drawColor(Color.BLACK);
  17. //保存画布大小为Rect(300, 300, 600, 600)
  18. canvas.save();
  19. canvas.clipRect(new Rect(400, 400, 500, 500));
  20. canvas.drawColor(Color.WHITE);
  21. //连续出栈三次,将最后一次出栈的Canvas状态作为当前画布,并画成黄色背景
  22. canvas.restore();
  23. canvas.restore();
  24. canvas.restore();
  25. canvas.drawColor(Color.YELLOW);
  26. }

结果为:

OK啦,这篇就到了啦。

扩展阅读:《Android 2D Graphics学习(二)、Canvas篇1、Canvas基本使用》

本文所涉及的代码集合,下载地址为:http://download.csdn.net/detail/harvic880925/7884883

请大家尊重原创者版权,转载请标明出处:http://blog.csdn.net/harvic880925/article/details/39080931 谢谢!

时间: 2024-10-12 04:03:51

android Graphics(四):canvas变换与操作的相关文章

【转】android Graphics(四):canvas变换与操作

android Graphics(四):canvas变换与操作 分类: 5.andriod开发2014-09-05 15:05 5877人阅读 评论(18) 收藏 举报 目录(?)[+] 前言:前几篇讲解了有关canvas绘图的一些操作,今天更深入一些,讲讲对画布的操作,这篇文章不像前几篇那么容易理解,如果以前没有接触过画布的童鞋可能比较难以理解,为什么会这样.我尽量多画图,让大家更清晰明白. 前几天偶然看到一篇文章,写的朴实无华,充满正能量,我非常喜欢里面的一句话,很像我现在的状态,分享给大家

android Graphics(三):区域(Range)

前言:最近几天对画图的研究有些缓慢,项目开始写代码了,只能在晚上空闲的时候捯饬一下自己的东西,今天给大家讲讲区域的相关知识,已经想好后面两篇的内容了,这几天有时间赶紧写出来给大家.有关界面开发的东东内容确实比较多,慢慢来吧,总有一天会不一样. 我自己的一句警言,送给大家: 想要跟别人不一样,你就要跟别人不一样.----- Harvic 相关文章: 1.<android Graphics(一):概述及基本几何图形绘制>2.<android Graphics(二):路径及文字>3.&l

android Graphics(二):路径及文字

前言:今天项目进入攻关期,他们改Bug要改疯掉了,主管为了激励大家,给大家发了一封邮件,讲到他对项目和学习的理解,一个很好的图形模型,分享给大家,如图在下面给出:(不便给出原文,我仅做转述)无论是学习还是其它回报,它的回报曲线如下 :蓝色是(成长+付出),红色是回报.有多久可以达到这个红心,要看我们自已的努力,付出了多少专注与汗水.红色线的上挑,是前期厚积薄发的过程,先有异常低调的学习和努力,才会有海阔天空的心态和能量. 相关文章: 1.<android Graphics(一):概述及基本几何图

Android Graphics专题(1)--- Canvas基础

作为Android Graphics专题的开篇,毫无疑问,我们将讨论Android UI技术的核心概念--Canvas. Canvas是Android UI框架的基础,在Android的控件体系中,所有容器类.控件类在实现上都依赖于Canvas,界面的绘制实质上都是Canvas绘制的.本文将讨论Canvs的由来,并通过实例展示Canvas的基础用法. 对于应用开发而言,我们可以不去深究Canvas与Android 控件体系的实现细节,但明白Canvas与控件的关联有助于我们更好的使用Canvas

Android中android.graphics下面的绘制图形类Canvas,Paint,Bitmap,Drawable

1.概念区别: 很多网友刚刚开始学习Android平台,对于Drawable.Bitmap.Canvas和Paint它们之间的概念不是很清楚, 其实它们除了Drawable外早在Sun的J2ME中就已经出现了,但是在Android平台中,Bitmap.Canvas相关的都有所变化. 首先让我们理解下Android平台中的显示类是View,但是还提供了底层图形类android.graphics,今天所说的这些均为graphics底层图形接口. Bitmap - 称作位图,一般位图的文件格式后缀为b

Android开发--利用Matrix进行图片操作

今天和大家分享一下Android中Matrix的简单用法,Matrix其实就是一个3*3的矩阵,利用这个矩阵对图像操作.在Android中,为我们提供一些封装好的方法可以进行一些简单的图像操作,总共分为rotate(旋转),scale(缩放),translate(平移)和skew(倾斜)四种,每一种变换都提供了set, post和pre三种操作方式,除了translate,其他三种操作都可以指定中心点.其中post的方式是对原矩阵进行后乘,pre方式是对原矩阵进行前乘,另外,每一次通过set的方

android.graphics.Matrix

Matrix类包含了一个3x3的矩阵用来改变坐标,它没有一个构造器来初始化它里边的内容,所以创建实例后需要调用reset()方法生成一个标准matrix,或者调用set..一类的函数,比如setTranslate, setRotate,,该函数将会决定matrix如何来改变坐标.SDK里边没有讲述Matrix的3x3矩阵是如何改变点的坐标值的,但是我在代码里边通过打印那9个点的值时,大致可以得到如下结论,9个值[a,b,c,d,e,f,g,h,i],坐标[x,y],当g=0,h=0,i=1,的时

android.graphics包中的一些类的使用

游戏编程相关参考 Matrix学习系列: http://www.moandroid.com/?p=1781 Android画图学习总结系列: http://www.moandroid.com/?p=764 游戏开发系列(opengl es基础知识): http://www.moandroid.com/?p=1730 线性代数(包含矩阵的相关知识): http://dl.iteye.com/topics/download/b56a388a-3408-3179-972b-3a72bdbaaa28 俄

Android第四期 - 单侧滑动效果

Android的设置或者登陆或者其他的一些主窗体要展示的功能需要用到sliding的效果,下面就叫大家怎么做.直接上代码!! MainActivity部分: package net.ting.sliding; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.