Canvas所提供的各种方法根据功能来看大致可以分为几类:

第一是以drawXXX为主的绘制方法;

第二是以clipXXX为主的裁剪方法;

第三是以scale、skew、translate和rotate组成的Canvas变换方法;

最后一类则是以saveXXX和restoreXXX构成的画布锁定和还原;

还有一些其他的就不归类了。

如果用到的不能硬件加速的方法,请务必关闭硬件加速,否则可能会产生不正确的效果。

一、clipRect(…)

clipRect是将当前画布裁剪为一个矩形,以后画的图像仅仅只能在画在这块矩形区域中,超出的部分完全看不到的。可以说裁剪后的区域是我们以后画图的画布,而原来的画布已经不存在了。

下面的代码在画布没裁剪的时候绘制了蓝色,裁剪后绘制了红色。可见裁剪后的画布就仅剩下红色区域了。

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4. canvas.drawColor(Color.BLUE);
  5. canvas.clipRect(, , , );
  6. canvas.drawColor(Color.RED);
  7. }

上面我们用到了:

  1. clipRect(int left, int top, int right, int bottom)

相似的还有:

  1. clipRect(float left, float top, float right, float bottom)

一个int一个float。除此之外还有两个与之对应的方法,传入rect或rectf即可。

  1. clipRect(Rect rect)
  2. clipRect(RectF rect)

Rect和RectF是类似的,只不过RectF中涉及计算的时候数值类型均为float型,两者均表示一块规则矩形。

二、用Rect实现多区域裁剪

2.1 intersect

先来看一段代码:

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4. canvas.drawColor(Color.BLUE);
  5.  
  6. Rect rect = new Rect(, , , );
  7.  
  8. rect.intersect(, , , );
  9.  
  10. canvas.clipRect(rect);
  11. canvas.drawColor(Color.RED);
  12. }

结果:

PS:黄色线框为后期加上的辅助线非程序生成

其实intersect的作用跟我们之前学到的图形混合模式有点类似,它会取两个区域的相交区域作为最终区域,上面我们的第一个区域是在实例化Rect时确定的(0, 0, 500, 500),第二个区域是调用intersect方法时指定的(250, 250, 750, 750),这两个区域对应上图的两个黄色线框,两者相交的地方则为最终的红色区域。

它的计算方法是,先得到左上方块区域的右下角坐标,再得到下方方框的左上角坐标,两个点,四个坐标构成一个新的rect,然后再进行裁剪。

2.2 union

union方法与intersect相反,取的是相交区域最远的左上端点作为新区域的左上端点,而取最远的右下端点作为新区域的右下端点,比如:

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4. canvas.drawColor(Color.BLUE);
  5.  
  6. Rect rect = new Rect(, , , );
  7.  
  8. rect.union(, , , );
  9.  
  10. canvas.clipRect(rect);
  11. canvas.drawColor(Color.RED);
  12. }

计算方式:先得到左上矩阵的左上角,再得到下方矩阵的右下角,两个点,四个坐标构成一个新的rect。

三、clipPath(Path path)  

我们可以利用该方法从Canvas中“挖”取一块不规则的画布:

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4. mPaint.setColor(Color.RED);
  5. mPaint.setStyle(Paint.Style.STROKE);
  6.  
  7. // 实例化路径
  8. mPath = new Path();
  9. // 移动起点至[50,50]
  10. mPath.moveTo(50, 50);
  11. mPath.lineTo(75, 23);
  12. mPath.lineTo(150, 100);
  13. mPath.lineTo(80, 110);
  14. // 闭合路径
  15. mPath.close();
  16.  
  17. // 在原始画布上绘制蓝色
  18. canvas.drawColor(Color.BLUE);
  19. // 按照路径进行裁剪
  20. canvas.clipPath(mPath);
  21. // 在裁剪后剩余的画布上绘制红色
  22. canvas.drawColor(Color.RED);
  23. }

四、Region.Op

4.1 引入

