一、android繪製view的過程簡單描述 簡單描述可以解釋為:計算大小(measure),佈局座標計算(layout),繪製到螢幕(draw); 下面看看每一步的動作到底是什麼, 第一步:當activity啟動的時候,觸發初始化view過程的是由Window物件的DecorView呼叫View(具體怎樣從xml中讀取是用LayoutInflater.from(context).inflate)物件的 public final void measure(int widthMeasureSpec,
int heightMeasureSpec)方法開始的,這個方法是final型別的,也就是所有的子類都不能繼承該方法,保證android初始化view的原理不變。具體引數類值,後面會介紹。 第二步:View的measure方法 onMeasure(widthMeasureSpec, heightMeasureSpec),該方法進行實質性的view大小計算。注意:view的大小是有父view和自己的大小決定的,而不是單一決定的。這也就是為什麼ViewGroup的子類會重新該方法,比如LinearLayout等。因為他們要計算自己和子view的大小。View基類有自己的實現,只是設定大小。其實根據原始碼來看,measure的過程本質上就是把Match_parent和wrap_content轉換為實際大小 第三步:當measure結束時,回到DecorView,計算大小計算好了,那麼就開始佈局了,開始呼叫view的 public final void layout(int l, int t, int r, int b),該還是也是final型別的,目的和measure方法一樣。layout方法內部會呼叫onlayout(int
l, int t, int r, int b )方法,二ViewGroup將此方法abstract的了,所以我們繼承ViewGroup的時候,需要重新該方法。該方法的本質是通過measure計算好的大小,計算出view在螢幕上的座標點 第四步:measure過了,layout過了,那麼就要開始繪製到螢幕上了,所以開始呼叫view的 public void draw(Canvas canvas)方法,此時方法不是final了,原因是程式設計師可以自己畫,內部會呼叫ondraw,我們經常需要重寫的方法。
二、measure(View大小計算)的過程分析
1.
public final void measure(int widthMeasureSpec, int heightMeasureSpec)的引數來源及代表的意思
這個兩個引數都是有父view傳遞過來的,也就是代表了父view的大小。其實說大小不太對,應該說是建議“規格”。他有兩部分組成,第一部分:高16位表示MODE,定義在MeasureSpec類中,有三種型別,MeasureSpec.EXACTLY:表示確定大小, MeasureSpec.AT_MOST:表示最大大小,
MeasureSpec.UNSPECIFIED:不確定。第二部分:低16位表示size,既父view的大小,這就是為什麼,我們在重寫onmeasure方法是需要:int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec);這樣呼叫,因為MeasureSpec知道怎麼讀取。對於跟view(並不是我們在xml中宣告的第一個元素),而是系統的Window物件的decorVIew物件。Mode一般都為MeasureSpec.EXACTLY
,而size分別對應螢幕寬,高。也就是Window第一次掉用的view(這個view才是Xml中宣告的第一個元素),一般都是這個值,而對於子view來說,這連個值就是你在xml定義的屬性 android:layout_width和android:layout_height這個,當然了上面說過view的大小是有父view和子view共同決定的,所以這樣有點不對,但是來源於這兩個值。意思明白了,我們看看measure裡邊做什麼了 2.measure方法內部操作過程-
呼叫 onMeasure(widthMeasureSpec, heightMeasureSpec),將父view的建議大小和規格傳入,view類自己的onmeasure方法,只是根據xml中配置的大小初始化大小 ,這個就不分析了,重要的我們看看viewGroup中的onMeasure方法,ViewGroup類中沒有處理該方法,一般在他的子類中處理,比如LinearLayout中,我們以Linearlayout作為分析類。
-
Linearlayout類的onMeasure方法分兩種情況處理,1:重置排序,2:水平排序,這個大家都知道,我們分析重置排序,
-
獲取所有的子view數量,對每個子view開始處理,如果子view是GONE的,則直接跳過
-
在LinearLaout.measureVertical方法中,首先:獲取該子view的LayoutParams,在xml中定義的引數,將通過layout_weight定義的值累加到變數totalWeight中,所有的權重,然後判斷如果view的height設定為零,但weight設定的大於0,則將height的值設定為LayoutParams.WRAP_CONTENT這個值,其他的不處理
-
然後呼叫measureChildWithMargins方法,這個做的處理包括:計運算元view的measureSpec,即MODE和SIZE,呼叫方法為:getChildMeasureSpec,呼叫兩次,分別 計算寬和高,getChildMeasureSpec內部根據父view的Measure和子view的layout_width和layout_height屬性計運算元view的measure,檢視圖片:getChildMeasureSpec計運算元view的measure,總結如下:1.如果在xml中指定了子view的具體大小,那麼計算結果不管父的measure是什麼,結果都是EXACITY+child_size,2.如果子view的height指定
的值為FILL_PARENT,則返回的結果為:EXACITY+size,原因很簡單:因為FILL_PARENT的意思是充滿這個父view,所以返回的子view的measure就是view的大小,白!3.如果子vide的大小為wrap_content,那麼返回的結果都為At_most+size,原因是:最大不能超過父view的大小。-
子view的measure確定好以後,然後呼叫子view的measure方法,如果子view是View物件,則該view的大小測量結束,開始下一個子view的迴圈,如果子view是ViewGroup那麼,又開始一個新的遞迴,處理邏輯和上面一樣,值得所有的view物件測量結束。
-
LinearLayout會在每個他的直接子view測量結束後,將該子view的高度累加到變數mTotalLength,其其實應該叫mTotalHeight更合適,但是為了和wight同一,所以命名為這個(這個過程不處理:父view的大小指定為具體值和fill_parent,且子view的高度指定為0和子viewweight值大於的)。
-
所有的子view測量結束後,才開始對layout_weight計算,這樣我們可能想到,如果父view已經被佔滿了,那麼有可能layout_weight大於0的view物件是不會顯示的,而計算layout_weight的方法也很簡單,就是用總高度減去上面分析完mTotalLength的值,就是剩下,然後去平分給view物件,注意計算權重時優先去android:android:weightSum(LinearLayout的xml屬性)的值,如果不設定該值會計算和,所以該值既然設定了,就一定要子view的weight的總和相等,否則平分可能不能得到預期效果,分析一個例子吧:
<LinearLayout android:layout_width="fill_parent" android:layout_height="200dp" > <TextView android:layout_width="fill_parent" android:layout_height="100dp" android:layout_weight="1" /> <TextView android:layout_width="fill_parent" android:layout_height="0dp" android:layout_weight="1" /> </LinearLayout> 上面這個例子再計算第一個TextView的時候,根據android:layout_height="100dp" 值,確定高度為100dp,計算第二個TextView的時候,由於android:layout_height="0"為0,所以不計算其高度,等到計算weight的時候才計算,當計算weight的時候,千萬別認為第一個TextView已經計算過了,就不計算了,還是計算的,計算過程如下:第一個分配了100dp,還剩100dp,所以兩個textview各分50dp,所以第一個TextVIew的
150dp,第二個就為50dp至此,view的measure就結束了,所有的view值都結束了,大家可能發現,這個過程只用了幾個屬性: android:layout_width,android:layout_height,android:layout_weight還有marger和pading,其他的多數屬性都在ondraw時候使用。
三、Android自定義View研究:View的大小
Androd開發View是一個基本的檢視介面,但是如何做一個自定義的View,那View的大小是多少呢?這小節就研究下View的大小。通過LogCat來研究View的大小是怎樣確定的。好了,直接切入正題吧.
1、 在Activity中直接new HelloView 時View的大小。
View的大小獲取可以用其中的兩種方法獲取:
this.getHeight():獲取View的高
this.getWidth():獲取View的寬
我們可以做一個猜想,View的大小是在什麼時候確定的,是在new一個View的時候還是在onDraw()的時候?還是在其他時候?為了研究這個,我們分別在建構函式和onDraw中打上Log補丁。 --- >HelloVew.java public HelloView(Context context){ super(context); Log.v("HelloView(Context context)","" + this.getHeight()+ " " + this.getWidth()); } /** * 這個是我們要在XML中初始化用的 * */ public HelloView(Context context,AttributeSet attrs){ super(context, attrs); Log.v("HelloView(Context context,AttributeSet attrs)","" + this.getHeight()+ " " + this.getWidth()); } /** * 繪製View * */ protected void onDraw(Canvas canvas){ Log.v("onDraw(Canvas canvas)","" + this.getHeight()+ " " + this.getWidth()); canvas.drawColor(Color.WHITE); myUseBitmapFactory(canvas); myUseBitmapDrawable(canvas); myUseInputStreamandBitmapDrawable(canvas); } 執行:
我們觀察可以發現,new View 的時候並沒有確定了View的大小,並且系統就沒有呼叫(Context context)這個建構函式。
也就是說View大小是在new View之後OnDraw之前確定的,那onDraw之前的又有那些方法了,呵呵,我們試著override這個方法試試: protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // TODO Auto-generated method stub super.onMeasure(widthMeasureSpec, heightMeasureSpec); Log.v("onMeasure","" + this.getHeight()+ " " + this.getWidth()); } 執行:
我們觀察發現:onMeasure方法執行了兩次:第一次寬和高都是0,但是第二次就變了,是不是可以說是在這個方法中確定的,但是實際上不一定會是這麼回事,這個我們放在以後研究。這裡我們只需要知道不是在new View時確定的就好了。
2、在XML中定義時View大小
這個我們直接上程式碼:
main.xml檔案修改:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
<view class="com.fxhy.stady.HelloView"
android:layout_width="50dip"
android:layout_height="120dip"
/>
</LinearLayout>
mainActivity : /**
* 使用自定義的View
* */
public class MainActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);// 使用自定義的View
}
}
執行:
我們發現,和我們Xml中定義的大小一樣,哈哈,有興趣的可以自己測試測試。 |