Andorid自定义View-Canvas之画布操作(学习笔记)

注明: 非常感谢 gcssloop 的博客,以下为我学习时的笔记记录。

Canvas常用速查表

操作类型 相关API 备注
绘制颜色 drawColor, drawRGB, drawARGB 使用单一颜色填充整个画布
绘制基本形状 drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc 依次为 点、线、矩形、圆角矩形、椭圆、圆、圆弧
绘制图片 drawBitmap, drawPicture 绘制位图和图片
绘制文本 drawText, drawPosText, drawTextOnPath 依次为 绘制文字、绘制文字时指定每个文字位置、根据路径绘制文字
绘制路径 drawPath 绘制路径,绘制贝塞尔曲线时也需要用到该函数
顶点操作 drawVertices, drawBitmapMesh 通过对顶点操作可以使图像形变,drawVertices直接对画布作用、 drawBitmapMesh只对绘制的Bitmap作用
画布剪裁 clipPath, clipRect 设置画布的显示区域
画布快照 save, restore, saveLayerXxx, restoreToCount, getSaveCount 依次为 保存当前状态、 回滚到上一次保存的状态、 保存图层状态、 回滚到指定状态、 获取保存次数
画布变换 translate, scale, rotate, skew 依次为 位移、缩放、 旋转、错切
Matrix(矩阵) getMatrix, setMatrix, concat 实际上画布的位移,缩放等操作的都是图像矩阵Matrix, 只不过Matrix比较难以理解和使用,故封装了一些常用的方法。

基本操作

位移(translate)

translate 是坐标系的移动,可以为图形绘制选择一个合适的坐标系。 但是需要注意的时,位移111111是基于当前位置移动,而不是每次基于屏幕左上角的(0,0)点移动。

//。。。省略一些创建画笔等等代码
//在坐标原点绘制了一个黑色小圆
canvas.translate(200,200);
canvas.drawCircle(0,0,80,paint);

//移动画布,再次绘制
canvas.translate(200,200);
canvas.drawCircle(0,0,80,paint);

Andorid自定义View-Canvas之画布操作(学习笔记)_第1张图片

首先我们将坐标系移动一端距离后绘制了一个圆形,之后再移动一端距离绘制, 两次移动是可以叠加的。

缩放(scale)

缩放提供了两个方法,如下:

//sx,sy为x,y轴缩放比例
public void scale(float sx,float sy)
 //sx,sy为x,y轴缩放比例,后面的 px,py为中心位置坐标
public final void scale(float sx, float sy, float px, float py)

缩放比例(sx,sy)的取值范围:

取值范围(n) 说明
(-∞, -1) 先根据缩放中心放大n倍,再根据中心轴进行翻转
-1 根据缩放中心轴进行翻转
(-1, 0) 先根据缩放中心缩小到n,再根据中心轴进行翻转
0 不会显示,若sx为0,则宽度为0,不会显示,sy同理
(0, 1) 根据缩放中心缩小到n
1 没有变化
(1, +∞) 根据缩放中心放大n倍

注明: 缩放中心的默认点为坐标原点,而缩放中心轴就是坐标轴。如下demo

//移动坐标系至画布中心点
canvas.translate(mWidth/2,mHeight/2);
//矩形区域
RectF  rectF=new RectF(0,-400,400,0);
//绘制矩形
canvas.drawRect(rectF,paint);

//画布缩放为一半
canvas.scale(0.5f,0.5f);
//改变画笔为填充
paint.setStyle(Paint.Style.FILL);
//更改画笔颜色
paint.setColor(Color.GREEN);
canvas.drawRect(rectF,paint);

Andorid自定义View-Canvas之画布操作(学习笔记)_第2张图片

接下来使用第二个方法,也就是4个参数的scale

//移动坐标系至画布中心点
canvas.translate(mWidth/2,mHeight/2);
//矩形区域
RectF  rectF=new RectF(0,-400,400,0);
//绘制矩形
canvas.drawRect(rectF,paint);

//画布缩放为一半,并将中心点改为200,200
canvas.scale(0.5f,0.5f,200,200);
//改变画笔为填充
paint.setStyle(Paint.Style.FILL);
//更改画笔颜色
paint.setColor(Color.GREEN);
canvas.drawRect(rectF,paint);

Andorid自定义View-Canvas之画布操作(学习笔记)_第3张图片

我们现在试一下,当缩放比例为负数时出现的情况。

//移动坐标系至画布中心点
canvas.translate(mWidth/2,mHeight/2);
//矩形区域
RectF  rectF=new RectF(0,-400,400,0);
//绘制矩形
canvas.drawRect(rectF,paint);

//画布缩放为一半,再按中心轴进行翻转
canvas.scale(-0.5f,-0.5f);
//改变画笔为填充
paint.setStyle(Paint.Style.FILL);
//更改画笔颜色
paint.setColor(Color.GREEN);
canvas.drawRect(rectF,paint);

Andorid自定义View-Canvas之画布操作(学习笔记)_第4张图片

现在我们再加上更改缩放中心点,因为默认的都是坐标原点。