回顾Canvas中有关裁剪的方法,你会发现有一大堆带有Region.Op参数的重载方法:

  1. clipPath(Path path, Region.Op op)
  2. clipRect(Rect rect, Region.Op op)
  3. clipRect(RectF rect, Region.Op op)
  4. clipRect(float left, float top, float right, float bottom, Region.Op op)
  5. clipRegion(Region region, Region.Op op)

要明白这些方法的Region.Op参数那么首先要了解Region为何物。Region的意思是“区域”,在Android里呢它同样表示的是一块封闭的区域,Region中的方法都非常的简单,我们重点来瞧瞧Region.Op,Op是Region的一个枚举类,里面呢有六个枚举常量:

那么Region.Op其实就是个组合模式。

4.2 用代码进行测试

我这里给出一个代码框架,大家可以自行替换粗体文字来看效果。记得关闭硬件加速哦~

  1. public class CanvasView extends View {
  2. private Region mRegionA, mRegionB;// 区域A和区域B对象
  3. private Paint mPaint;// 绘制边框的Paint
  4.  
  5. public CanvasView(Context context, AttributeSet attrs) {
  6. super(context, attrs);
  7.  
  8. // 实例化画笔并设置属性
  9. mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
  10. mPaint.setStyle(Paint.Style.STROKE);
  11. mPaint.setColor(Color.WHITE);
  12. mPaint.setStrokeWidth(2);
  13.  
  14. // 实例化区域A和区域B
  15. mRegionA = new Region(100, 100, 300, 300);
  16. mRegionB = new Region(200, 200, 400, 400);
  17. }
  18.  
  19. @Override
  20. protected void onDraw(Canvas canvas) {
  21. // 填充颜色
  22. canvas.drawColor(Color.BLUE);
  23.  
  24. canvas.save();
  25.  
  26. // 裁剪区域A
  27. canvas.clipRegion(mRegionA);
  28.  
  29. // 再通过组合方式裁剪区域B
  30. canvas.clipRegion(mRegionB, Region.Op.DIFFERENCE);
  31. // 填充颜色
  32. canvas.drawColor(Color.RED);
  33.  
  34. canvas.restore();
  35.  
  36. // 绘制框框帮助我们观察
  37. canvas.drawRect(100, 100, 300, 300, mPaint);
  38. canvas.drawRect(200, 200, 400, 400, mPaint);
  39. }
  40. }

以下是各种组合模式的效果:

DIFFERENCE

最终区域为第一个区域与第二个区域不同的区域。

INTERSECT

最终区域为第一个区域与第二个区域相交的区域。

REPLACE

最终区域为第二个区域。

REVERSE_DIFFERENCE

最终区域为第二个区域与第一个区域不同的区域。

UNION

最终区域为第一个区域加第二个区域。

XOR

最终区域为第一个区域加第二个区域并减去两者相交的区域。

4.3 Region

上面用的是region进行演示的,其余的rect什么的自己去试试,都是一样的。

有些童鞋会问那么Region和Rect有什么区别呢?首先最重要的一点,Region表示的是一个区域,而Rect表示的是一个矩形,这是最根本的区别之一。其次,Region有个很特别的地方是它不受Canvas的变换影响,Canvas的local不会直接影响到Region自身。

我们来看一个simple你就会明白:

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4. // 因为用到了clipRegion,所以要关闭硬件加速
  5. setLayerType(View.LAYER_TYPE_SOFTWARE, null);
  6.  
  7. // 设置画笔
  8. mPaint.setStyle(Paint.Style.STROKE);
  9. mPaint.setColor(Color.GREEN);
  10. mPaint.setStrokeWidth(10);
  11.  
  12. // 实例化矩形对象
  13. Rect rect = new Rect(0, 0, 200, 200);
  14.  
  15. // 实例化区域对象
  16. Region region = new Region(200, 200, 400, 400);
  17.  
  18. canvas.save();
  19.  
  20. // 裁剪矩形
  21. canvas.clipRect(rect);
  22. canvas.drawColor(Color.RED);
  23.  
  24. canvas.restore();
  25.  
  26. canvas.save();
  27.  
  28. // 裁剪区域
  29. canvas.clipRegion(region);
  30. canvas.drawColor(Color.BLUE);
  31.  
  32. canvas.restore();
  33.  
  34. // 为画布绘制一个边框便于观察
  35. canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mPaint);
  36. }

