自定義控件其實很簡單1/2

尊重原創轉載請註明:From AigeStudio(http://blog.csdn.net/aigestudio)Power by Aige 侵權必究!

炮兵鎮樓

年關將至事情巨多,最近因爲安排蓄謀已已久的旅行事宜很久沒更我們的系列教程,約莫着有一個月了,這事情多起來啊閒都閒不下來~~那麼我們閒話少說,來看看這一節我們的重點,上一節因爲之前從未涉及Canvas的clipXXX方法所以我們優先對其做了一定的介紹並順帶將Path類的方法做了一個小結,如我之前所說Canvas方法可以分爲幾類,clipXXX算一類,各種drawXXX又是一類,還有一類則是對Canvas的各種變換操作,這一節我們將來具體看看關於Canvas變換操作的一些具體內容,在講解之前呢我們先來了解一個關於“層”的設計理念,爲什麼說它是一個設計理念呢?因爲在很多很多的地方,當然不止是開發,還有設計等領域你都能見到它的蹤影,那麼何爲圖層呢?大家小時候一定都畫過畫,比如下面這種:


一個松鼠、幾棵樹、兩個鳥、一個日,這幾個簡單的圖畫其實就包含了最簡單“層”的概念,由圖我們可以知道松鼠一定是在樹和地面的前面,而樹和地面的關係呢則比較模糊,可以是樹在前也可以是地面在前,日肯定是在最底層的,兩隻鳥我們按照一般邏輯可以推測在日的上一層也就是倒數第二層,那麼從底層到頂層我們就有這樣的一個層次關係:日-鳥-樹/地面-地面/樹-松鼠,這麼一說大家覺得好像也是,但是目測沒毛用啊……意義何在,別急,想像一下,這時候如果你不想要松鼠而是想放一隻貓在前面……或者你想把松鼠放在樹的後面“藏”起來……這時你就蛋疼了,不停地拿橡皮擦擦啊擦草啊草,一不小心還得把其他的擦掉一塊,這時候你就會想可以不可以有這麼一個功能能讓不同的元素通過一定的次序單獨地畫在一張大小一致“紙”上直到畫完最後一個元素後把這些所有“紙”上的元素都整合起來構成一幅完整的圖畫呢?這樣一個功能的存在能大大提高我們繪圖的效率還能實現更多的繪圖功能,基於這樣的一個假想,“層”的概念應運而生:


如上圖所示,位於最底層的是一個圓,第二層是一個藍色的橢圓,最頂層的是兩個藍色的圓,三個層中不同的元素最終構成右邊的圖像,這就是圖層最直觀也是最簡單的體現。在Android中我們可以使用Canvas的saveXXX和restoreXXX方法來模擬圖層的類似效果:

  1. public class LayerView extends View {  
  2.     private Paint mPaint;// 畫筆對象  
  3.   
  4.     private int mViewWidth, mViewHeight;// 控件寬高  
  5.   
  6.     public LayerView(Context context, AttributeSet attrs) {  
  7.         super(context, attrs);  
  8.   
  9.         // 實例化畫筆對象並設置其標識值  
  10.         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);  
  11.     }  
  12.   
  13.     @Override  
  14.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  15.         /* 
  16.          * 獲取控件寬高 
  17.          */  
  18.         mViewWidth = w;  
  19.         mViewHeight = h;  
  20.     }  
  21.   
  22.     @Override  
  23.     protected void onDraw(Canvas canvas) {  
  24.         /* 
  25.          * 繪製一個紅色矩形 
  26.          */  
  27.         mPaint.setColor(Color.RED);  
  28.         canvas.drawRect(mViewWidth / 2F - 200, mViewHeight / 2F - 200, mViewWidth / 2F + 200, mViewHeight / 2F + 200, mPaint);  
  29.   
  30.         /* 
  31.          * 保存畫布並繪製一個藍色的矩形 
  32.          */  
  33.         canvas.save();  
  34.         mPaint.setColor(Color.BLUE);  
  35.         canvas.drawRect(mViewWidth / 2F - 100, mViewHeight / 2F - 100, mViewWidth / 2F + 100, mViewHeight / 2F + 100, mPaint);  
  36.         canvas.restore();  
  37.     }  
  38. }  
