Android自定義View-Canvas基本操作

weixin_33978044發表於2016-11-30

一.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);
2403563-1e3e5def246df6c4.png

(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)取值範圍詳解:


2403563-d148bf2950172532.png

如果在縮放時稍微注意一下就會發現縮放的中心預設為座標原點,而縮放中心軸就是座標軸,如下:

// 將座標系原點移動到畫布正中心
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);
2403563-1ee6abe060c61d37.png

接下來我們使用第二種方法讓縮放中心位置稍微改變一下,如下:
// 將座標系原點移動到畫布正中心
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);
2403563-3e981a52b0dcc4f1.png

前面兩個示例縮放的數值都是正數,按照表格中的說明,當縮放比例為負數的時候會根據縮放中心軸進行翻轉,下面我們就來實驗一下:

// 將座標系原點移動到畫布正中心
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);
2403563-e39703a4803244f6.png
// 將座標系原點移動到畫布正中心
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);
2403563-37923ebf68eec7cc.png

和位移(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);
}
2403563-067f93c60ba6af6a.png

(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);
2403563-7518bf4f038099b2.png

改變旋轉中心位置:
// 將座標系原點移動到畫布正中心
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);
2403563-b81ce764f2435d82.png

旋轉也是可疊加的

canvas.rotate(180);
canvas.rotate(20);

呼叫兩次旋轉,則實際的旋轉角度為180+20=200度。

(4)快照(save)和回滾(restore)

Q: 為什存在快照與回滾

A:畫布的操作是不可逆的,而且很多畫布操作會影響後續的步驟,例如第一個例子,兩個圓形都是在座標原點繪製的,而因為座標系的移動繪製出來的實際位置不同。所以會對畫布的一些狀態進行儲存和回滾。

與之相關的API:

2403563-9d9906797f63bc87.png

下面對其中的一些概念和方法進行分析:

狀態棧:

2403563-19a24e03aecc4bbf.png

Q:什麼是畫布和圖層?

A:實際上我們看到的畫布是由多個圖層構成的,如下圖:

2403563-1ff104ddc4d52d58.png

實際上我們之前講解的繪製操作和畫布操作都是在預設圖層上進行的。
在通常情況下,使用預設圖層就可滿足需求,但是如果需要繪製比較複雜的內容,如地圖(地圖可以有多個地圖層疊加而成,比如:政區層,道路層,興趣點層)等,則分圖層繪製比較好一些。
你可以把這些圖層看做是一層一層的玻璃板,你在每層的玻璃板上繪製內容,然後把這些玻璃板疊在一起看就是最終效果。

SaveFlags

2403563-82f5288db842a00b.png

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();   //回滾到之前的狀態

參考:安卓自定義View教程目錄

相關文章