概述
畫2D圖形有兩種方法:
- 把圖片和動畫設置到布局文件的View里,整個繪圖過程由系統(tǒng)的視圖樹處理,我們只需要定義好圖形。適合于不需要動態(tài)改變的簡單圖形,如靜態(tài)圖片和定義好的動畫
- 在Canvas上繪圖。在類的ondraw方法內獲取Canvas,或者調用Canvas.drawXXX方法。適合重復重繪自己的圖形,如視頻。既可以通過自定義View通過invalidate,在UI線程里回調onDraw方法,也可以通過SurfaceView啟動別的線程調用invalidate。
用Canvas繪圖
Canvas實際上是封裝了各種draw方法的類,調用draw方法把圖形繪制到底層的Surface上,即繪制在Window上。
- 在onDraw方法里使用Canvas繪制比較簡單,不再贅述。
- 在SurfaceView通過lockCanvas獲取Canvas,同上。
- 自定義Canvas。創(chuàng)建Canvas時,需要設置Bitmap。Bitmap中有內存指針mNativePtr和屬性,可以簡單視為一小塊內存。從前篇知道,繪制其實是寫一塊共享內存,而Bitmap可視為共享內存的一小塊。
這個例子中構造了兩個Canvas和一個Bitmap,分別調用其draw方法,先是mCanvas往Bitmap里繪制一個方塊,再在onDraw方法內調用canvas.drawBitmap繪制這個方塊。
思考一個問題,為什么mCanvas需要設置Bitmap?
很簡單,因為它沒有持有一塊內存地址,自然沒法繪制。來看一下draw的起點ViewRootImpl(軟件繪制,不開啟硬件加速下)。
這個通過mSurface.lockCanvas返回的Canvas是View.draw的canvas變量,所以當1,2情況時,Canvas都持有一個Bitmap,指向共享內存里的某一小塊,當調用Canvas.draw方法時就能繪制出東西。但對于自定義Canvas來說并不是,即使設置一個Bitmap和繪制了Bitmap,但不往共享內存上寫,屏幕上是不會顯示的,SurfaceView同理,通過Surface.lockCanvas獲取持有共享內存的Canvas,繪制完畢后調用Surface.unlockCanvasAndPost把繪制內容顯示到surface上并release掉Canvas。
順帶一提Canvas.save和Canvas.restore方法,如下Demo
效果圖如
畫的是三個顏色和旋轉角度都不同的小方形。
步驟1把默認坐標系旋轉20°,畫出第一個藍色的方形,步驟2保存當前的matrix(旋轉了20°),繼續(xù)旋轉20°,此時坐標系已經旋轉了40°,畫出第二個黃色的方塊,步驟3,恢復上一步保存的matrix(旋轉了20°),此時坐標系還是旋轉了20°,步驟4,再旋轉40°,此時坐標系旋轉了60°,畫出第三個黑色方塊。
Canvas.save用于保存當前matrix和clip,Canvas.restore用于恢復上次保存的matrix和clip。
Drawable
Drawable是一個能畫出來的物體的抽象,使用前需要調用setBounds確定位置和大小,通過getIntrinsicHeight和getIntrinsicWidth取到實際大小。Drawable可以有幾種形式存在:Bitmap、Nine Patch、Vector、Shape、Layers等。
從Resource.getDrawable會判斷是否.xml結尾,不是的話走6,7步,如果從xml中讀取,需要getResource.getDrawable -> ResourceImpl.loadDrawableForCookie -> drawable.createFromXml -> DrawableInflater.inflateFromXmlForDensity -> drawable.inflateFromTag
看一下Shape實現(xiàn)類GradientDrawable的inflate實現(xiàn),讀取各項屬性并賦值,到draw方法。
調用canvas.drawRect把mRect畫出來,而mRect的賦值在ensureValidRect。[圖片上傳失敗...(image-a25af0-1515826613001)]
bounds在哪里設置的?答案是ImageView.updateDrawable內,會調用Drawable.getIntrinsicHeight賦值(從xml中size屬性讀取),再調用configureBounds -> setBounds,如果使用的不是ImageView,一定要在draw之前調用setBounds,否則size就會出錯。
回到loadDrawableForCookie,再看一下6,7步加載圖片的過程,通過AssetManager讀取圖片流數(shù)據(jù),通過Drawable.createFromResourceStream這個我們經常使用的方法獲取到Drawable。
取到屏幕密度之后調用BitmapFactory.decodeResourcesStream,計算密度后調用native創(chuàng)建Bitmap,感興趣的同學可以看下更具體的分析文章(如理解Bitmap)。
總結
本文探究了兩點
- Canvas能繪制的原因,View.Canvas與new Canvas的區(qū)別。
- 創(chuàng)建Drawable的過程
參考資料
Android 7.1.1 源碼
Android 官方文檔,Canvas and Drawable, Drawable等