如代碼所示,我們先在onDraw方法中繪製一個紅色的大矩形再保存畫布繪製了一個藍色的小矩形:


此時我們嘗試旋轉一下我們的畫布:

  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.     // 旋轉畫布  
  4.     canvas.rotate(30);  
  5.   
  6.     /* 
  7.      * 繪製一個紅色矩形 
  8.      */  
  9.     mPaint.setColor(Color.RED);  
  10.     canvas.drawRect(mViewWidth / 2F - 200, mViewHeight / 2F - 200, mViewWidth / 2F + 200, mViewHeight / 2F + 200, mPaint);  
  11.   
  12.     /* 
  13.      * 保存畫布並繪製一個藍色的矩形 
  14.      */  
  15.     canvas.save();  
  16.     mPaint.setColor(Color.BLUE);  
  17.     canvas.drawRect(mViewWidth / 2F - 100, mViewHeight / 2F - 100, mViewWidth / 2F + 100, mViewHeight / 2F + 100, mPaint);  
  18.     canvas.restore();  
  19. }  
如代碼所示順時針旋轉30度,這裏要注意,我們在對Canvas(實際上大多數Android中的其他與座標有關的)進行座標操作的時候,默認情況下是以控件的左上角爲原點座標的,效果如下:


可以看到兩個矩形都一起飛了,可是我們只想讓藍色的飛而紅色的不動怎麼辦呢?很簡單,我們只在保存的圖層裏操作即可:

  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.     /* 
  4.      * 繪製一個紅色矩形 
  5.      */  
  6.     mPaint.setColor(Color.RED);  
  7.     canvas.drawRect(mViewWidth / 2F - 200, mViewHeight / 2F - 200, mViewWidth / 2F + 200, mViewHeight / 2F + 200, mPaint);  
  8.   
  9.     /* 
  10.      * 保存畫布並繪製一個藍色的矩形 
  11.      */  
  12.     canvas.save();  
  13.     mPaint.setColor(Color.BLUE);  
  14.   
  15.     // 旋轉畫布  
  16.     canvas.rotate(30);  
  17.     canvas.drawRect(mViewWidth / 2F - 100, mViewHeight / 2F - 100, mViewWidth / 2F + 100, mViewHeight / 2F + 100, mPaint);  
  18.     canvas.restore();  
  19. }  
可以看到,我們只針對藍色的矩形進行了旋轉:


至此結合上一節對Canvas的一些原理闡述我們該對它有個全新的認識,之前我們一直稱其爲畫布,其實更準確地說Canvas是一個容器,如果把Canvas理解成畫板,那麼我們的“層”就像張張夾在畫板上的透明的紙,而這些紙對應到Android則是一個個封裝在Canvas中的Bitmap。

除了save()方法Canvas還給我們提供了一系列的saveLayerXXX方法給我們保存畫布,與save()方法不同的是,saveLayerXXX方法會將所有的操作存到一個新的Bitmap中而不影響當前Canvas的Bitmap,而save()方法則是在當前的Bitmap中進行操作,並且只能針對Bitmap的形變和裁剪進行操作,saveLayerXXX方法則無所不能,當然兩者還有很多的不同,我們稍作講解。雖然save和saveLayerXXX方法有着很大的區別但是在一般應用上兩者能實現的功能是差不多,上面的代碼我們也可以改成這樣:

  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.     /* 
  4.      * 繪製一個紅色矩形 
  5.      */  
  6.     mPaint.setColor(Color.RED);  
  7.     canvas.drawRect(mViewWidth / 2F - 200, mViewHeight / 2F - 200, mViewWidth / 2F + 200, mViewHeight / 2F + 200, mPaint);  
  8.   
  9.     /* 
  10.      * 保存畫布並繪製一個藍色的矩形 
  11.      */  
  12.     canvas.saveLayer(00, mViewWidth, mViewHeight, null, Canvas.ALL_SAVE_FLAG);  
  13.     mPaint.setColor(Color.BLUE);  
  14.   
  15.     // 旋轉畫布  
  16.     canvas.rotate(30);  
  17.     canvas.drawRect(mViewWidth / 2F - 100, mViewHeight / 2F - 100, mViewWidth / 2F + 100, mViewHeight / 2F + 100, mPaint);  
  18.     canvas.restore();  
  19. }  
