Android繪製View的過程研究——計算View的大小

1、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,咱們常常須要重寫的方法。

2、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時候使用。

3、Android自定義View研究:View的大小
Androd開發View是一個基本的視圖界面,可是如何作一個自定義的View,那View的大小是多少呢?這小節就研究下View的大小。經過LogCat來研究View的大小是怎樣肯定的。好了,直接切入正題吧.
  一、 在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)這個構造函數。 java

  也就是說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時肯定的就行了。

 二、在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中定義的大小同樣,哈哈,有興趣的能夠本身測試測試。
相關文章
相關標籤/搜索