//移动坐标系至画布中心点
canvas.translate(mWidth/2,mHeight/2);
//矩形区域
RectF  rectF=new RectF(0,-400,400,0);
//绘制矩形
canvas.drawRect(rectF,paint);

//画布缩放为一半,再将中心点设为(200,0)
canvas.scale(-0.5f,-0.5f,200,0);
//改变画笔为填充
paint.setStyle(Paint.Style.FILL);
//更改画笔颜色
paint.setColor(Color.GREEN);
canvas.drawRect(rectF,paint);

Andorid自定义View-Canvas之画布操作(学习笔记)_第5张图片

红色的小点就是我们的中心点。

注:和位移(translate)一样,缩放也是可以叠加的。

canvas.scale(0.5f,0.5f)
canvas.scale(0.5f,0.1f)

如以下Demo

canvas.translate(mWidth/2,mHeight/2);
//矩形区域
RectF  rectF=new RectF(-400,-400,400,400);
//绘制矩形

//画布缩放为一半,再将中心点设为(200,0)
//改变画笔为填充
paint.setStyle(Paint.Style.STROKE);
for (int i=0;i<20;i++){
    canvas.scale(0.9f,0.9f);
    canvas.drawRect(rectF,paint);
}

Andorid自定义View-Canvas之画布操作(学习笔记)_第6张图片

 

旋转(rotate)

旋转提供了两种方法

public void rotate(float degress)
public final void rotate(float degress,float px,float py)

和缩放一样,第二种方法多出来的两个参数依旧是控制旋转中心点的。

默认的旋转中心依旧是坐标原点:

//移动坐标系至画布中心点
canvas.translate(mWidth/2,mHeight/2);
//矩形区域
RectF  rectF=new RectF(0,-400,400,0);
//绘制矩形
canvas.drawRect(rectF,paint);
//旋转画布180度
canvas.rotate(180);
//设置画笔颜色
paint.setColor(Color.GREEN);
//绘制矩形
canvas.drawRect(rectF,paint);

Andorid自定义View-Canvas之画布操作(学习笔记)_第7张图片

改变旋转中心位置

 canvas.translate(mWidth/2,mHeight/2);
        //矩形区域
        RectF  rectF=new RectF(0,-400,400,0);
        //绘制矩形
        canvas.drawRect(rectF,paint);
        //旋转画布180度,并改变中心点位置
        canvas.rotate(180,-100,0);
        //设置画笔颜色
        paint.setColor(Color.GREEN);
        //绘制矩形
        canvas.drawRect(rectF,paint);

Andorid自定义View-Canvas之画布操作(学习笔记)_第8张图片

注:旋转也是可以叠加的:

  canvas.translate(mWidth/2,mHeight/2);
        canvas.drawCircle(0,0,400,paint);
        canvas.drawCircle(0,0,380,paint);

        for (int i=0;i<=360;i++){
            //开始坐标(0,380) 结束坐标(0,400)
            canvas.drawLine(0,380,0,400 ,paint);
            canvas.rotate(10);
        }

Andorid自定义View-Canvas之画布操作(学习笔记)_第9张图片

错切(skew)

skew这里翻译为错切,错切是特殊类型的线性交换。

错切只提供了一种方法

public void skew(float sx,float sy)

相应的参数含义:

  1. float sx:将画布在x方向上倾斜相应的角度,sx倾斜角度的tan值
  2. float sy:将画布在y轴方向上倾斜相应的角度,sy为倾斜角度的tan值

变换后:

X=x+sx*y;
Y=sy*x+y;

Demo

//移动坐标系至画布中心点
canvas.translate(mWidth/2,mHeight/2);
RectF rectF=new RectF(0,0,200,200);
canvas.drawRect(rectF,paint);
//水平错切<-45度,其实也就是将y逆时针旋转45度,x轴不变
canvas.skew(1,0);
//其实也就是y轴不变,x轴顺时针旋转45度
canvas.skew(0,1);
paint.setColor(Color.BLUE);
canvas.drawRect(rectF,paint);

Andorid自定义View-Canvas之画布操作(学习笔记)_第10张图片

再看一个Demo

Andorid自定义View-Canvas之画布操作(学习笔记)_第11张图片

这种效果是怎么出来的呢,如果是利用错切,而不是旋转canvas和scale

其实我们只需要明白 skew(float sx,float sy),里面两个参数所代表的意义。

  1. 当sx为0,sy为1,即为,y轴不变,x轴顺时针旋转。
  2. 当sx为1,sy为0,即为,x轴不变,y轴逆时针旋转。
  3. 如果sx为负数,或者sy为负数,那么它两相应的旋转也会相反,逆时针转顺时针,顺时针转逆时针。
  4. 如果sx为1,sy为1,相当于没有改变,因为y轴逆时针转,x轴顺时针转,所以等于没转。
  5. 如果sx为1,sy为-1,就成为了上面的效果,结果很简单,x,y都逆时针旋转。

只要明白了以上规律,那么错切也就明白了。

快照(save)和回滚(restore)

画布的操作时不可逆的,而且很多画布操作会影响后续的步骤,所以会对画布的一些状态进行保存和回滚。