當然實現的效果也是一樣的就不多說了。saveLayer可以讓我們自行設定需要保存的區域,比如我們可以只保存和藍色方塊一樣的區域:

  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.     /* 
  4.      * 繪製一個紅色矩形 
  5.      */  
  6.     mPaint.setColor(Color.RED);  
  7.     canvas.drawRect(mViewWidth / 2F - 200, mViewHeight / 2F - 200, mViewWidth / 2F + 200, mViewHeight / 2F + 200, mPaint);  
  8.   
  9.     /* 
  10.      * 保存畫布並繪製一個藍色的矩形 
  11.      */  
  12.     canvas.saveLayer(mViewWidth / 2F - 100, mViewHeight / 2F - 100, mViewWidth / 2F + 100, mViewHeight / 2F + 100null, Canvas.ALL_SAVE_FLAG);  
  13.     mPaint.setColor(Color.BLUE);  
  14.   
  15.     // 旋轉畫布  
  16.     canvas.rotate(30);  
  17.     canvas.drawRect(mViewWidth / 2F - 100, mViewHeight / 2F - 100, mViewWidth / 2F + 100, mViewHeight / 2F + 100, mPaint);  
  18.     canvas.restore();  
  19. }  
這時候如果你運行就會發現藍色的方塊已經不見了,因爲我們圖層的大小就這麼點,超出的部分就不能被顯示了,這時我們改小畫布旋轉:

  1. canvas.rotate(5);  
你就可以看到旋轉後的藍色方塊的一角:


是不是有點類似於clipRect的效果呢?那麼很多朋友會好奇爲什麼會有這樣一種保存一小塊畫布區域的功能呢?其實原因很簡單,上面我們說了saveLayerXXX方法會將操作保存到一個新的Bitmap中,而這個Bitmap的大小取決於我們傳入的參數大小,Bitmap是個相當危險的對象,很多朋友在操作Bitmap時不太理解其原理經常導致OOM,在saveLayer時我們會依據傳入的參數獲取一個相同大小的Bitmap,雖然這個Bitmap是空的但是其會佔用一定的內存空間,我們希望儘可能小地保存該保存的區域,而saveLayer則提供了這樣的功能,順帶提一下,onDraw方法傳入的Canvas對象的Bitmap在Android沒引入HW之前理論上是無限大的,實際上其依然是根據你的圖像來不斷計算的,而在引入HW之後,該Bitmap受到限制,具體多大大家可以嘗試畫一個超長的path運行下你就可以在Logcat中看到warning。

好了,閒話不扯,接着說,除了saveLayer,Canvas還提供了一個saveLayerAlpha方法,顧名思義,該方法可以在我們保存畫布時設置畫布的透明度:

  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.     /* 
  4.      * 繪製一個紅色矩形 
  5.      */  
  6.     mPaint.setColor(Color.RED);  
  7.     canvas.drawRect(mViewWidth / 2F - 200, mViewHeight / 2F - 200, mViewWidth / 2F + 200, mViewHeight / 2F + 200, mPaint);  
  8.   
  9.     /* 
  10.      * 保存畫布並繪製一個藍色的矩形 
  11.      */  
  12.     canvas.saveLayerAlpha(mViewWidth / 2F - 100, mViewHeight / 2F - 100, mViewWidth / 2F + 100, mViewHeight / 2F + 1000x55, Canvas.ALL_SAVE_FLAG);  
  13.     mPaint.setColor(Color.BLUE);  
  14.   
  15.     // 旋轉畫布  
  16.     canvas.rotate(5);  
  17.     canvas.drawRect(mViewWidth / 2F - 100, mViewHeight / 2F - 100, mViewWidth / 2F + 100, mViewHeight / 2F + 100, mPaint);  
  18.     canvas.restore();  
  19. }  