效果图中很清楚的表示了画布当前是和屏幕一样大小的,rect区域是红色的,region区域是蓝色的,两个矩形一样大。当我们按比例缩小画布,来看看会发生什么改变呢?

  1. // 缩放画布
  2. canvas.scale(0.75F, 0.75F);

完整代码:

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4. // 因为用到了clipRegion,所以要关闭硬件加速
  5. setLayerType(View.LAYER_TYPE_SOFTWARE, null);
  6.  
  7. // 缩放画布
  8. canvas.scale(0.75F, 0.75F);
  9.  
  10. // 设置画笔
  11. mPaint.setStyle(Paint.Style.STROKE);
  12. mPaint.setColor(Color.GREEN);
  13. mPaint.setStrokeWidth(10);
  14.  
  15. // 实例化矩形对象
  16. Rect rect = new Rect(0, 0, 200, 200);
  17.  
  18. // 实例化区域对象
  19. Region region = new Region(200, 200, 400, 400);
  20.  
  21. canvas.save();
  22.  
  23. // 裁剪矩形
  24. canvas.clipRect(rect);
  25. canvas.drawColor(Color.RED);
  26.  
  27. canvas.restore();
  28.  
  29. canvas.save();
  30.  
  31. // 裁剪区域
  32. canvas.clipRegion(region);
  33. canvas.drawColor(Color.BLUE);
  34.  
  35. canvas.restore();
  36.  
  37. // 为画布绘制一个边框便于观察
  38. canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mPaint);
  39. }

画布缩小后,矩形区域也跟着缩小了,但是region却没有缩小,这就表示region不会随着画布的缩放而改变。

五、saveXXX和restoreXXX

5.1 引入“层”的概念

saveXXX和restoreXXX这两个方法一个是保存画布,一个是释放画布。其实满抽象的,下面我给出一段代码:

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4.  
  5. mPaint.setColor(Color.RED);
  6. canvas.drawRect(, , , , mPaint);
  7.  
  8. // 保存画布
  9. canvas.save();
  10.  
  11. mPaint.setColor(Color.BLUE);
  12. canvas.drawRect(, , , , mPaint);
  13.  
  14. // 还原画布
  15. canvas.restore();
  16.  
  17. mPaint.setColor(Color.GREEN);
  18. canvas.drawCircle(, , , mPaint);
  19. }

这段代码中先绘制了一个一个红色矩形,然后保存了画布,再绘制一个蓝色矩形,接着释放画布,最后绘制了绿色的圆。简单来说就是画了矩形,矩形,圆形。而save()和restore()区域内的代码你可以简单理解为一个PS图层。

当然上面仅仅是简单的类比,android的绘制机制和ps的有一些本质差异,ps中各种图层都是在画板上的,你可以调整上下顺序;而android中是用栈来处理的,而且一旦你调用了drawxxx方法,图像就被绘制到屏幕上了,你再想改变就不可能了。也就是说drawxxx是一个提交操作,类似于ps中的保存,你做图的时候可以各种设置,一旦你保存了某一个图层,那么就没办法再改了。

5.2 用代码来解释“层”

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4.  
  5. mPaint.setColor(Color.RED);
  6. canvas.drawRect(50, 50, 250, 250, mPaint);// 保存画布
  7. canvas.save();
  8.  
  9. mPaint.setColor(Color.BLUE);
  10. canvas.drawRect(100, 100, 200, 200, mPaint);
  11.  
  12. // 还原画布
  13. canvas.restore();
  14.  
  15. mPaint.setColor(Color.GREEN);
  16. canvas.drawCircle(150, 150, 40, mPaint);
  17. }