与之相关的API:

相关API 简介
save 把当前的画布的状态进行保存,然后放入特定的栈中
saveLayerXxx 新建一个图层,并放入特定的栈中
restore 把栈中最顶层的画布状态取出来,并按照这个状态恢复当前的画布
restoreToCount 弹出指定位置及其以上所有的状态,并按照指定位置的状态进行恢复
getSaveCount 获取栈中内容的数量(即保存次数)

对于他们的状态我们用状态栈来称呼:

Andorid自定义View-Canvas之画布操作(学习笔记)_第12张图片

这个栈可以存储画布状态和图层状态。

什么是画布和图层?

实际上我们看到的画布是由多个图层构成。如下图:

Andorid自定义View-Canvas之画布操作(学习笔记)_第13张图片

实际上我们的绘制操作和画布操作都是在默认图层上进行的。

在通常情况下,使用默认图层就可满足需求,但是如果需要绘制比较复杂的内容,如现在常见的ui都是多层级,我们在每层进行不同的绘制,最后把这些图层叠在一起就是最终的效果。

SaveFlags

名称 简介
ALL_SAVE_FLAG 默认,保存全部状态
CLIP_SAVE_FLAG 保存剪辑区
CLIP_TO_LAYER_SAVE_FLAG 剪裁区作为图层保存
FULL_COLOR_LAYER_SAVE_FLAG 保存图层的全部色彩通道
HAS_ALPHA_LAYER_SAVE_FLAG 保存图层的alpha(不透明度)通道
MATRIX_SAVE_FLAG 保存Matrix信息( translate, rotate, scale, skew)

save

//保存全部状态
public int save();
//根据saveFlags参数保存一部分状态
public int save(int svaeFlags);

可以看到第二种方法比第一种多了一个saveFlags的参数,使用这个参数可以只保存一部分状态,更加灵活,这个 saveFlags参数具体可以根据上面表格中的内容来使用。

没调用一次 save 方法,都会在栈顶添加一条状态信息,以上面的状态栈图片为例,在调用一次save 则会在第五次上面添加一条状态。

saveLayerXxx

saveLayerXxx有比较多的方法:

// 无图层alpha(不透明度)通道
public int saveLayer (RectF bounds, Paint paint)
public int saveLayer (RectF bounds, Paint paint, int saveFlags)
public int saveLayer (float left, float top, float right, float bottom, Paint paint)
public int saveLayer (float left, float top, float right, float bottom, Paint paint, int saveFlags)

// 有图层alpha(不透明度)通道
public int saveLayerAlpha (RectF bounds, int alpha)
public int saveLayerAlpha (RectF bounds, int alpha, int saveFlags)
public int saveLayerAlpha (float left, float top, float right, float bottom, int alpha)
public int saveLayerAlpha (float left, float top, float right, float bottom, int alpha, int saveFlags)

注意:saveLayerXxx方法会让你花费更多的时间去渲染图像(图层多了相互之间叠加会导致计算量成倍增长),使用前请谨慎,如果可能,尽量避免使用。

这个暂时不过多讲述,如果以后用到详细讲解。(因为这里面东西也有不少啊QAQ)

restore

状态回滚,就是从栈顶取出一个状态然后根据内容进行恢复。

同样以上面状态栈图片为例,调用一次restore方法则将状态栈中第5次取出,根据里面保存的状态进行状态恢复。

restoreToCount

弹出指定位置以及以上所有状态,并根据指定位置状态进行恢复。

以上面状态栈图片为例,如果调用restoreToCount(2) 则会弹出 2 3 4 5 的状态,并根据第2次保存的状态进行恢复。

getSaveCount

获取保存的次数,即状态栈中保存状态的数量,以上面状态栈图片为例,使用该函数的返回值为5。

不过请注意,该函数的最小返回值为1,即使弹出了所有的状态,返回值依旧为1,代表默认状态。

常用格式

虽然关于状态的保存和回滚啰嗦了不少,不过大多数情况下只需要记住下面的步骤就可以了:

save();      //保存状态
...          //具体操作
restore();   //回滚到之前的状态

这种方式也是最简单和最容易理解的使用方法。

Demo

//移动坐标系至画布中心点
canvas.translate(mWidth/2,mHeight/2);
RectF rectF=new RectF(0,0,200,200);
//先保存状态
canvas.save();
//将画布缩小一半
canvas.scale(0.5f,0.5f);
//画矩形
canvas.drawRect(rectF,paint);
//回到保存前的画布状态
canvas.restore();
//设置画笔颜色
paint.setColor(Color.BLUE);
//画矩形
canvas.drawRect(rectF,paint);

Andorid自定义View-Canvas之画布操作(学习笔记)_第14张图片

这里我们先保存画布状态,然后缩小画布为1/2,再画了个小矩形,接着回退到之前的状态,接着又画了一个矩形。如上图所示。

 

更多Android学习笔记,请关注 Android-NoteBook,欢迎Star。

你可能感兴趣的:(日常开发,Android初级-夯实基础,自定义View,Canvas)