我們將saveLayer替換成saveLayerAlpha並設置透明值爲0x55,運行可得如下效果:


可見藍色的方塊被半透明瞭。such easy!如果大家留心,會發現save()也有個重載方法save (int saveFlags),而saveLayer和saveLayerAlpha你也會發現又一個類似的參數,那麼這個參數是幹嘛用的呢?在Canvas中有六個常量值:


這六個常量值分別標識了我們在調用restore方法後還原什麼,六個標識位除了CLIP_SAVE_FLAG、MATRIX_SAVE_FLAG和ALL_SAVE_FLAG是save和saveLayerXXX方法都通用外其餘三個只能使saveLayerXXX方法有效,ALL_SAVE_FLAG很簡單也是我們新手級常用的標識保存所有,CLIP_SAVE_FLAG和MATRIX_SAVE_FLAG也很好理解,一個是裁剪的標識位一個是變換的標識位,CLIP_TO_LAYER_SAVE_FLAG、FULL_COLOR_LAYER_SAVE_FLAG和HAS_ALPHA_LAYER_SAVE_FLAG只對saveLayer和saveLayerAlpha有效,CLIP_TO_LAYER_SAVE_FLAG表示對當前圖層執行裁剪操作需要對齊圖層邊界,FULL_COLOR_LAYER_SAVE_FLAG表示當前圖層的色彩模式至少需要是8位色,而HAS_ALPHA_LAYER_SAVE_FLAG表示在當前圖層中將需要使用逐像素Alpha混合模式,關於色彩深度和Alpha混合大家可以參考維基百科,這裏就不多說,這些標識位,特別是layer的標識位,大大超出了本系列的範疇,我就不多說了,平時使用大家可以直接ALL_SAVE_FLAG,有機會將單獨開一篇剖析Android對色彩的處理。

所有的save、saveLayer和saveLayerAlpha方法都有一個int型的返回值,該返回值作爲一個標識給與了一個你當前保存操作的唯一ID編號,我們可以利用restoreToCount(int saveCount)方法來指定在還原的時候還原哪一個保存操作:

  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.     /* 
  4.      * 繪製一個紅色矩形 
  5.      */  
  6.     mPaint.setColor(Color.RED);  
  7.     canvas.drawRect(mViewWidth / 2F - 200, mViewHeight / 2F - 200, mViewWidth / 2F + 200, mViewHeight / 2F + 200, mPaint);  
  8.   
  9.     /* 
  10.      * 保存並裁剪畫布填充綠色 
  11.      */  
  12.     int saveID1 = canvas.save(Canvas.CLIP_SAVE_FLAG);  
  13.     canvas.clipRect(mViewWidth / 2F - 200, mViewHeight / 2F - 200, mViewWidth / 2F + 200, mViewHeight / 2F + 200);  
  14.     canvas.drawColor(Color.GREEN);  
  15.   
  16.     /* 
  17.      * 保存畫布並旋轉後繪製一個藍色的矩形 
  18.      */  
  19.     int saveID2 = canvas.save(Canvas.MATRIX_SAVE_FLAG);  
  20.   
  21.     // 旋轉畫布  
  22.     canvas.rotate(5);  
  23.     mPaint.setColor(Color.BLUE);  
  24.     canvas.drawRect(mViewWidth / 2F - 100, mViewHeight / 2F - 100, mViewWidth / 2F + 100, mViewHeight / 2F + 100, mPaint);  
  25.   
  26.     canvas.restoreToCount(saveID1);  
  27. }  