代码很简单,直接上截图:

这里你看不到任何有关于层的特性,只看到了先画的图形会被放在最下面,后画出的图像会叠放在上层。那么我们修改一下代码加上一个旋转的效果,看看会如何。

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4.  
  5. mPaint.setColor(Color.RED);
  6. canvas.drawRect(50, 50, 250, 250, mPaint);
  7.  
  8. // 保存画布
  9. canvas.save();
  10. // 旋转画布
  11. canvas.rotate(30);
  12.  
  13. mPaint.setColor(Color.BLUE);
  14. canvas.drawRect(100, 100, 200, 200, mPaint);
  15.  
  16. // 还原画布
  17. canvas.restore();
  18.  
  19. mPaint.setColor(Color.GREEN);
  20. canvas.drawCircle(150, 150, 40, mPaint);
  21. }

我们在save()后添加了旋转,绘制后释放了画布。你会发现旋转的只有蓝色的矩形,之后绘制的绿色圆形没有旋转。这就说明蓝色的矩形被我们放在了一个新的层上,在那里进行的操作是不会影响到之后的图层的。一旦你释放了画布,之后的操作就在别的层上了,所以我们的绿色圆形没有发生任何变化。

至此结合上一节对Canvas的一些原理阐述我们该对它有个全新的认识,之前我们一直称其为画布,其实更准确地说Canvas是一个容器,如果把Canvas理解成画板,那么我们的“层”就像张张夹在画板上的透明的纸,而这些纸对应到Android则是一个个封装在Canvas中的Bitmap。

六、saveLayerXXX

6.1 save()和saveLayer(…)

除了save()方法Canvas还给我们提供了一系列的saveLayerXXX方法给我们保存画布,与save()方法不同的是,saveLayerXXX方法会将所有的操作存到一个新的Bitmap中而不影响当前Canvas的Bitmap,而save()方法则是在当前的Bitmap中进行操作,并且只能针对Bitmap的形变和裁剪进行操作,saveLayerXXX方法则无所不能,当然两者还有很多的不同,我们稍作讲解。虽然save和saveLayerXXX方法有着很大的区别但是在一般应用上两者能实现的功能是差不多,我们可以用代码来进比较:

save()

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4.  
  5. mPaint.setColor(Color.RED);
  6. canvas.drawRect(50, 50, 250, 250, mPaint);
  7.  
  8. // 保存画布
  9. canvas.save();
  10. // 旋转画布
  11. canvas.rotate(30);
  12.  
  13. mPaint.setColor(Color.BLUE);
  14. canvas.drawRect(100, 100, 200, 200, mPaint);
  15.  
  16. // 还原画布
  17. canvas.restore();
  18.  
  19. mPaint.setColor(Color.GREEN);
  20. canvas.drawCircle(150, 150, 40, mPaint);
  21. }

saveLayer(…)

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4.  
  5. mPaint.setColor(Color.RED);
  6. canvas.drawRect(50, 50, 250, 250, mPaint);
  7.  
  8. // 保存画布
  9. canvas.saveLayer(0, 0, 250, 250, null, Canvas.ALL_SAVE_FLAG);
  10. // 旋转画布
  11. canvas.rotate(30);
  12.  
  13. mPaint.setColor(Color.BLUE);
  14. canvas.drawRect(100, 100, 200, 200, mPaint);
  15.  
  16. // 还原画布
  17. canvas.restore();
  18.  
  19. mPaint.setColor(Color.GREEN);
  20. canvas.drawCircle(150, 150, 40, mPaint);
  21. }

  其实二者的效果是一样的,但为什么我们的蓝色矩形的一个角不见了呢?这是因为saveLayer保存的是一个区域,而这个区域小于蓝色矩形显示的区域,所以就进行了裁切。类比于PS,ps中的每个图层的大小都是等于图片大小的,但是这里我们可以单独设置每个层的大小。下面我们来引入saveLayer保存图层的原理。

  saveLayerXXX方法会将操作保存到一个新的Bitmap中,而这个Bitmap的大小取决于我们传入的参数大小,Bitmap是个相当危险的对象,很多朋友在操作Bitmap时不太理解其原理经常导致OOM,在saveLayer时我们会依据传入的参数获取一个相同大小的Bitmap,虽然这个Bitmap是空的但是其会占用一定的内存空间,我们希望尽可能小地保存该保存的区域,而saveLayer则提供了这样的功能,顺带提一下,onDraw方法传入的Canvas对象的Bitmap在Android没引入HW之前理论上是无限大的,实际上其依然是根据你的图像来不断计算的,而在引入HW之后,该Bitmap受到限制,具体多大大家可以尝试画一个超长的path运行下,你就可以在Logcat中看到warning。

