androidviewcanvas
Ⅰ Android 自定義View之Layout過程
系列文章:
在上篇文章: Android 自定義View之Measure過程 ,我們分析了Measure過程,本次將會掀開承上啟下的Layout過程神秘面紗,
通過本篇文章,你將了解到:
在上篇文章的比喻里,我們說過:
該ViewGroup 重寫了onMeasure(xx)和onLayout(xx)方法:
同時,當layout 執行結束,清除PFLAG_FORCE_LAYOUT標記,該標記會影響Measure過程是否需要執行onMeasure。
該View 重寫了onMeasure(xx)和onLayout(xx)方法:
MyViewGroup里添加了MyView、Button兩個控制項,最終運行的效果如下:
可以看出,MyViewGroup 里子布局的是橫向擺放的。我們重點關注Layout過程。實際上,MyViewGroup里我們只重寫了onLayout(xx)方法,MyView也是重寫了onLayout(xx)方法。
接下來,分析View Layout過程。
與Measure過程類似,連接ViewGroup onLayout(xx)和View onLayout(xx)之間的橋梁是View layout(xx)。
可以看出,最終都調用了setFrame(xx)方法。
對於Measure過程在onMeasure(xx)里記錄了尺寸的值,而對於Layout過程則在layout(xx)里記錄了坐標值,具體來說是在setFrame(xx)里,該方法兩個重點地方:
View.onLayout(xx)是空實現
從layout(xx)和onLayout(xx)聲明可知,這兩個方法都是可以被重寫的,接下來看看ViewGroup是否重寫了它們。
ViewGroup.layout(xx)雖然重寫了layout(xx),但是僅僅做了簡單判斷,最後還是調用了View.layout(xx)。
這重寫後將onLayout變為抽象方法,也就是說繼承自ViewGroup的類必須重寫onLayout(xx)方法。
我們以FrameLayout為例,分析其onLayout(xx)做了什麼。
FrameLayout.onLayout(xx)為子布局Layout的時候,起始坐標都是以FrameLayout為基準,並沒有記錄上一個子布局佔了哪塊位置,因此子布局的擺放位置可能會重疊,這也是FrameLayout布局特性的由來。而我們之前的Demo在水平方向上記錄了上一個子布局的擺放位置,下一個擺放時只能在它之後,因此就形成了水平擺放的功能。
由此類推,我們常說的某個子布局在父布局裡的哪個位置,決定這個位置的即是ViewGroup.onLayout(xx)。
上邊我們分析了View.layout(xx)、View.onLayout(xx)、ViewGroup.layout(xx)、ViewGroup.onLayout(xx),這四者什麼關系呢?
View.layout(xx)
View.onLayout(xx)
ViewGroup.layout(xx)
ViewGroup.onLayout(xx)
View/ViewGroup 子類需要重寫哪些方法:
用圖表示:
通過上述的描述,我們發現Measure過程和Layout過程里定義的方法比較類似:
它倆的套路比較類似:measure(xx)、layout(xx)一般不需要我們重寫,measure(xx)里調用onMeasure(xx),layout(xx)為調用者設置坐標值。
若是ViewGroup:onMeasure(xx)里遍歷子布局,並測量每個子布局,最後將結果匯總,設置自己測量的尺寸;onLayout(xx)里遍歷子布局,並設置每個子布局的坐標。
若是View:onMeasure(xx)則測量自身,並存儲測量尺寸;onLayout(xx)不需要做什麼。
Measure過程雖然比Layout過程復雜,但仔細分析後就會發現其本質就是為了設置兩個成員變數:
而Layout過程雖然比較簡單,其本質是為了設置坐標值
將Measure設置的變數和Layout設置的變數聯系起來:
此外,Measure過程通過設置PFLAG_LAYOUT_REQUIRED 標記來告訴需要進行onLayout,而Layout過程通過清除 PFLAG_FORCE_LAYOUT來告訴Measure過程不需要執行onMeasure了。
這就是Layout的承上作用
我們知道View的繪制需要依靠Canvas繪制,而Canvas是有作用區域限制的。例如我們使用:
Cavas繪制的起點是哪呢?
對於硬體繪制加速來說:正是通過Layout過程中設置的RenderNode坐標。
而對於軟體繪制來說:
關於硬體繪制加速/軟體繪制 後續文章會分析。
這就是Layout的啟下作用
以上即是Measure、Layout、Draw三者的內在聯系。
當然Layout的"承上"還需要考慮margin、gravity等參數的影響。具體用法參見最開始的Demo。
getMeasuredWidth()/getMeasuredHeight 與 getWidth/getHeight區別
我們以獲取width為例,分別來看看其方法:
getMeasuredWidth():獲取測量的寬,屬於"臨時值"
getWidth():獲取View真實的寬
在Layout過程之前,getWidth() 默認為0
何時可以獲取真實的寬、高
下篇將分析Draw()過程,我們將分析"一切都是draw出來的"道理
本篇基於 Android 10.0
Ⅱ Android繪圖基礎--Canvas和Drawable
Github鏈接
畫2D圖形有兩種方法:
Canvas實際上是封裝了各種draw方法的類,調用draw方法把圖形繪制到底層的Surface上,即繪制在Window上。
這個例子中構造了兩個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°),繼續旋轉20°,此時坐標系已經旋轉了40°,畫出第二個黃色的方塊,步驟3,恢復上一步保存的matrix(旋轉了20°),此時坐標系還是旋轉了20°,步驟4,再旋轉40°,此時坐標系旋轉了60°,畫出第三個黑色方塊。
Canvas.save用於保存當前matrix和clip,Canvas.restore用於恢復上次保存的matrix和clip。
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實現類GradientDrawable的inflate實現,讀取各項屬性並賦值,到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讀取圖片流數據,通過Drawable.createFromResourceStream這個我們經常使用的方法獲取到Drawable。
取到屏幕密度之後調用BitmapFactory.decodeResourcesStream,計算密度後調用native創建Bitmap,感興趣的同學可以看下更具體的分析文章(如 理解Bitmap )。
本文探究了兩點
Android 7.1.1 源碼
Android 官方文檔, Canvas and Drawable , Drawable 等
Ⅲ android canvas view重繪 我在一個activity中調用了canvas。在canvas中我點擊事件需要重新繪制當前view
除了SurfaceView,其它的都必須通過調用View.invalidate方法刷新View
所以不能直接執行moren(canvas),需要在onTouchEvent調用invalidate
Ⅳ Android canvas繪制的可滑動刻度尺
首先放上所有成員變數
以上用到的方法
本Demo是使用Canvas進行繪制刻度,為了適配不同寬度的屏幕,所以小的刻度使用從中心指針處向兩邊繪制到屏幕邊界的方法,使用GestureDetector接管View的觸摸事件,在onScroll中修改體重值並不斷進行重繪,達到刻度尺滾動的效果。並在onFling中接收拋動的事件,開啟一個屬性動畫達到平滑的效果。由於我沒有在GestureDetector中找到能響應不fling的up事件,所以要自己手動在onTouchEvent中寫ACIONT_UP時的動作
遮罩層的實現方式,是drawRec 一個整個畫布大小的白色矩形,使用線性漸變,兩邊不透明到中間透明。注意,由於使用了PorterDuff.Mode.MULTIPLY來合成,所以需要使用到離屏緩沖,關於離屏緩沖可以看看 HenCoder
最後附上github: https://github.com/Pro47x/BodyWeightScaleTableView
Ⅳ android:如何用canvas在自定義view里畫圖
將構造函數改成這個試試
public iniView01(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
本人在寫的時候也遇到過代碼沒問題,但是一到虛擬機裡面運行就崩了,然後將構造函數改成這個就不會了
Ⅵ 請問各位高手,android開發中的View裡面的Ondraw函數的參數Canvas是如何獲得的
Canvascanvas=newdrawSurfaceView(activity).getHolder().lockCanvas(null);//用一個view定義一個canvas
canvas.drawLine(startX,startY,stopX,stopY,paint)//畫線,需要起始坐標和結束坐標
canvas.drawBitmap(bitmap,null,dest,paint);//一樣了,dest定義了起始和結束
canvas是指一個畫布對象,你需要給定大小和「畫」這個動作的起始位置,然後用paint畫筆完成這個動作,比如從(0,0)(1,1)畫了一條線,再從(1,1)到(3,3)又話了一條線,兩條線連起來就是一個曲線了,畫圖的話就是第三行代碼了;如果你沒有得到這個畫布,說明你有個參數是錯誤的,沒有得到大小,或者沒有得到位置,就這樣。
如上所示,
canvas是從SurfaceView控制項裡面得到,上面有注釋,你的大小參數自己定義就行了,別超出了控制項大小