一.Canvas基本操作
(1)位移(translate)
translate是坐标系的移动,可以为图形绘制选择一个合适的坐标系。 请注意,位移是基于当前位置移动,而不是每次基于屏幕左上角的(0,0)点移动,如下:
// 在坐标原点绘制一个黑色圆形
mPaint.setColor(Color.BLACK);
canvas.translate(200,200);
canvas.drawCircle(0,0,100,mPaint);
// 在坐标原点绘制一个蓝色圆形
mPaint.setColor(Color.BLUE);
canvas.translate(200,200);
canvas.drawCircle(0,0,100,mPaint);
(2)缩放(scale)
缩放提供了两个方法,如下:
public void scale (float sx, float sy)
public final void scale (float sx, float sy, float px, float py)
这两个方法中前两个参数是相同的分别为x轴和y轴的缩放比例。而第二种方法比前一种多了两个参数,用来控制缩放中心位置的。
缩放比例(sx,sy)取值范围详解:
如果在缩放时稍微注意一下就会发现缩放的中心默认为坐标原点,而缩放中心轴就是坐标轴,如下:
// 将坐标系原点移动到画布正中心
canvas.translate(mWidth / 2, mHeight / 2);
RectF rect = new RectF(0,-400,400,0); // 矩形区域
mPaint.setColor(Color.BLACK); // 绘制黑色矩形
canvas.drawRect(rect,mPaint);
canvas.scale(0.5f,0.5f); // 画布缩放
mPaint.setColor(Color.BLUE); // 绘制蓝色矩形
canvas.drawRect(rect,mPaint);
接下来我们使用第二种方法让缩放中心位置稍微改变一下,如下:
// 将坐标系原点移动到画布正中心
canvas.translate(mWidth / 2, mHeight / 2);
RectF rect = new RectF(0,-400,400,0); // 矩形区域
mPaint.setColor(Color.BLACK); // 绘制黑色矩形
canvas.drawRect(rect,mPaint);
canvas.scale(0.5f,0.5f,200,0); // 画布缩放 <-- 缩放中心向右偏移了200个单位
mPaint.setColor(Color.BLUE); // 绘制蓝色矩形
canvas.drawRect(rect,mPaint);
前面两个示例缩放的数值都是正数,按照表格中的说明,当缩放比例为负数的时候会根据缩放中心轴进行翻转,下面我们就来实验一下:
// 将坐标系原点移动到画布正中心
canvas.translate(mWidth / 2, mHeight / 2);
RectF rect = new RectF(0,-400,400,0); // 矩形区域
mPaint.setColor(Color.BLACK); // 绘制黑色矩形
canvas.drawRect(rect,mPaint);
canvas.scale(-0.5f,-0.5f); // 画布缩放
mPaint.setColor(Color.BLUE); // 绘制蓝色矩形
canvas.drawRect(rect,mPaint);
// 将坐标系原点移动到画布正中心
canvas.translate(mWidth / 2, mHeight / 2);
RectF rect = new RectF(0,-400,400,0); // 矩形区域
mPaint.setColor(Color.BLACK); // 绘制黑色矩形
canvas.drawRect(rect,mPaint);
canvas.scale(-0.5f,-0.5f,200,0); // 画布缩放 <-- 缩放中心向右偏移了200个单位
mPaint.setColor(Color.BLUE); // 绘制蓝色矩形
canvas.drawRect(rect,mPaint);
和位移(translate)一样,缩放也是可以叠加的。
canvas.scale(0.5f,0.5f);
canvas.scale(0.5f,0.1f);
调用两次缩放则 x轴实际缩放为0.5x0.5=0.25 y轴实际缩放为0.5x0.1=0.05
// 将坐标系原点移动到画布正中心
canvas.translate(mWidth / 2, mHeight / 2);
RectF rect = new RectF(-400,-400,400,400); // 矩形区域
for (int i=0; i<=20; i++)
{
canvas.scale(0.9f,0.9f);
canvas.drawRect(rect,mPaint);
}
(3)旋转(rotate)
旋转提供了两种方法:
public void rotate (float degrees)
public final void rotate (float degrees, float px, float py)
和缩放一样,第二种方法多出来的两个参数依旧是控制旋转中心点的。
默认的旋转中心依旧是坐标原点:
// 将坐标系原点移动到画布正中心
canvas.translate(mWidth / 2, mHeight / 2);
RectF rect = new RectF(0,-400,400,0); // 矩形区域
mPaint.setColor(Color.BLACK); // 绘制黑色矩形
canvas.drawRect(rect,mPaint);
canvas.rotate(180); // 旋转180度 <-- 默认旋转中心为原点
mPaint.setColor(Color.BLUE); // 绘制蓝色矩形
canvas.drawRect(rect,mPaint);
改变旋转中心位置:
// 将坐标系原点移动到画布正中心
canvas.translate(mWidth / 2, mHeight / 2);
RectF rect = new RectF(0,-400,400,0); // 矩形区域
mPaint.setColor(Color.BLACK); // 绘制黑色矩形
canvas.drawRect(rect,mPaint);
canvas.rotate(180,200,0); // 旋转180度 <-- 旋转中心向右偏移200个单位
mPaint.setColor(Color.BLUE); // 绘制蓝色矩形
canvas.drawRect(rect,mPaint);
旋转也是可叠加的
canvas.rotate(180);
canvas.rotate(20);
调用两次旋转,则实际的旋转角度为180+20=200度。
(4)快照(save)和回滚(restore)
Q: 为什存在快照与回滚
A:画布的操作是不可逆的,而且很多画布操作会影响后续的步骤,例如第一个例子,两个圆形都是在坐标原点绘制的,而因为坐标系的移动绘制出来的实际位置不同。所以会对画布的一些状态进行保存和回滚。
与之相关的API:
下面对其中的一些概念和方法进行分析:
状态栈:
Q:什么是画布和图层?
A:实际上我们看到的画布是由多个图层构成的,如下图:
实际上我们之前讲解的绘制操作和画布操作都是在默认图层上进行的。
在通常情况下,使用默认图层就可满足需求,但是如果需要绘制比较复杂的内容,如地图(地图可以有多个地图层叠加而成,比如:政区层,道路层,兴趣点层)等,则分图层绘制比较好一些。
你可以把这些图层看做是一层一层的玻璃板,你在每层的玻璃板上绘制内容,然后把这些玻璃板叠在一起看就是最终效果。
SaveFlags
save
save 有两种方法:
// 保存全部状态
public int save ()
// 根据saveFlags参数保存一部分状态
public int save (int saveFlags)
restore
状态回滚,就是从栈顶取出一个状态然后根据内容进行恢复。
同样以上面状态栈图片为例,调用一次restore方法则将状态栈中第5次取出,根据里面保存的状态进行状态恢复。
restoreToCount
弹出指定位置以及以上所有状态,并根据指定位置状态进行恢复。
以上面状态栈图片为例,如果调用restoreToCount(2) 则会弹出 2 3 4 5 的状态,并根据第2次保存的状态进行恢复。
getSaveCount
获取保存的次数,即状态栈中保存状态的数量,以上面状态栈图片为例,使用该函数的返回值为5。
不过请注意,该函数的最小返回值为1,即使弹出了所有的状态,返回值依旧为1,代表默认状态。
可以看到第二种方法比第一种多了一个saveFlags参数,使用这个参数可以只保存一部分状态,更加灵活,这个saveFlags参数具体可参考上面表格中的内容。
每调用一次save方法,都会在栈顶添加一条状态信息,以上面状态栈图片为例,再调用一次save则会在第5次上面载添加一条状态。
常用格式
虽然关于状态的保存和回滚啰嗦了不少,不过大多数情况下只需要记住下面的步骤就可以了:
save(); //保存状态
... //具体操作
restore(); //回滚到之前的状态