6.2 saveLayerAlpha(…)

除了saveLayer,Canvas还提供了一个saveLayerAlpha方法,顾名思义,该方法可以在我们保存画布时设置画布的透明度:

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4.  
  5. mPaint.setColor(Color.RED);
  6. canvas.drawRect(50, 50, 250, 250, mPaint);
  7.  
  8. // 保存画布
  9. canvas.saveLayerAlpha(0, 0, 250, 250, 100, Canvas.ALL_SAVE_FLAG);
  10. // 旋转画布
  11. canvas.rotate(30);
  12.  
  13. mPaint.setColor(Color.BLUE);
  14. canvas.drawRect(100, 100, 200, 200, mPaint);
  15.  
  16. // 还原画布
  17. canvas.restore();
  18.  
  19. mPaint.setColor(Color.GREEN);
  20. canvas.drawCircle(150, 150, 40, mPaint);
  21. }

6.3 flag的值

我们在用saveLayerAlphasaveLayer方法时都用到了一个flag值Canvas.ALL_SAVE_FLAG,还有别的常量值可以使用么?我们还发现

save (int saveFlags)

也需要传入一个flag,这个flag是什么呢?

这六个常量值分别标识了我们在调用restore方法后还原什么。

六个标识位除了CLIP_SAVE_FLAG、MATRIX_SAVE_FLAG和ALL_SAVE_FLAG是savesaveLayerXXX方法都通用外,其余三个只能使saveLayerXXX方法有效。也就是说

CLIP_SAVE_FLAG

MATRIX_SAVE_FLAG

ALL_SAVE_FLAG

可以被save(int flag)和saveLayerXXX(…,int flag)使用,其余的仅仅适用于saveLayerXXX方法。

ALL_SAVE_FLAG  很简单也是我们新手级常用的标识保存所有;

CLIP_SAVE_FLAG  是裁剪标识位;

MATRIX_SAVE_FLAG  是变换的标识位;

CLIP_TO_LAYER_SAVE_FLAG、FULL_COLOR_LAYER_SAVE_FLAG和HAS_ALPHA_LAYER_SAVE_FLAG只对saveLayer和saveLayerAlpha有效。

CLIP_TO_LAYER_SAVE_FLAG  表示对当前图层执行裁剪操作需要对齐图层边界;

FULL_COLOR_LAYER_SAVE_FLAG  表示当前图层的色彩模式至少需要是8位色,

HAS_ALPHA_LAYER_SAVE_FLAG  表示在当前图层中将需要使用逐像素Alpha混合模式,关于色彩深度和Alpha混合大家可以参考维基百科

平时使用大家可以直接ALL_SAVE_FLAG就行,感兴趣的可以了解下别的标志位的作用。

七、restoreToCount(int saveCount)

所有的save、saveLayer和saveLayerAlpha方法都有一个int型的返回值,该返回值作为一个标识给与了一个你当前保存操作的唯一ID编号,我们可以利用restoreToCount(int saveCount)方法来指定在还原的时候还原哪一个保存操作。

