
作者:李旺成
時間:2016年5月11日
這個 Hack 將介紹如何使用 Canvas 類在屏幕上繪制圖形,并為其添加動畫效果。
理解 Canvas
Canvas 類就是表示一塊畫布,你可以在上面畫你想畫的東西。
Canvas 是 Android 2D 繪圖中的一個關(guān)鍵類,先看一下官方對 Canvas 的簡介:

好吧!確實很簡潔,看了之后基本上不知道何為 Canvas(反正我是這個感覺),里面就一句話有用:”For more information about how to use Canvas, read the Canvas and Drawables developer guide.“(就是查看詳情)
英文閱讀無障礙的同學(xué)請自行點擊上面的鏈接跳轉(zhuǎn)過去閱讀,不喜歡看英文的,這里提供一段《50 Android Hacks》上對 Canvas 的介紹(從以前文檔翻譯來的):
”可以把 Canvas 視為 Surface 的替身或者接口,圖形便是繪制在 Surface 上的。Canvas 封裝了所有的繪圖調(diào)用。通過 Canvas,繪制到 Surface 上的內(nèi)容首先存儲到與之關(guān)聯(lián)的 Bitmap 中,該 Bitmap 最終會呈現(xiàn)到窗口上。“
簡而言之,Canvas 就是畫布,可以在上面畫各種圖形或圖像。下面來看看如何使用 Canvas。
Canvas 簡單使用
這里以畫一個紅色方塊為例介紹一下 Canvas 的使用。效果如下:

獲取 Canvas
獲取 Canvas 一般有兩種方式:
1、從 View 的 onDraw() 方法中獲取
看一下 View onDraw() 方法的方法簽名:
protected void onDraw(Canvas canvas);
自定義 View 一般會重寫 onDraw() 方法,這時候 View 中的 Canvas 對象會被當(dāng)做參數(shù)傳遞過來,可以直接操作這個 Canvas (Google 也建議使用 onDraw() 傳入的 Canvas),對該 Canvas 的效果會直接反應(yīng)在 View 中。
2、自行創(chuàng)建 Canvas 對象
Canvas 類提供了兩個 public 的構(gòu)造方法。可以直接調(diào)用構(gòu)造方法創(chuàng)建對象:
Bitmap b = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);
// 創(chuàng)建 Canvas 方式一
Canvas c1 = new Canvas();
c1.setBitmap(b);
// 創(chuàng)建 Canvas 方式二
Canvas c2 = new Canvas(b);
Canvas 需要以 Bitmap 為操作對象,無論哪種方式創(chuàng)建的 Canvas 都是需要傳入自定義的 Bitmap。
當(dāng)使用自己創(chuàng)建的 Canvas 在 bitmap 上執(zhí)行完繪制操作后,可以將繪制的結(jié)果轉(zhuǎn)交給另外一個 Canvas,這樣就可以達(dá)到兩個 Canvas 協(xié)作完成的效果,簡化邏輯。(參考自:Android Canvas繪圖詳解(圖文))
使用 Canvas 繪制方塊
直接看代碼:
// 創(chuàng)建畫筆
Paint paint = new Paint();
// 設(shè)置畫筆顏色
paint.setARGB(255, 255, 0, 0);
// 設(shè)置抗鋸齒
paint.setAntiAlias(true);
// 創(chuàng)建矩形
RectF rect = new RectF(50, 50, 50, 50);
// 繪制矩形
canvas.drawRoundRect(rect,
100, //x軸的半徑
100, //y軸的半徑
paint);
好了,準(zhǔn)備工作完成了,下面來看看如何在 Canvas 上顯示動畫。
在 Canvas 上顯示動畫
我們想實現(xiàn)這樣一個效果:一個紅色的方塊在屏幕上彈跳,當(dāng)觸碰到屏幕邊緣的時候就會彈開。具體效果如下動圖所示:

思路分析
這里的關(guān)鍵是如何讓”紅色方塊“動起來,一般可能可以想到這么個思路:啟動一個線程,每隔一段時間改變方塊的位置,然后刷新視圖顯示。
是的,用這種方式可以實現(xiàn),但是有沒有想過是否有更簡單的方式?如果有很多需要持續(xù)運行的元素,這樣做是不是會很麻煩,而且會影響性能(開啟子線程挺消耗性能的)。
不知道是否聽過這樣一種最佳實踐的提示:
不要在 onDraw() 方法中創(chuàng)建對象,或者使用臨時對象,應(yīng)該該方法會頻繁調(diào)用。
好了,我的思路是:
利用 View 調(diào)用 invalidate() 方法請求重新繪制視圖時會調(diào)用該視圖的 onDraw() 方法,以到達(dá)循環(huán)調(diào)用的目的。在每次 onDraw() 的時候都改變”紅色方塊“的坐標(biāo),這樣就可以實現(xiàn)不停的運動了。
具體實現(xiàn)
上面介紹了實現(xiàn)的思路,我們這里的具體實現(xiàn)和上面說的稍有不同,但是,實際上是一樣的。
創(chuàng)建紅色方塊視圖
這個紅色方塊需要提供繪制”紅色方塊“的實現(xiàn),以及坐標(biāo)改變的方法,實現(xiàn)代碼如下(Rectangle.java):
public class Rectangle extends View {
...
public void move() {
moveTo(mSpeedX, mSpeedY);
}
// 真正的移動方法
private void moveTo(int goX, int goY) {
// check the borders, and set the direction if a border has reached
if (mCoordX > (mDrawView.width - MAX_SIZE)) {
goRight = false;
}
if (mCoordX < 0) {
goRight = true;
}
if (mCoordY > (mDrawView.height - MAX_SIZE)) {
goDown = false;
}
if (mCoordY < 0) {
goDown = true;
}
// move the x and y
if (goRight) {
mCoordX += goX;
} else {
mCoordX -= goX;
}
if (goDown) {
mCoordY += goY;
} else {
mCoordY -= goY;
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mDrawRect.set(mCoordX, mCoordY, mCoordX + mRealSize, mCoordY
+ mRealSize);
canvas.drawRoundRect(mDrawRect, 0, 0, mInnerPaint);
}
...
}
這里截取了關(guān)鍵代碼,具體代碼請自行下載項目代碼。
繪制方塊
專門提供一個類 DrawView.java 用于負(fù)責(zé)在屏幕上繪制上面創(chuàng)建的”方塊視圖“??创a:
public class DrawView extends View {
private Rectangle mRectangle;
public int width;
public int height;
public DrawView(Context context) {
super(context);
mRectangle = new Rectangle(context, this);
mRectangle.setARGB(255, 255, 0, 0);
mRectangle.setSpeedX(3);
mRectangle.setSpeedY(3);
}
@Override
protected void onDraw(Canvas canvas) {
invalidate();
mRectangle.move();
mRectangle.onDraw(canvas);
}
}
Rectangle 將在 DrawView 的 width 和 height 范圍內(nèi)運動,真正導(dǎo)致運動的原因是 onDraw() 方法中調(diào)用了 invalidate() 方法,該方法會請求重繪,這時 Rectangle 的坐標(biāo)變化了,所以就出現(xiàn)了移動的效果。
顯示 DrawView
你把它當(dāng)作普通的自定義 View 使用即可,這里將 DrawView 做為 Activity 的內(nèi)容視圖:
public class CanvasAnimActivity extends AppCompatActivity {
private DrawView mDrawView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 獲取屏幕尺寸
Display display = getWindowManager().getDefaultDisplay();
mDrawView = new DrawView(this);
// 這里簡單簡單起見為狀態(tài)欄和ActionBar的高度取了個固定值 200
mDrawView.height = display.getHeight() - 200;
mDrawView.width = display.getWidth();
setContentView(mDrawView);
}
}
運行起來就可以看到紅色方塊運動的效果了。
小結(jié)
這個 Hack 的代碼其實挺簡單的,關(guān)鍵是理解”方塊“是如何動起來的。
在 onDraw() 方法中通過調(diào)用 invalidate() 方法變換視圖的位置實現(xiàn)自定義動畫的簡單方法。當(dāng)然,使用這個小技巧來處理游戲的主循環(huán)也是一個不錯的方案。
項目地址
項目示例代碼:
CanvasAnimActivity.java
Rectangle.java
DrawView.java
參考
Android Canvas繪圖詳解(圖文)
Android——Canvas類的使用
Canvas and Drawables