如上代碼所示,我們第一次保存畫布並獲取其返回值:

  1. int saveID1 = canvas.save(Canvas.CLIP_SAVE_FLAG);  
然後對畫布進行裁剪並填色,第二次保存畫布並獲取其返回值:

  1. int saveID2 = canvas.save(Canvas.MATRIX_SAVE_FLAG);  
然後繪製一個藍色的矩形,最後我們只還原了了saveID1的畫布狀態,運行一下你會發現好像效果沒什麼不同啊:


然後我們試試

  1. canvas.restoreToCount(saveID2);  
發現效果還是一樣…………很多童鞋就困惑了,是哪不對麼?沒有,其實都是對的,你覺得奇怪是你還不理解save和restore,這裏我在restore之後再繪製一個矩形:

  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.     /* 
  4.      * 繪製一個紅色矩形 
  5.      */  
  6.     mPaint.setColor(Color.RED);  
  7.     canvas.drawRect(mViewWidth / 2F - 200, mViewHeight / 2F - 200, mViewWidth / 2F + 200, mViewHeight / 2F + 200, mPaint);  
  8.   
  9.     /* 
  10.      * 保存並裁剪畫布填充綠色 
  11.      */  
  12.     int saveID1 = canvas.save(Canvas.CLIP_SAVE_FLAG);  
  13.     canvas.clipRect(mViewWidth / 2F - 200, mViewHeight / 2F - 200, mViewWidth / 2F + 200, mViewHeight / 2F + 200);  
  14.     canvas.drawColor(Color.GREEN);  
  15.   
  16.     /* 
  17.      * 保存畫布並旋轉後繪製一個藍色的矩形 
  18.      */  
  19.     int saveID2 = canvas.save(Canvas.MATRIX_SAVE_FLAG);  
  20.   
  21.     // 旋轉畫布  
  22.     canvas.rotate(5);  
  23.     mPaint.setColor(Color.BLUE);  
  24.     canvas.drawRect(mViewWidth / 2F - 100, mViewHeight / 2F - 100, mViewWidth / 2F + 100, mViewHeight / 2F + 100, mPaint);  
  25.   
  26.     canvas.restoreToCount(saveID2);  
  27.   
  28.     mPaint.setColor(Color.YELLOW);  
  29.     canvas.drawRect(mViewWidth / 2F - 400, mViewHeight / 2F - 400, mViewWidth / 2F + 400, mViewHeight / 2F + 400, mPaint);  
  30. }  
可以看到我在

  1. canvas.restoreToCount(saveID2);  
之後又繪製了一個黃色的矩形:


可是不管你如何調大這個矩形,你會發現它就那麼大點……也就是說,這個黃色的矩形其實是被clip掉了,進一步說,我們繪製黃色矩形的這個操作其實說白了就是在saveID1的狀態下進行的。前面我們曾說過save和saveLayerXXX方法有着本質的區別,saveLayerXXX方法會將所有操作在一個新的Bitmap中進行,而save則是依靠stack棧來進行,假設我們有如下代碼:

  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.     /* 
  4.      * 保存並裁剪畫布填充綠色 
  5.      */  
  6.     int saveID1 = canvas.save(Canvas.CLIP_SAVE_FLAG);  
  7.     canvas.clipRect(mViewWidth / 2F - 300, mViewHeight / 2F - 300, mViewWidth / 2F + 300, mViewHeight / 2F + 300);  
  8.     canvas.drawColor(Color.YELLOW);  
  9.   
  10.     /* 
  11.      * 保存並裁剪畫布填充綠色 
  12.      */  
  13.     int saveID2 = canvas.save(Canvas.CLIP_SAVE_FLAG);  
  14.     canvas.clipRect(mViewWidth / 2F - 200, mViewHeight / 2F - 200, mViewWidth / 2F + 200, mViewHeight / 2F + 200);  
  15.     canvas.drawColor(Color.GREEN);  
  16.   
  17.     /* 
  18.      * 保存畫布並旋轉後繪製一個藍色的矩形 
  19.      */  
  20.     int saveID3 = canvas.save(Canvas.MATRIX_SAVE_FLAG);  
  21.     canvas.rotate(5);  
  22.     mPaint.setColor(Color.BLUE);  
  23.     canvas.drawRect(mViewWidth / 2F - 100, mViewHeight / 2F - 100, mViewWidth / 2F + 100, mViewHeight / 2F + 100, mPaint);  
  24. }  