7.1 restoreToCount和save

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4.  
  5. // 保存画布,并且得到id01
  6. int saveID1 = canvas.save(Canvas.MATRIX_SAVE_FLAG);
  7. // 旋转画布
  8. canvas.rotate(30);
  9. // 画红色的矩形
  10. mPaint.setColor(Color.RED);
  11. canvas.drawRect(50, 50, 250, 250, mPaint);
  12.  
  13. // 建立新的层,得到id02
  14. int saveID2 = canvas.save(Canvas.MATRIX_SAVE_FLAG);
  15. // 画蓝色的矩形
  16. mPaint.setColor(Color.BLUE);
  17. canvas.drawRect(100, 100, 200, 200, mPaint);
  18.  
  19. // 还原画布01
  20. canvas.restoreToCount(saveID1);
  21.  
  22. // 画绿色圆形
  23. mPaint.setColor(Color.GREEN);
  24. canvas.drawCircle(150, 150, 40, mPaint);
  25.  
  26. }

上面的效果可以告诉我们,虽然我们在绘制蓝色矩形前新建了一个图层,但蓝色矩形还是随着红色矩形旋转了。而我们调用canvas.restoreToCount(saveID1)后绘制的绿色圆形却出现了正常的位置上,没有进行旋转。

下面我们用栈来解释一下:

前面我们曾说过save和saveLayerXXX方法有着本质的区别,saveLayerXXX方法会将所有操作在一个新的Bitmap中进行,而save则是依靠stack栈来进行。

Canvas会默认保存一个底层的空间给我们绘制一些东西,当我们没有调用save方法时所有的绘图操作都在这个Default Stack ID中进行,每当我们调用一次save就会往Stack中存入一个ID,将其后所有的操作都在这个ID所指向的空间进行直到我们调用restore方法还原操作,上面代码我们save了2次且仅仅restore了ID1,所以我们之后绘制的绿色圆形是在ID2的栈中,而蓝色的矩形还是在ID1的栈中。

当我们调用canvas.restore()后标志着上一个save操作的结束或者说回滚了,下面我们整理下代码来看看不同的效果:

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4.  
  5. // 保存画布,并且得到id01
  6. int saveID1 = canvas.save(Canvas.MATRIX_SAVE_FLAG);
  7. // 旋转画布
  8. canvas.rotate(30);
  9. // 画红色的矩形
  10. mPaint.setColor(Color.RED);
  11. canvas.drawRect(50, 50, 250, 250, mPaint);
  12.  
  13. // 还原画布01
  14. canvas.restoreToCount(saveID1);
  15.  
  16. // 建立新的层,得到id02
  17. int saveID2 = canvas.save(Canvas.MATRIX_SAVE_FLAG);
  18.  
  19. // 画蓝色的矩形
  20. mPaint.setColor(Color.BLUE);
  21. canvas.drawRect(100, 100, 200, 200, mPaint);
  22.  
  23. // 还原画布02
  24. canvas.restoreToCount(saveID2);
  25.  
  26. // 画绿色圆形
  27. mPaint.setColor(Color.GREEN);
  28. canvas.drawCircle(150, 150, 40, mPaint);
  29.  
  30. }

可以明显的发现蓝色矩形这时候和红色矩形处于不同的层上了,没有随着红色矩形进行旋转。

7.2 getSaveCount()

前面说了,每当我们调用restore还原Canvas,对应的save栈空间就会从Stack中弹出去,Canvas提供了getSaveCount()方法来为我们提供查询当前栈中有多少save的空间。这个方法没啥可说的,上代码:

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4.  
  5. System.out.println(canvas.getSaveCount());
  6.  
  7. // 保存画布,并且得到id01
  8. int saveID1 = canvas.save(Canvas.MATRIX_SAVE_FLAG);
  9. System.out.println(canvas.getSaveCount());
  10. // 旋转画布
  11. canvas.rotate(30);
  12. // 画红色的矩形
  13. mPaint.setColor(Color.RED);
  14. canvas.drawRect(50, 50, 250, 250, mPaint);
  15.  
  16. // 还原画布01
  17. canvas.restoreToCount(saveID1);

