Canvas API詳解(Part 1)
本節引言:
前面我們花了13小節詳細地講解了Android中Paint類別大部分常用的API,本節開始我們來解釋 Canvas(畫板)的一些常用API,我們在
- 8.3.1 三個繪圖工具類別詳解
- 中已經列出了我們可供調用的一些方法,我們分下類別:
- drawXxx方法族:以一定的座標值在目前畫圖區域畫圖,另外圖層會疊加, 即後面繪畫的圖層會覆蓋前面繪畫的圖層。
- clipXXX方法族:在目前的畫圖區域裁切(clip)出一個新的畫圖區域,這個 畫圖區域就是canvas物件目前的畫圖區域了。如:clipRect(new Rect()), 那麼此矩形區域就是canvas目前的畫圖區域
- getXxx方法族:獲得與Canvas相關一些值,例如寬高,螢幕密度等。
- save(),restore(),saveLayer(),restoreToCount()等儲存復原圖層的方法
- translate(平移),scale(縮放),rotate(旋轉),skew(傾斜)
當然還有其他一些零散的方法,嗯,從本節開始我會挑一些感覺有點意思的API來進行學習~
而本節先給大家帶來的是translate(平移),scale(縮放),rotate(旋轉),skew(傾斜) 以及save(),restore()的詳解!
官方API文件:Canvas
另外我們先要明確Canvas中X軸與Y軸的方向:
1.translate(平移)
方法:translate(float dx, float dy)
#解析:平移,將畫布的座標原點向左右方向移動x,向上下方向移動y,canvas預設位置在(0,0)
參數:dx為水平方向的移動距離,dy為垂直方向的移動距離
使用範例:
for(int i=0; i < 5; i++) { canvas.drawCircle(50, 50, 50, mPaint); canvas.translate(100, 100); }運行效果 :
2.rotate(旋轉)
#:rotate(float degrees) / rotate(float degrees, float px, float py)
解析:圍繞座標原點旋轉degrees度,值為正順時針
#參數:degrees為旋轉角度,px和py為指定旋轉的中心點座標(px,py)
使用範例:
Rect rect = new Rect(50,0,150,50); canvas.translate(200, 200); for(int i = 0; i < 36;i++){ canvas.rotate(10); canvas.drawRect(rect, mPaint); }
執行效果:
程式碼分析:
這裡我們先呼叫了translate(200,200)將canvas的座標原點移向了(200,200),再進行繪製,所以我們 繪製的結果可以完整的在畫布上顯示出來,假如我們是為rotate設定了(10,200,200),會是這樣一個 結果:
有疑問是吧,這個牽涉到Canvas多圖層的概念,等等會講~
3.scale(縮放)
方法:scale(float sx, float sy) / scale(float sx, float sy, float px , float py)
解析:對畫布進行縮放
參數:sx為水平方向縮放比例,sy為垂直方向的縮放比例,px和py我也不知道,小數為縮小,整數為放大
使用範例:
canvas.drawBitmap(bmp,0,0,mPaint); canvas.scale(0.8f, 0.8f); canvas.drawBitmap(bmp, 0, 0, mPaint); canvas.scale(0.8f, 0.8f); canvas.drawBitmap(bmp,0,0,mPaint);
運行效果:
#4.skew(傾斜)
##方法:skew(float sx, float sy)
#解析:傾斜,也可以翻譯為斜切,扭曲
#參數:sx為x軸方向上傾斜的對應角度,sy為y軸方向上傾斜的對應角度,兩個值都是tan值喔! 都是tan值!都是tan值!例如要在x軸方向上傾斜60度,那麼小數值對應:tan 60 = 根號3 = 1.732!
使用範例:
canvas.drawBitmap(bmp,0,0,mPaint); canvas.translate(200, 200); canvas.skew(0.2f,-0.8f); canvas.drawBitmap(bmp,0,0,mPaint);
執行效果:
5.Canvas圖層的概念以及save()和restore()詳解
我們一般喜歡稱呼Canvas為畫布,童鞋們一直覺得Canvas就是一張簡單的畫紙,那我想 問下多層的動畫是怎麼用canvas來完成的?上面那個translate平移的例子,為什麼 drawCircle(50, 50, 50, mPaint); 參考座標一直是(50,50)那為何會出現這樣的效果? 有疑惑的童鞋可能是一直將螢幕的概念與Canvas的概念混淆了,下面我們來還原下 呼叫translate的案例發現場:
#如圖,是畫布座標原點的每次分別在x,y軸上移動100;那麼假如我們要重新回到(0,0) 點處繪製新的圖形呢?怎麼破,translate(-100,-100)的慢慢平移回去?不會真的這麼 糾結吧...
好吧,不賣關子了,我們可以在做平移變換之前將當前canvas的狀態保存,其實Canvas為 我們提供了圖層(Layer)的支持,而這些Layer(圖層)是按"堆疊結構"來進行管理的
當我們呼叫save()方法,會儲存目前Canvas的狀態然後作為一個Layer(圖層),加入到Canvas堆疊中, 另外,這個Layer(圖層)不是一個具體的類,就是一個概念性的東西而已!
而當我們呼叫restore()方法的時候,會恢復先前Canvas的狀態,而此時Canvas的圖層堆疊 會彈出棧頂的那個Layer,後繼的Layer來到棧頂,此時的Canvas回復到此棧頂時保存的Canvas狀態!
簡單說就是:save()往堆疊壓入一個Layer,restore()彈出堆疊頂部的一個Layer,這個Layer代表Canvas的 狀態! 也就是說可以save()多次,也可以restore()多次,但restore的呼叫次數不能大於save 否則會引發錯誤!這是網路上大部分的說法,不過實際測試中並沒有出現這樣的問題,即使我restore的 次數多於save,也沒有出現錯誤~目測是系統改了,等下測給大家看~來來來,寫個例子驗證下save和restore的作用!
寫個範例:
範例程式碼:
canvas.save(); //保存当前canvas的状态 canvas.translate(100, 100); canvas.drawCircle(50, 50, 50, mPaint); canvas.restore(); //恢复保存的Canvas的状态 canvas.drawCircle(50, 50, 50, mPaint);
執行結果:
沒說什麼了吧,程式碼和結果已經說明了一切,接著我們搞得複雜點,來一發 多個save()和restore()!
範例程式碼:
canvas.save(); canvas.translate(300, 300); canvas.drawBitmap(bmp, 0, 0, mPaint); canvas.save(); canvas.rotate(45); canvas.drawBitmap(bmp, 0, 0, mPaint); canvas.save(); canvas.rotate(45); canvas.drawBitmap(bmp, 0, 0, mPaint); canvas.save(); canvas.translate(0, 200); canvas.drawBitmap(bmp, 0, 0, mPaint);
運行結果:
##結果分析:
首先平移(300,300)畫圖,然後旋轉45度畫圖,再接著旋轉45度畫圖,接著平移(0,200), 期間每次畫圖前都save()一下,看到這裡你可能有個疑問,最後這個平移不是y移動200 麼,怎麼變成向左了?嘿嘿,我會告訴你rotate()旋轉的是整個座標軸麼?座標軸的 變化:#
嗯,rotate()弄清楚了是吧,那就行,接著我們來試試restore咯~我們在最後繪圖的前面 加兩個restore()!
canvas.restore(); canvas.restore(); canvas.translate(0, 200); canvas.drawBitmap(bmp, 0, 0, mPaint);
運行結果:
#不說什麼,自己體會,再加多個restore()!
有點意思,再來,繼續加上restore()
##嗯,好像不可以再寫
restore了是吧,因為我們只save了四次,按照網路上的說法, 這會報錯的,真的是這樣嗎?這裡我們呼叫Canvas提供給我們的一個獲得當前棧中 有多少個Layer的方法:getSaveCount();然後在save()和restore()的前後都 加上一個Log將堆疊中Layer的層數印出來:
結果真是喜聞樂見,畢竟實踐出真知,可能是Canvas改過吧,或者其他原因,這裡 要看源碼才知道了,時間關係,這裡我們知道下restore的次數可以比save多就好了, 但還是建議restore的次數還是少於save,以避免造成不必要的問題~ 至於進棧和出棧的流程我就不話了,筆者自己動筆畫畫,非常容易理解!6.saveLayer()與restoreToCount()講解
其實這兩個方法和save以及restore大同小異,只是在後者的基礎上多了一些東東而已, 例如saveLayer(),有下面多個重載方法:你可以理解為save()方法保存的是整個Canvas ,而saveLayer()則可以選擇性的保存某個區域的狀態, 另外,我們看到餐宿和中有個:int saveFlags,這個是設定改保存那個物件的!可選值有:
標記 | 說明 |
---|---|
ALL_SAVE_FLAG | #儲存全部的狀態 |
CLIP_SAVE_FLAG | 儲存裁切的某個區域的狀態 |
CLIP_TO_LAYER_SAVE_FLAG | 儲存預先設定的範圍裡的狀態 |
FULL_COLOR_LAYER_SAVE_FLAG | |
#111彩色塗層
HAS_ALPHA_LAYER_SAVE_FLAG
不透明圖層保存
RectF bounds = new RectF(0, 0, 400, 400); canvas.saveLayer(bounds, mPaint, Canvas.CLIP_TO_LAYER_SAVE_FLAG); canvas.drawColor(getResources().getColor(R.color.moss_tide)); canvas.drawBitmap(bmp, 200, 200, mPaint); canvas.restoreToCount(1); canvas.drawBitmap(bmp, 300, 200, mPaint);######運行結果###:################關於saveLayer()後面用到再詳解研究吧~這裡先知道個大概~######接著到這個###restoreToCount(int)###,這個比較簡單,直接傳入要恢復到的Layer層數, 直接就跳到對應的那一層,同時會將該層上面所有的Layer踢出棧,讓該層 成為棧頂~!比起你寫多個restore()方便快速多了~#########7.本節程式碼範例下載:######嗯,程式碼是寫著測試的,要來也沒多大意思,不過可能讀者還是想要,就貼下連結吧! #########程式碼下載###:###CanvasDemo.zip###可能你們要的是這個圖吧!哈哈~##################本節小結:#########本節是糾結了幾天才寫出來的,因為筆者一開始對這個Canvas圖層的概念也不是很清晰, 今天下午做完事捋了捋思路,晚上再加加班終於把這篇東西寫出來了,相信應該能幫助 大家更清楚的理解Canvas,進階自訂控制時也不會一頭霧水~嘿嘿,本節就到這裡, 如果有寫錯的地方歡迎提出,萬分感謝~#############