此時,在Canvas內部會有這樣的一個Stack棧:


Canvas會默認保存一個底層的空間給我們繪製一些東西,當我們沒有調用save方法時所有的繪圖操作都在這個Default Stack ID中進行,每當我們調用一次save就會往Stack中存入一個ID,將其後所有的操作都在這個ID所指向的空間進行直到我們調用restore方法還原操作,上面代碼我們save了三次且沒有restore,stack的結構就如上圖所示,此時如果我們繼續繪製東西,比如:

  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.     /* 
  4.      * 保存並裁剪畫布填充綠色 
  5.      */  
  6.     int saveID1 = canvas.save(Canvas.CLIP_SAVE_FLAG);  
  7.     canvas.clipRect(mViewWidth / 2F - 300, mViewHeight / 2F - 300, mViewWidth / 2F + 300, mViewHeight / 2F + 300);  
  8.     canvas.drawColor(Color.YELLOW);  
  9.   
  10.     /* 
  11.      * 保存並裁剪畫布填充綠色 
  12.      */  
  13.     int saveID2 = canvas.save(Canvas.CLIP_SAVE_FLAG);  
  14.     canvas.clipRect(mViewWidth / 2F - 200, mViewHeight / 2F - 200, mViewWidth / 2F + 200, mViewHeight / 2F + 200);  
  15.     canvas.drawColor(Color.GREEN);  
  16.   
  17.     /* 
  18.      * 保存畫布並旋轉後繪製一個藍色的矩形 
  19.      */  
  20.     int saveID3 = canvas.save(Canvas.MATRIX_SAVE_FLAG);  
  21.   
  22.     // 旋轉畫布  
  23.     canvas.rotate(5);  
  24.     mPaint.setColor(Color.BLUE);  
  25.     canvas.drawRect(mViewWidth / 2F - 100, mViewHeight / 2F - 100, mViewWidth / 2F + 100, mViewHeight / 2F + 100, mPaint);  
  26.       
  27.     mPaint.setColor(Color.CYAN);  
  28.     canvas.drawRect(mViewWidth / 2F, mViewHeight / 2F, mViewWidth / 2F + 200, mViewHeight / 2F + 200, mPaint);  
  29. }  
我們在saveID3之後又畫了一個青色的矩形,只要你不是傻子明眼都能看出這段代碼是在saveID3所標識的空間中繪製的,因此其必然會受到saveID3的約束旋轉:


除此之外,大家還可以很明顯的看到,這個矩形除了被旋轉,還被clip了~也就是說saveID1、saveID2也同時對其產生了影響,此時我們再次嘗試在saveID2繪製完我們想要的東西后將其還原:

  1. /* 
  2.  * 保存並裁剪畫布填充綠色 
  3.  */  
  4. int saveID2 = canvas.save(Canvas.CLIP_SAVE_FLAG);  
  5. canvas.clipRect(mViewWidth / 2F - 200, mViewHeight / 2F - 200, mViewWidth / 2F + 200, mViewHeight / 2F + 200);  
  6. canvas.drawColor(Color.GREEN);  
  7. canvas.restore();  
同時將青色的矩形變大一點:

  1. canvas.drawRect(mViewWidth / 2F, mViewHeight / 2F, mViewWidth / 2F + 400, mViewHeight / 2F + 400, mPaint);  
這時我們得到什麼樣的效果呢:


其實猜都猜得到,saveID2已經不再對下面的saveID3起作用了,也就是說當我們調用canvas.restore()後標誌着上一個save操作的結束或者說回滾了。同理,我們再把saveID1也restore:

  1. /* 
  2.  * 保存並裁剪畫布填充綠色 
  3.  */  
  4. int saveID1 = canvas.save(Canvas.CLIP_SAVE_FLAG);  
  5. canvas.clipRect(mViewWidth / 2F - 300, mViewHeight / 2F - 300, mViewWidth / 2F + 300, mViewHeight / 2F + 300);  
  6. canvas.drawColor(Color.YELLOW);  
  7. canvas.restore();  
這時saveID3將徹底不再受前面操作的影響:


如果我們在繪製青色的矩形之前將saveID3也還原:

  1. /* 
  2.  * 保存畫布並旋轉後繪製一個藍色的矩形 
  3.  */  
  4. int saveID3 = canvas.save(Canvas.MATRIX_SAVE_FLAG);  
  5. canvas.rotate(5);  
  6. mPaint.setColor(Color.BLUE);  
  7. canvas.drawRect(mViewWidth / 2F - 100, mViewHeight / 2F - 100, mViewWidth / 2F + 100, mViewHeight / 2F + 100, mPaint);  
  8. canvas.restore();  
那麼這個青色的矩形將會被繪製在Default Stack ID上而不受其他save狀態的影響:


上面我們提到的restoreToCount(int saveCount)方法接受一個標識值,我們可以根據這個標識值來還原特定的棧空間,效果類似就不多說了。每當我們調用restore還原Canvas,對應的save棧空間就會從Stack中彈出去,Canvas提供了getSaveCount()方法來爲我們提供查詢當前棧中有多少save的空間:

  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.     System.out.println(canvas.getSaveCount());  
  4.     /* 
  5.      * 保存並裁剪畫布填充綠色 
  6.      */  
  7.     int saveID1 = canvas.save(Canvas.CLIP_SAVE_FLAG);  
  8.     System.out.println(canvas.getSaveCount());  
  9.     canvas.clipRect(mViewWidth / 2F - 300, mViewHeight / 2F - 300, mViewWidth / 2F + 300, mViewHeight / 2F + 300);  
  10.     canvas.drawColor(Color.YELLOW);  
  11.     canvas.restore();  
  12.   
  13.     /* 
  14.      * 保存並裁剪畫布填充綠色 
  15.      */  
  16.     int saveID2 = canvas.save(Canvas.CLIP_SAVE_FLAG);  
  17.     System.out.println(canvas.getSaveCount());  
  18.     canvas.clipRect(mViewWidth / 2F - 200, mViewHeight / 2F - 200, mViewWidth / 2F + 200, mViewHeight / 2F + 200);  
  19.     canvas.drawColor(Color.GREEN);  
  20.     canvas.restore();  
  21.   
  22.     /* 
  23.      * 保存畫布並旋轉後繪製一個藍色的矩形 
  24.      */  
  25.     int saveID3 = canvas.save(Canvas.MATRIX_SAVE_FLAG);  
  26.     System.out.println(canvas.getSaveCount());  
  27.   
  28.     // 旋轉畫布  
  29.     canvas.rotate(5);  
  30.     mPaint.setColor(Color.BLUE);  
  31.     canvas.drawRect(mViewWidth / 2F - 100, mViewHeight / 2F - 100, mViewWidth / 2F + 100, mViewHeight / 2F + 100, mPaint);  
  32.     canvas.restore();  
  33.   
  34.     System.out.println(canvas.getSaveCount());  
  35.     mPaint.setColor(Color.CYAN);  
  36.     canvas.drawRect(mViewWidth / 2F, mViewHeight / 2F, mViewWidth / 2F + 400, mViewHeight / 2F + 400, mPaint);  
  37. }  
運行後你會看到Logcat的如下輸出:


OK,對層的瞭解到此爲止,接下來我們主要來看看Canvas中的變換操作,說起變換,無非就幾種:平移、旋轉、縮放和錯切,而我們的Canvas也繼承了變換的精髓,同樣提供了這幾種相應的方法,前面的很多章節我們也都用到了,像translate(float dx, float dy)方法平移畫布用了無數次,這裏再次強調,translate方法會改變畫布的原點座標,原點座標對變換的影響彌足輕重,前面也多次強調了!scale(float sx, float sy)縮放也很好理解,但是它有一個重載方法scale(float sx, float sy, float px, float py),後兩個參數用於指定縮放的中心點,前兩個參數用於指定橫縱向的縮放比率值在0-1之間爲縮小:

  1. public class LayerView extends View {  
  2.     private Bitmap mBitmap;// 位圖對象  
  3.   
  4.     private int mViewWidth, mViewHeight;// 控件寬高  
  5.   
  6.     public LayerView(Context context, AttributeSet attrs) {  
  7.         super(context, attrs);  
  8.   
  9.         // 從資源中獲取位圖對象  
  10.         mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.z);  
  11.     }  
  12.   
  13.     @Override  
  14.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  15.         /* 
  16.          * 獲取控件寬高 
  17.          */  
  18.         mViewWidth = w;  
  19.         mViewHeight = h;  
  20.   
  21.         // 縮放位圖與控件一致  
  22.         mBitmap = Bitmap.createScaledBitmap(mBitmap, mViewWidth, mViewHeight, true);  
  23.     }  
  24.   
  25.     @Override  
  26.     protected void onDraw(Canvas canvas) {  
  27.         canvas.save(Canvas.MATRIX_SAVE_FLAG);  
  28.         canvas.scale(1.0F, 1.0F);  
  29.         canvas.drawBitmap(mBitmap, 00null);  
  30.         canvas.restore();  
  31.     }  
  32. }  
當縮放比率爲1時表示不縮放:


我們改變下縮放比率:

  1. canvas.scale(0.8F, 0.35F);  
此時畫面效果如下:


可以看到縮放中心在左上角,我們可以使用scale的重載方法更改縮放中心:

  1. canvas.scale(0.8F, 0.35F, mViewWidth, 0);  
效果如下,很好理解:


rotate(float degrees)和重載方法rotate(float degrees, float px, float py)類似前面也用過不少就不多說了,沒接觸過的只有skew(float sx, float sy)錯切方法,關於錯切的概念前面我們都有講過很多,其實知道原理,方法再怎麼變都不難:

  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.     canvas.save(Canvas.MATRIX_SAVE_FLAG);  
  4.     canvas.skew(0.5F, 0F);  
  5.     canvas.drawBitmap(mBitmap, 00null);  
  6.     canvas.restore();  
  7. }  
兩個參數與scale類似表示橫縱向的錯切比率,上面代碼的效果如下:


在之前的章節中我們曾講過一個類似的用來專門操作變換的玩意Matrix,之前我也說過我們會在很多地方用到這畜生,Canvas也提供了對應的方法來便於我們設置Matrix直接變換Canvas:

  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.     canvas.save(Canvas.MATRIX_SAVE_FLAG);  
  4.     Matrix matrix = new Matrix();  
  5.     matrix.setScale(0.8F, 0.35F);  
  6.     matrix.postTranslate(100100);  
  7.     canvas.setMatrix(matrix);  
  8.     canvas.drawBitmap(mBitmap, 00null);  
  9.     canvas.restore();  
  10. }  
運行效果如下:


好了,關於Canvas的保存還原和變換的簡單操作就介紹到這吧,剩些的一些draw方法都很好理解簡單,難的我前面已經陸續穿插講了,作爲自定義控件的一部分,繪製我們用了六節的篇幅去介紹,內容多主要是Android給我們提供了很完善的接口方法以至於你在上層開發的時候壓根不用去管什麼源碼實現,接下來的章節我們會開始進入另一個重點:控件的測量,不過在此之前我想給大家結合前面學到的一些知識來做一個關於翻頁效果的小例子。

該部分源碼下載:傳送門

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章