八、变换

说起变换,无非就几种:平移、旋转、缩放和错切,而我们的Canvas也继承了变换的精髓,同样提供了这几种相应的方法,像translate(float dx, float dy)方法平移画布用了无数次,这里再次强调,translate方法会改变画布的原点坐标,原点坐标对变换的影响弥足轻重!

8.1 scale(float sx, float sy)

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4.  
  5. Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.kale);
  6. // 缩放比率为1时表示不缩放
  7. canvas.scale(1.0F, 1.0F);
  8. canvas.drawBitmap(bitmap, 0, 0, null);
  9. }

上面的缩放比为1,也就是不做任何改变。我们来改动下缩放比:

  1. canvas.scale(0.8F, 0.35F);

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

scale(float sx, float sy)缩放也很好理解,但是它有一个重载方法scale(float sx, float sy, float px, float py),后两个参数用于指定缩放的中心点,前两个参数用于指定横纵向的缩放比率值在0-1之间为缩小。

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4.  
  5. Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.kale);
  6. canvas.scale(0.8F, 0.35F, bitmap.getWidth(), 0);
  7. canvas.drawBitmap(bitmap, 0, 0, null);
  8. }

我们发现图像往右边移动了一点。这是因为我们把缩放中心进行的改变,大家尝试改变下参数,多多理解理解。

8.3 rotate(float degrees)和rotate(float degrees, float px, float py)

rotate(float degrees)和重载方法rotate(float degrees, float px, float py)它们的作用是旋转画布。这个在上面已经多次用到了,就不再赘述了。

8.4 skew(float sx, float sy)

kew(float sx, float sy)是错切方法,两个参数与scale类似表示横纵向的错切比率。所谓错切就是把图片分割为不同的格子,类似于棋盘。然后移动上面的各个交点的位置,让图片进行变换的效果。

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4.  
  5. Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.kale);
  6. canvas.skew(0.5F, 0F);
  7. canvas.drawBitmap(bitmap, 0, 0, null);
  8. }

效果:

8.5 setMatrix(matrix)

看名字就知道了,通过矩阵来进行变换。有关矩阵的一些知识,我在之前的文章中已经介绍过了,如果不了解计算原理,可以去参考下文的第七节:

详解Paint的setShader(Shader shader)

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4.  
  5. Matrix matrix = new Matrix();
  6. matrix.setScale(0.8F, 0.35F);
  7. matrix.postTranslate(100, 100);
  8. canvas.setMatrix(matrix);
  9.  
  10. Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.kale);
  11. canvas.drawBitmap(bitmap, 0, 0, null);
  12. }

本文大部分内容参考自:

http://blog.csdn.net/aigestudio/article/details/41960507

http://blog.csdn.net/aigestudio/article/details/42677973

http://blog.csdn.net/airk000/article/details/38925059

我记录在此,作为学习笔记。

From AigeStudio(http://blog.csdn.net/aigestudio)Power by Aige 尊重原作者,感谢作者的无私分享!

讲解Canvas中的一些重要方法的更多相关文章

  1. Canvas中的剪切clip()方法

    Canvas中的剪切 接下来我们要聊的不是图像的合成,而是Canvas中的另一个有用的功能:剪切区域.它是Canvas之中由路径所定义的一块区域,浏览器会将所有的绘图操作都限制在本区域内执行.在默认情 ...

  2. canvas中save()和restore()方法

    save()和restore()方法是绘制复杂图形不可缺少的方法它们是分别用来保存和恢复canvas状态的,都没有参数 save():用来保存Canvas的状态.save之后,可以调用Canvas的平 ...

  3. 【转】详细讲解Java中log4j的使用方法

    转载地址:http://www.233.com/Java/zhuangye/20070731/142625631.html 1.Log4j是什么? Log4j可以帮助调试(有时候debug是发挥不了作 ...

  4. 由浅入深讲解数据库中Synonym的使用方法

    1.Synonym的概念 Synonym(同义词)是SQL Server 2005的新特性.推出已经有几年的时间了.我们可以简单的理解Synonym为其他表的别名.本文中使用Northwind数据库为 ...

  5. <canvas>中isPointInPath()方法在不同绘制内容中的效果

    <canvas>是HTML5中新增加的一个元素,我们可以使用脚本(通常使用JavaScript)在上面绘制图形,就像个画布一样.我们可以用它来绘制图表.制作一些动画.默认大小为300px ...

  6. Canvas中图片翻转的应用

    很多时候拿到的素材都是单方向的,需要将其手动翻转来达到需求,比如下面这张图片: 它是朝右边方向的,但还需要一张朝左边方向的,于是不得不打开PS将其翻转然后做成雪碧图.如果只是一张图片还好说,但通常情况 ...

  7. HTML5中canvas的save和restore方法

    canvas的save和restore方法: save() 方法把当前绘画状态的一份拷贝压入到一个保存图像状态的栈中.这里的绘画状态指坐标原点.变形时的变化矩阵(该矩阵是调用 rotate().sca ...

  8. iOS7中UIView的animateKeyframesWithDuration方法讲解

    iOS7中UIView的animateKeyframesWithDuration方法讲解 在iOS7中,给UIView添加了一个方法用来直接使用关键帧动画而不用借助CoreAnimation来实现,那 ...

  9. Canvas中的save方法和restore方法

    初学者也许会误认为canvas中save方法是用来保存绘图状态的图形,而restore方法是用来还原之前保存的绘图状态的图形,其实不然. save():保存当前的绘图状态. restore():恢复之 ...

随机推荐

  1. 天猫浏览型应用的CDN静态化架构演变

    原文链接:http://www.csdn.net/article/2014-01-22/2818227-CDN-Architecture 在天猫双11活动中,商品详情.店铺等浏览型系统,通常会承受超出 ...

  2. WCF多种调用方式兼容

    1.能被ajax get 2.能post 3.wcf正常调用 实现: [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompati ...

  3. webkit特有的css属性

    内容参见:http://css-infos.net/properties/webkit 具体的定义网页里有详细说明.做有一些html5的应用的时候如果不能很好的适应手机,可以到这上面去找找方法-web ...

  4. SEO优化之Title 和 Meta 标签

    对搜索引擎最友好(Search Engine Friendly)的网页是静态网页,但大部分内容丰富或互动型网站都不可避免采用到相关技术语言来实现内容管理和交互功能.SEO 思想指导下的技术支持,主要是 ...

  5. Character Controller (角色控制器) 中 Move()和SimpleMove() 的区别

    首先给出两者的圣典: CollisionFlagsMove(Vector3motion); Description A more complex move function taking absolu ...

  6. 15个带给您优秀用户体验的移动应用 UI 设计

    在今天在移动 App 界面设计中,你可以看到不同创意类型的视觉效果.特别是在 Dribbble 上面,有有很多移动应用程序的 UI 概念设计,让你惊叹.如果你想获得灵感,那很有必要看看下面15个优秀用 ...

  7. node log4js包

    http://blog.csdn.net/heiantianshi1/article/details/43984601

  8. BlocksKit初见:一个支持将delegate转换成block的Cocoa库

    简介 项目主页: https://github.com/zwaldowski/BlocksKit BlocksKit 是一个开源的框架,对 Cocoa 进行了扩展,将许多需要通过 delegate 调 ...

  9. Struts2文件上传(基于表单的文件上传)

    •Commons-FileUpload组件 –Commons是Apache开放源代码组织的一个Java子项目,其中的FileUpload是用来处理HTTP文件上传的子项目   •Commons-Fil ...

  10. Qt Style Sheet实践(四):行文本编辑框QLineEdit及自动补全

    导读 行文本输入框在用于界面的文本输入,在WEB登录表单中应用广泛.一般行文本编辑框可定制性较高,既可以当作密码输入框,又可以作为文本过滤器.QLineEdit本身使用方法也很简单,无需过多的设置就能 ...