>中,我們了解了View樹的轉(zhuǎn)換過程以及如何設(shè)置View的LayoutParams的。本文繼續(xù)沿著既定軌跡繼續(xù)未完成的job。主要知識點(diǎn)如下:1、MeasureSpc類說明2、measure過程詳解(揭秘其細(xì)節(jié));3、rootView被添加至窗口時(shí),UI框架是" />

亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

Android中measure過程、WRAP_CONTENT詳解以及xm

系統(tǒng) 2242 0

本文原創(chuàng), 轉(zhuǎn)載請注明出處 http://blog.csdn.net/qinjuning



上篇文章<< Android中measure過程、WRAP_CONTENT詳解以及xml布局文件解析流程淺析(上) >>中,我們

了解了View樹的轉(zhuǎn)換過程以及如何設(shè)置View的LayoutParams的。本文繼續(xù)沿著既定軌跡繼續(xù)未完成的job。

主要知識點(diǎn)如下:
1、MeasureSpc類說明
2、measure過程詳解(揭秘其細(xì)節(jié));
3、root View被添加至窗口時(shí),UI框架是如何設(shè)置其LayoutParams值得。

在講解measure過程前,我們非常有必要理解MeasureSpc類的使用,否則理解起來也只能算是囫圇吞棗。


1、MeasureSpc類說明


1.1 SDK 說明如下

A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec

represents a requirement for either the width or the height. A MeasureSpec is comprised of a size and

a mode.

即:
MeasureSpc類封裝了父View傳遞給子View的布局(layout)要求。每個(gè)MeasureSpc實(shí)例代表寬度或者高度

(只能 是其一)要求。 它有三種模式:

①、UNSPECIFIED(未指定),父元素部隊(duì)自元素施加任何束縛,子元素可以得到任意想要的大小;

②、EXACTLY(完全),父元素決定自元素的確切大小,子元素將被限定在給定的邊界里而忽略它本身大小;

③、AT_MOST(至多),子元素至多達(dá)到指定大小的值。


常用的三個(gè)函數(shù):

static int getMode(int measureSpec) : 根據(jù)提供的測量值(格式)提取模式(上述三個(gè)模式之一)

static int getSize(int measureSpec) : 根據(jù)提供的測量值(格式)提取大小值(這個(gè)大小也就是我們通常所說的大小)

static int makeMeasureSpec(int size,int mode) : 根據(jù)提供的大小值和模式創(chuàng)建一個(gè)測量值(格式)


以上摘取自: <<
MeasureSpec介紹及使用詳解 >>

1.2 MeasureSpc類源碼分析 其為View.java類的內(nèi)部類,路徑:\frameworks\base\core\java\android\view\View.java

    public class View implements ... {
	 ...
	 public static class MeasureSpec {
        private static final int MODE_SHIFT = 30; //移位位數(shù)為30
        //int類型占32位,向右移位30位,該屬性表示掩碼值,用來與size和mode進(jìn)行"&"運(yùn)算,獲取對應(yīng)值。
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

        //向右移位30位,其值為00 + (30位0)  , 即 0x0000(16進(jìn)制表示)
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;
        //向右移位30位,其值為01 + (30位0)  , 即0x1000(16進(jìn)制表示)
        public static final int EXACTLY     = 1 << MODE_SHIFT;
        //向右移位30位,其值為02 + (30位0)  , 即0x2000(16進(jìn)制表示)
        public static final int AT_MOST     = 2 << MODE_SHIFT;

        //創(chuàng)建一個(gè)整形值,其高兩位代表mode類型,其余30位代表長或?qū)挼膶?shí)際值。可以是WRAP_CONTENT、MATCH_PARENT或具體大小exactly size
        public static int makeMeasureSpec(int size, int mode) {
            return size + mode;
        }
        //獲取模式  ,與運(yùn)算
        public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
        }
        //獲取長或?qū)挼膶?shí)際值 ,與運(yùn)算
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

    }
	...
}
  

MeasureSpec類的處理思路是:

①、右移運(yùn)算,使int 類型的高兩位表示模式的實(shí)際值,其余30位表示其余30位代表長或?qū)挼膶?shí)際值----可以是

WRAP_CONTENT、MATCH_PARENT或具體大小exactly size。


②、通過掩碼MODE_MASK進(jìn)行與運(yùn)算 “&”,取得模式(mode)以及長或?qū)?value)的實(shí)際值。


2、measure過程詳解


2.1 measure過程深入分析


之前的一篇博文 << Android中View繪制流程以及invalidate()等相關(guān)方法分析 >> ,我們從”二B程序員”的角度簡單 解了measure過程的調(diào)用過程。過了這么多,我們也該 升級了,- - 。現(xiàn)在請開始從”普通程序員”角度去理解這個(gè)

程。我們重點(diǎn)查看measure過程中地相關(guān)方法。

我們說過,當(dāng)UI框架開始繪制時(shí),皆是從ViewRoot.java類開始繪制的。


ViewRoot類簡要說明 : 任何顯示在設(shè)備中的窗口,例如:Activity、Dialog等,都包含一個(gè)ViewRoot實(shí)例,該

主要用來與遠(yuǎn)端 WindowManagerService交互以及控制(開始/銷毀)繪制。


Step 1 、 開始UI繪制 , 具體繪制方法則是:

    路徑:\frameworks\base\core\java\android\view\ViewRoot.java
public final class ViewRoot extends Handler implements ViewParent,View.AttachInfo.Callbacks {
    ...
    //mView對象指添加至窗口的root View ,對Activity窗口而言,則是DecorView對象。
    View mView;    
    
    //開始View繪制流程
    private void performTraversals(){
    	...
    	//這兩個(gè)值我們在后面討論時(shí),在回過頭來看看是怎么賦值的。現(xiàn)在只需要記住其值MeasureSpec.makeMeasureSpec()構(gòu)建的。
    	int childWidthMeasureSpec; //其值由MeasureSpec類構(gòu)建 , makeMeasureSpec
        int childHeightMeasureSpec;//其值由MeasureSpec類構(gòu)建 , makeMeasureSpec
        

        // Ask host how big it wants to be
        host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    	...
    }
    ...
}
  

這兒,我并沒有說出childWidthMeasureSpec和childHeightMeasureSpec類的來由(為了避免額外地開銷,等到
第三部分時(shí)我們在來攻克它,現(xiàn)在只需要記住其值MeasureSpec.makeMeasureSpec()構(gòu)建的。

Step 2 、 調(diào)用measure()方法去做一些前期準(zhǔn)備

measure()方法原型定義在View.java類中,final修飾符修飾,其不能被重載:

    public class View implements ... {
   	...
    /**
     * This is called to find out how big a view should be. The parent
     * supplies constraint information in the width and height parameters.
     *
     * @param widthMeasureSpec Horizontal space requirements as imposed by the
     *        parent
     * @param heightMeasureSpec Vertical space requirements as imposed by the
     *        parent
     * @see #onMeasure(int, int)
     */
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
   		//判斷是否為強(qiáng)制布局,即帶有“FORCE_LAYOUT”標(biāo)記 以及 widthMeasureSpec或heightMeasureSpec發(fā)生了改變
        if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
                widthMeasureSpec != mOldWidthMeasureSpec ||
                heightMeasureSpec != mOldHeightMeasureSpec) {

            // first clears the measured dimension flag
        	//清除MEASURED_DIMENSION_SET標(biāo)記   ,該標(biāo)記會在onMeasure()方法后被設(shè)置
            mPrivateFlags &= ~MEASURED_DIMENSION_SET; 

            // measure ourselves, this should set the measured dimension flag back
            // 1、 測量該View本身的大小 ; 2 、 設(shè)置MEASURED_DIMENSION_SET標(biāo)記,否則接寫來會報(bào)異常。
            onMeasure(widthMeasureSpec, heightMeasureSpec);

            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("onMeasure() did not set the"
                        + " measured dimension by calling" + " setMeasuredDimension()");
            }

            mPrivateFlags |= LAYOUT_REQUIRED;  //下一步是layout了,添加LAYOUT_REQUIRED標(biāo)記
        }

        mOldWidthMeasureSpec = widthMeasureSpec;   //保存值
        mOldHeightMeasureSpec = heightMeasureSpec; //保存值
    }
   	...
}
  


參數(shù)widthMeasureSpec和heightMeasureSpec 由父View構(gòu)建,表示父View給子View的測量要求。其值地構(gòu)建

會在下面步驟中詳解。

measure()方法顯示判斷是否需要重新調(diào)用設(shè)置改View大小,即調(diào)用onMeasure()方法,然后操作兩個(gè)標(biāo)識符:

①、重置 MEASURED_DIMENSION_SET : onMeasure()方法中,需要添加該標(biāo)識符,否則,會報(bào)異常;

②、添加 LAYOUT_REQUIRED : 表示需要進(jìn)行l(wèi)ayout操作。

最后,保存當(dāng)前的widthMeasureSpec和heightMeasureSpec值。


Step 3 、 調(diào)用onMeasure()方法去真正設(shè)置View的長寬值,其默認(rèn)實(shí)現(xiàn)為:

     /**
    * Measure the view and its content to determine the measured width and the
    * measured height. This method is invoked by {@link #measure(int, int)} and
    * should be overriden by subclasses to provide accurate and efficient
    * measurement of their contents.
    * 
    * @param widthMeasureSpec horizontal space requirements as imposed by the parent.
    *                         The requirements are encoded with
    * @param heightMeasureSpec vertical space requirements as imposed by the parent.
    *                         The requirements are encoded with
    */
   //設(shè)置該View本身地大小
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
               getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
   }
   
   /**
    * Utility to return a default size. Uses the supplied size if the
    * MeasureSpec imposed no contraints. Will get larger if allowed
    * by the MeasureSpec.
    *
    * @param size Default size for this view
    * @param measureSpec Constraints imposed by the parent
    * @return The size this view should be.
    */
   //@param size參數(shù)一般表示設(shè)置了android:minHeight屬性或者該View背景圖片的大小值
   public static int getDefaultSize(int size, int measureSpec) {
       int result = size;  
       int specMode = MeasureSpec.getMode(measureSpec);
       int specSize =  MeasureSpec.getSize(measureSpec);

       //根據(jù)不同的mode值,取得寬和高的實(shí)際值。
       switch (specMode) {
       case MeasureSpec.UNSPECIFIED:  //表示該View的大小父視圖未定,設(shè)置為默認(rèn)值
           result = size;
           break;
       case MeasureSpec.AT_MOST:      //表示該View的大小由父視圖指定了
       case MeasureSpec.EXACTLY:
           result = specSize;
           break;
       }
       return result;
   }
   //獲得設(shè)置了android:minHeight屬性或者該View背景圖片的大小值, 最為該View的參考值
   protected int getSuggestedMinimumWidth() {
       int suggestedMinWidth = mMinWidth;  //  android:minHeight

       if (mBGDrawable != null) { // 背景圖片對應(yīng)地Width。
           final int bgMinWidth = mBGDrawable.getMinimumWidth();
           if (suggestedMinWidth < bgMinWidth) {
               suggestedMinWidth = bgMinWidth;
           }
       }

       return suggestedMinWidth;
   }
   //設(shè)置View在measure過程中寬和高
   protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
       mMeasuredWidth = measuredWidth;
       mMeasuredHeight = measuredHeight;

       mPrivateFlags |= MEASURED_DIMENSION_SET;  //設(shè)置了MEASURED_DIMENSION_SET標(biāo)記
   }
  

主要功能就是根據(jù)該View屬性(android:minWidth和背景圖片大小)和父View對該子View的"測量要求",設(shè)置該 View mMeasuredWidth 和 mMeasuredHeight 值。


這兒只是一般的View類型地實(shí)現(xiàn)方法。一般來說,父View,也就是ViewGroup類型,都需要在重寫onMeasure() 方法,遍歷 所有子View,設(shè)置每個(gè)子View的大小。基本思想如下: 遍歷所有子View,設(shè)置每個(gè)子View的大小。偽

碼表示為:

       //某個(gè)ViewGroup類型的視圖
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
	    //必須調(diào)用super.ononMeasure()或者直接調(diào)用setMeasuredDimension()方法設(shè)置該View大小,否則會報(bào)異常。
	    super.onMeasure(widthMeasureSpec , heightMeasureSpec)
        //setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
        //        getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
        
	    //遍歷每個(gè)子View
	    for(int i = 0 ; i < getChildCount() ; i++){
	    	View child = getChildAt(i);
	    	//調(diào)用子View的onMeasure,設(shè)置他們的大小。childWidthMeasureSpec , childHeightMeasureSpec ?
	    	child.onMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
	    }
   }
  


Step 2、 Step 3 代碼也比較好理解,但問題是我們示例代碼中widthMeasureSpec、 heightMeasureSpec是如何

確定的呢?父View是如何設(shè)定其值的?

要想回答這個(gè)問題,我們看是去源代碼里找找答案吧。在ViewGroup.java類中,為我們提供了三個(gè)方法,去設(shè)置

個(gè)子View的大小,基本思想也如同我們 之前描述的思想:遍歷所有子View,設(shè)置每個(gè)子View的大小。

主要有如下方法:

    /**
 * Ask all of the children of this view to measure themselves, taking into
 * account both the MeasureSpec requirements for this view and its padding.
 * We skip children that are in the GONE state The heavy lifting is done in
 * getChildMeasureSpec.
 */
//widthMeasureSpec 和  heightMeasureSpec 表示該父View的布局要求
//遍歷每個(gè)子View,然后調(diào)用measureChild()方法去實(shí)現(xiàn)每個(gè)子View大小
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { // 不處于 “GONE” 狀態(tài)
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}
   
/**
 * Ask one of the children of this view to measure itself, taking into
 * account both the MeasureSpec requirements for this view and its padding.
 * The heavy lifting is done in getChildMeasureSpec.
 *
 * @param child The child to measure
 * @param parentWidthMeasureSpec The width requirements for this view
 * @param parentHeightMeasureSpec The height requirements for this view
 */
//測量每個(gè)子View高寬時(shí),清楚了該View本身的邊距大小,即android:padding屬性 或android:paddingLeft等屬性標(biāo)記
protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams(); // LayoutParams屬性
    //設(shè)置子View的childWidthMeasureSpec屬性,去除了該父View的邊距值  mPaddingLeft + mPaddingRight
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    //設(shè)置子View的childHeightMeasureSpec屬性,去除了該父View的邊距值  mPaddingTop + mPaddingBottom
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
  

measureChildren()方法:遍歷所有子View,調(diào)用 measureChild()方法去設(shè)置該子View的屬性值。

measureChild() 方法 : 獲取特定子View的 widthMeasureSpec、 heightMeasureSpec,調(diào)用measure()方法

設(shè)置子View的實(shí)際寬高值。

getChildMeasureSpec ()就是獲取 子View的 widthMeasureSpec、 heightMeasureSpec值。

    /**
 * Does the hard part of measureChildren: figuring out the MeasureSpec to
 * pass to a particular child. This method figures out the right MeasureSpec
 * for one dimension (height or width) of one child view.
 *
 * The goal is to combine information from our MeasureSpec with the
 * LayoutParams of the child to get the best possible results.
 */
// spec參數(shù)                                    表示該父View本身所占的widthMeasureSpec 或  heightMeasureSpec值
// padding參數(shù)                          表示該父View的邊距大小,見于android:padding屬性 或android:paddingLeft等屬性標(biāo)記
// childDimension參數(shù)  表示該子View內(nèi)部LayoutParams屬性的值,可以是wrap_content、match_parent、一個(gè)精確指(an exactly size),
//           例如:由android:width指定等。
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    int specMode = MeasureSpec.getMode(spec);  //獲得父View的mode
    int specSize = MeasureSpec.getSize(spec);  //獲得父View的實(shí)際值

    int size = Math.max(0, specSize - padding); //父View為子View設(shè)定的大小,減去邊距值,

    int resultSize = 0;    //子View對應(yīng)地 size 實(shí)際值 ,由下面的邏輯條件賦值
    int resultMode = 0;    //子View對應(yīng)地 mode 值 , 由下面的邏輯條件賦值

    switch (specMode) {
    // Parent has imposed an exact size on us
    //1、父View是EXACTLY的 !
    case MeasureSpec.EXACTLY: 
    	//1.1、子View的width或height是個(gè)精確值 (an exactly size)
        if (childDimension >= 0) {          
            resultSize = childDimension;         //size為精確值
            resultMode = MeasureSpec.EXACTLY;    //mode為 EXACTLY 。
        } 
        //1.2、子View的width或height為 MATCH_PARENT/FILL_PARENT 
        else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size. So be it.
            resultSize = size;                   //size為父視圖大小
            resultMode = MeasureSpec.EXACTLY;    //mode為 EXACTLY 。
        } 
        //1.3、子View的width或height為 WRAP_CONTENT
        else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size. It can't be
            // bigger than us.
            resultSize = size;                   //size為父視圖大小
            resultMode = MeasureSpec.AT_MOST;    //mode為AT_MOST 。
        }
        break;

    // Parent has imposed a maximum size on us
    //2、父View是AT_MOST的 !    
    case MeasureSpec.AT_MOST:
    	//2.1、子View的width或height是個(gè)精確值 (an exactly size)
        if (childDimension >= 0) {
            // Child wants a specific size... so be it
            resultSize = childDimension;        //size為精確值
            resultMode = MeasureSpec.EXACTLY;   //mode為 EXACTLY 。
        }
        //2.2、子View的width或height為 MATCH_PARENT/FILL_PARENT
        else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size, but our size is not fixed.
            // Constrain child to not be bigger than us.
            resultSize = size;                  //size為父視圖大小
            resultMode = MeasureSpec.AT_MOST;   //mode為AT_MOST
        }
        //2.3、子View的width或height為 WRAP_CONTENT
        else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size. It can't be
            // bigger than us.
            resultSize = size;                  //size為父視圖大小
            resultMode = MeasureSpec.AT_MOST;   //mode為AT_MOST
        }
        break;

    // Parent asked to see how big we want to be
    //3、父View是UNSPECIFIED的 !
    case MeasureSpec.UNSPECIFIED:
    	//3.1、子View的width或height是個(gè)精確值 (an exactly size)
        if (childDimension >= 0) {
            // Child wants a specific size... let him have it
            resultSize = childDimension;        //size為精確值
            resultMode = MeasureSpec.EXACTLY;   //mode為 EXACTLY
        }
        //3.2、子View的width或height為 MATCH_PARENT/FILL_PARENT
        else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size... find out how big it should
            // be
            resultSize = 0;                        //size為0! ,其值未定
            resultMode = MeasureSpec.UNSPECIFIED;  //mode為 UNSPECIFIED
        } 
        //3.3、子View的width或height為 WRAP_CONTENT
        else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size.... find out how
            // big it should be
            resultSize = 0;                        //size為0! ,其值未定
            resultMode = MeasureSpec.UNSPECIFIED;  //mode為 UNSPECIFIED
        }
        break;
    }
    //根據(jù)上面邏輯條件獲取的mode和size構(gòu)建MeasureSpec對象。
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
  

為了便于分析,我將上面的邏輯判斷語句使用列表項(xiàng)進(jìn)行了說明.


getChildMeasureSpec ()方法的主要功能如下:


根據(jù)父View的measureSpec值(widthMeasureSpec,heightMeasureSpec)值以及子View的子View內(nèi)部

LayoutParams 屬性值,共同決定子View的measureSpec值的大小。主要判斷條件主要為MeasureSpec的mode

類型 以及 LayoutParams 的寬高實(shí)際值(lp.width,lp.height), 見于以上所貼代碼中的列表項(xiàng): 1、 1.1 ; 1.2 ; 1.3 ;

2、2.1等。


例如,分析列表3:假設(shè)當(dāng)父View為MeasureSpec.UNSPECIFIED類型,即未定義時(shí),只有當(dāng)子View的width

或height指定時(shí),其mode才為 MeasureSpec.EXACTLY ,否者該View size為 0 ,mode為 MeasureSpec.UNSPECIFIED 時(shí)

,即處于未指定狀態(tài)。

由此可以得出, 每個(gè)View大小的設(shè)定都事由其父View以及該View共同決定的。但這只是一個(gè)期望的大小,每個(gè)

View在測量時(shí)最終大小的設(shè)定是由 setMeasuredDimension()最終決定的。因此,最終確定一個(gè)View的“測量長寬“是

由以下幾個(gè)方面影響:

1、父View的 MeasureSpec屬性;

2、子View的 LayoutParams屬性 ;

3、 setMeasuredDimension()或者其它類似設(shè)定 mMeasuredWidth 和 mMeasuredHeight 值的方法。

setMeasuredDimension()原型:

       //設(shè)置View在measure過程中寬和高
   protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
       mMeasuredWidth = measuredWidth;
       mMeasuredHeight = measuredHeight;

       mPrivateFlags |= MEASURED_DIMENSION_SET;  //設(shè)置了MEASURED_DIMENSION_SET標(biāo)記
   }
  


將上面列表項(xiàng)轉(zhuǎn)換為表格為:

Android中measure過程、WRAP_CONTENT詳解以及xml布局文件解析流程淺析(下)

這張表格更能幫助我們分析View的MeasureSpec的確定條件關(guān)系。


為了幫助大家理解,下面我們分析某個(gè)窗口使用地xml布局文件,我們弄清楚該xml布局文件中每個(gè)View的

MeasureSpec值的組成。

       <?xml version="1.0" encoding="utf-8"?>
   <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
       android:id="@+id/llayout"
   	   android:orientation="vertical" 
       android:layout_width="match_parent"
   	   android:layout_height="match_parent">
   	
   	
   	<TextView android:id="@+id/tv" 
   	    android:layout_width="match_parent"
   		android:layout_height="wrap_content"
   	    android:text="@string/hello" />

   </LinearLayout>   
  

該布局文件共有兩個(gè)View: ①、id為llayout的 LinearLayout 布局控件 ;

②、id為tv的 TextView 控件。


假設(shè)LinearLayout的父View對應(yīng)地widthSpec和heightSpec值皆為MeasureSpec.EXACTLY類型(Activity窗口

的父View為DecorView,具體原因見第三部分說明)。


LinearLayout 而言比較簡單,由于 android:layout_width="match_parent",因此其width對應(yīng)地widthSpec

mode 值為MeasureSpec.EXACTLY , size由父視圖大小指定 ; 由于android:layout_height = "match_parent",

此其height對應(yīng)地heightSpec mode MeasureSpec.EXACTLY ,size由父視圖大小指定 ;


TextView 而言 ,其父View為LinearLayout的widthSpec和heightSpec值皆為MeasureSpec.EXACTLY類型,

由于android:layout_width="match_parent" , 因此其width對應(yīng)地widthSpec mode值為MeasureSpec.EXACTLY

size由父視圖大小指定 ; 由于android:layout_width="wrap_content" , 因此其height對應(yīng)地widthSpec mode值為

MeasureSpec.AT_MOST,size由父視圖大小指定 。


我們繼續(xù)窺測下LinearLayout類是如何進(jìn)行measure過程的:

       public class LinearLayout extends ViewGroup {
		...
		@Override  //onMeasure方法。
		protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		    //判斷是垂直方向還是水平方向,這兒我們假設(shè)是VERTICAL垂直方向,
			if (mOrientation == VERTICAL) {
		        measureVertical(widthMeasureSpec, heightMeasureSpec);
		    } else {
		        measureHorizontal(widthMeasureSpec, heightMeasureSpec);
		    }
		}
		//垂直方向布局
	    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
	        mTotalLength = 0;         //該LinearLayout測量子View時(shí)的總高度。
	    	float totalWeight = 0;    //所有子View的權(quán)重和 , android:layout_weight
	    	int maxWidth = 0;         //保存子View中最大width值
	        ...
	        final int count = getVirtualChildCount();  //子View的個(gè)數(shù)
	        
	        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
	        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            ...
	        // See how tall everyone is. Also remember max width.
	        for (int i = 0; i < count; ++i) {
	            final View child = getVirtualChildAt(i);
                ...
	            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();

	            totalWeight += lp.weight;  
	            //滿足該條件地View會在該LinearLayout有剩余高度時(shí),才真正調(diào)用measure()
	            if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
	                ...
	            } else {
	                int oldHeight = Integer.MIN_VALUE;
	                //如果View的hight值為0,并且設(shè)置了android:layout_weight屬性,重新糾正其height值為WRAP_CONTENT
	                if (lp.height == 0 && lp.weight > 0) {
	                    oldHeight = 0;
	                    lp.height = LayoutParams.WRAP_CONTENT;
	                }
	                // Determine how big this child would like to be. If this or
	                // previous children have given a weight, then we allow it to
	                // use all available space (and we will shrink things later
	                // if needed).
	                //對每個(gè)子View調(diào)用measure()方法
	                measureChildBeforeLayout(
	                       child, i, widthMeasureSpec, 0, heightMeasureSpec,
	                       totalWeight == 0 ? mTotalLength : 0);
	                
	                //這三行代碼做了如下兩件事情:
	                //1、獲得該View的measuredHeight值,每個(gè)View都會根據(jù)他們地屬性正確設(shè)置值  > 0 ;
	                //2、更新mTotalLength值:取當(dāng)前高度mTotalLength值與mTotalLength + childHeight 的最大值
	                // 于是對于android:layout_height="wrap_height"屬性地LinearLayout控件也就知道了它的確切高度值了。
	                final int childHeight = child.getMeasuredHeight();
	                final int totalLength = mTotalLength;
	                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
	                       lp.bottomMargin + getNextLocationOffset(child));
	                ...
	            }
	            final int margin = lp.leftMargin + lp.rightMargin;
	            final int measuredWidth = child.getMeasuredWidth() + margin;
	            maxWidth = Math.max(maxWidth, measuredWidth);
	            ...
	        }
            //后續(xù)還有很多處理,包括繼續(xù)measure()某些符合條件地子View
	        ...
	    }
	    void measureChildBeforeLayout(View child, int childIndex,
	            int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
	            int totalHeight) {
	    	//調(diào)用measureChildWithMargins()方法去設(shè)置子View大小
	        measureChildWithMargins(child, widthMeasureSpec, totalWidth,
	                heightMeasureSpec, totalHeight);
	    }
		...
	}
  

繼續(xù)看看measureChildWithMargins()方法,該方法定義在ViewGroup.java內(nèi),基本流程同于measureChild()方法,但添加了對子View Margin的處理,即:android:margin屬性或者android:marginLeft等屬性的處理。

measureChildWithMargins@ ViewGroup.java

    	/**
	 * Ask one of the children of this view to measure itself, taking into
	 * account both the MeasureSpec requirements for this view and its padding
	 * and margins. The child must have MarginLayoutParams The heavy lifting is
	 * done in getChildMeasureSpec.
	 */
	//基本流程同于measureChild()方法,但添加了對子View Margin的處理,即:android:margin屬性或者android:marginLeft等屬性的處理
	//widthUsed參數(shù)  表示該父View已經(jīng)使用的寬度
	//heightUsed參數(shù)  表示該父View已經(jīng)使用的高度
	protected void measureChildWithMargins(View child,
	        int parentWidthMeasureSpec, int widthUsed,
	        int parentHeightMeasureSpec, int heightUsed) {
	    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

	    //獲得子View的childWidthMeasureSpec和childHeightMeasureSpec值
	    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
	            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
	                    + widthUsed, lp.width);
	    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
	            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
	                    + heightUsed, lp.height);

	    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
	}
  



measure()過程時(shí),LinearLayout類做了如下事情 :

1、遍歷每個(gè)子View,對其調(diào)用measure()方法;

2、子View measure()完成后,需要取得該子View地寬高實(shí)際值,繼而做處理(例如:LinearLayout屬性為

android:widht="wrap_content"時(shí), LinearLayout的實(shí)際width值則是每個(gè)子View的 width值的累加值 )。

2.2 WRAP_CONTENT、MATCH_PARENT以及measure動(dòng)機(jī)揭秘


子View地寬高實(shí)際值 ,即 child.getMeasuredWidth()值得返回最終會是一個(gè)確定值? 難道 WRAP_CONTENT (

其值為-2) 、MATCH_PARENT(值為-1)或者說一個(gè)具體值(an exactly size > 0)。前面我們說過, View最終“測量”值的

確定是有三個(gè)部分組成地:

①、父View的MeasureSpec屬性;

②、子View的 LayoutParams屬性 ;

③、setMeasuredDimension()或者其它類似設(shè)定 mMeasuredWidth 和 mMeasuredHeight 值的方法。

因此,一個(gè)View必須以某種合適地方法確定它地最終大小。例如,如下自定義View:

    //自定義View	
public Class MyView extends View {
	
	 //針對不同地mode值,設(shè)置本View地大小
	 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
		 //獲得父View傳遞給我們地測量需求
		 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
	     int heightMode = MeasureSpec.getMode(heightMeasureSpec);
		 
	     int width = 0 ;
	     int height = 0 ;
	     //對UNSPECIFIED 則拋出異常
	     if(widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED)
	         throw new RuntimeException("widthMode or heightMode cannot be UNSPECIFIED");
	    
	     //精確指定
	     if(widthMode == MeasureSpec.EXACTLY){
	    	 width = 100 ;
	     }
	     //模糊指定
	     else if(widthMode == MeasureSpec.AT_MOST )
	    	 width = 50 ; 
	     
	      //精確指定
	     if(heightMode == MeasureSpec.EXACTLY){
	    	 height = 100 ;
	     }
	     //模糊指定
	     else if(heightMode == MeasureSpec.AT_MOST )
	    	 height = 50 ;
	     
	     setMeasuredDimension(width , height) ;
	 }
}
  



該自定義View重寫了onMeasure()方法,根據(jù)傳遞過來的widthMeasureSpec和heightMeasureSpec簡單設(shè)置了

View的 mMeasuredWidth 和 mMeasuredHeight值。

對于TextView而言,如果它地mode不是Exactly類型 , 它會根據(jù)一些屬性,例如: android:textStyle

android:textSize android:typeface 等去確定TextView類地需要占用地長和寬。

因此,如果你地自定義View必須手動(dòng)對不同mode做出處理。否則,則是mode對你而言是無效的。

Android框架中提供地一系列View/ViewGroup都需要去進(jìn)行這個(gè)measure()過程地 ,因?yàn)樵趌ayout()過程中,父

View需要調(diào)用getMeasuredWidth()或getMeasuredHeight()去為每個(gè)子View設(shè)置他們地布局坐標(biāo),只有確定布局

坐標(biāo)后,才能真正地將該View 繪制 (draw) 出來,否則該View的layout大小為0,得不到期望效果。我們繼續(xù)看看

LinearLayout的layout布局過程:

    public class LinearLayout extends ViewGroup {
	...
    @Override  //layout 過程
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
		//假定是垂直方向布局
        if (mOrientation == VERTICAL) {
            layoutVertical();
        } else {
            layoutHorizontal();
        }
    }
	//對每個(gè)子View調(diào)用layout過程
    void layoutVertical() {
        ...
        final int count = getVirtualChildCount();
        ...
        for (int i = 0; i < count; i++) {
            final View child = getVirtualChildAt(i);
            if (child == null) {  //一般為非null
                childTop += measureNullChild(i);
            } else if (child.getVisibility() != GONE) {
            	//獲得子View測量時(shí)的實(shí)際寬高值,
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();
                
                ...
                //  封裝了child.layout()方法,見如下
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight); 
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

                i += getChildrenSkipCount(child, i);
            }
        }
    }
    //width = getMeasuredWidth() ; height = childHeight(); View的大小就是測量大小
    private void setChildFrame(View child, int left, int top, int width, int height) {
    	
        child.layout(left, top, left + width, top + height);
    }
	...
}	
  

對一個(gè)View進(jìn)行measure操作地主要目的就是為了確定該View地布局大小,見上面所示代碼。但measure操作

通常是耗時(shí)的,因此 對自定義ViewGroup而言,我們可以自由控制measure、layout過程,如果我們知道如何layout

一個(gè)View,我們可以跳過該ViewGroup地measure操作(onMeasure()方法中measure 所有子View地 ),直接去layout


在前面一篇博客<< Android中滑屏初探 ---- scrollTo 以及 scrollBy方法使用說明 >> 中,我們自定義了 一個(gè) ViewGroup, 并且重寫了onMeasure()和onLayout()方法去分別操作每個(gè)View。就該ViewGroup而言,我們只需要

重寫onLayout() 操作即可,因?yàn)槲覀冎廊绾蝜ayout每個(gè)子View。 如下代碼所示:


    //自定義ViewGroup , 包含了三個(gè)LinearLayout控件,存放在不同的布局位置
public class MultiViewGroup extends ViewGroup {
	private void init() {
		// 初始化3個(gè) LinearLayout控件
		LinearLayout oneLL = new LinearLayout(mContext);
		oneLL.setBackgroundColor(Color.RED);
        addView(oneLL);
        ...
	}
	@Override
	// 我們知曉每個(gè)子View的layout布局大小,因此我們不需要為每個(gè)子View進(jìn)行measure()操作了。
//	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//		setMeasuredDimension(width, height);
//		// 設(shè)置該ViewGroup的大小
//		int width = MeasureSpec.getSize(widthMeasureSpec);
//		int height = MeasureSpec.getSize(heightMeasureSpec);
//		int childCount = getChildCount();
//		for (int i = 0; i < childCount; i++) {
//			View child = getChildAt(i);
//			// 設(shè)置每個(gè)子視圖的大小 , 即全屏
//			child.measure(MultiScreenActivity.screenWidth, MultiScreenActivity.scrrenHeight);
//		}
	}

	// layout過程
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		// TODO Auto-generated method stub
		Log.i(TAG, "--- start onLayout --");
		int startLeft = 0; // 每個(gè)子視圖的起始布局坐標(biāo)
		int startTop = 10; // 間距設(shè)置為10px 相當(dāng)于 android:marginTop= "10px"
		int childCount = getChildCount();
		Log.i(TAG, "--- onLayout childCount is -->" + childCount);
		for (int i = 0; i < childCount; i++) {
			View child = getChildAt(i);
			child.layout(startLeft, startTop, 
					startLeft + MultiScreenActivity.screenWidth, 
					startTop + MultiScreenActivity.scrrenHeight);
			startLeft = startLeft + MultiScreenActivity.screenWidth ; //校準(zhǔn)每個(gè)子View的起始布局位置
			//三個(gè)子視圖的在屏幕中的分布如下 [0 , 320] / [320,640] / [640,960]
		}
	}
}  
  

更多關(guān)于自定義ViewGroup無須重寫measure動(dòng)作的,可以參考 Android API :

< < Optimizing the View >>

中文翻譯見于:<< Android中View繪制優(yōu)化之三---- 優(yōu)化View >>


3、root View被添加至窗口時(shí),UI框架是如何設(shè)置其LayoutParams值


老子道德經(jīng)有言:“道生一,一生二,二生三,三生萬物。” UI繪制也就是個(gè)遞歸過程。理解其基本架構(gòu)后,
也就“掌握了一個(gè)中心點(diǎn)”了。在第一節(jié)中,我們沒有說明開始UI繪制時(shí) ,沒有說明mView.measure()參數(shù)地由來,
參數(shù)也就是我們本節(jié)需要弄懂的“道” --- root View的 widthMeasureSpec和heightMeasureSpec 是如何確定的。

對于如下布局文件: 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"
    />
</LinearLayout>

    
當(dāng)使用LayoutInflater類解析成View時(shí) ,LinearLayout對象的LayoutParams參數(shù)為null 。具體原因請參考上篇博文

任何一個(gè)View被添加至窗口時(shí),都需要利用WindowManager類去操作。例如,如下代碼:
      //顯示一個(gè)懸浮窗吧 , just so so 
public void showView()
{
    //解析布局文件
	LayoutInflater layoutInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    //rootView對應(yīng)地LayoutParams屬性值為null,將會在UI繪制時(shí)設(shè)定其值
	View rootView = layoutInflater.inflate(R.layout.main, null);
	
	WindowManager windowManager = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
	//設(shè)置WindowManager.LayoutParams參數(shù)值,作為該窗口的各種屬性
	WindowManager.LayoutParams winparams = WindowManager.LayoutParams();
     // 以屏幕左上角為原點(diǎn),設(shè)置x、y初始值
	winparams.x = 0;
	winparams.y = 0;

    //設(shè)置懸浮窗口長寬數(shù)據(jù)
	winparams.width = WindowManager.LayoutParams.WRAP_CONTENT;;
	winparams.height = WindowManager.LayoutParams.WRAP_CONTENT;;
     
	windowManager.addView(rootView, winparams);
}
    

  
關(guān)于WindowManager的使用請看如下博客 :
關(guān)于WindowManager.LayoutParams類說明請看如下博客:
下面,我們從獲得WindowManager對象引用開始,一步步觀察addView()做了一些什么事情。
Step 1 、獲得WindowManager對象服務(wù) ,具體實(shí)現(xiàn)類在ContextImpl.java內(nèi)中
路徑:/frameworks/base/core/java/android/app/ContextImpl.java
         @Override
   public Object getSystemService(String name) {
       if (WINDOW_SERVICE.equals(name)) {
           return WindowManagerImpl.getDefault();
       }
       ...
   }

    
WindowManager是個(gè)接口,具體返回對象則是WindowManagerImpl的單例對象。

Step 2 、獲得WindowManagerImpl的單例對象,以及部分源碼分析
路徑:/frameworks/base/core/java/android/view/ WindowManagerImpl .j ava
      public class WindowManagerImpl implements WindowManager{
	   
   public static WindowManagerImpl getDefault()
   {
       return mWindowManager;
   }
   //以特定Window屬性添加一個(gè)窗口
   public void addView(View view, ViewGroup.LayoutParams params)
   {
       addView(view, params, false);
   }
   //參數(shù)nest表示該窗口是不是一個(gè)字窗口
   private void addView(View view, ViewGroup.LayoutParams params, boolean nest)
   {   ...
       final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
       
       ViewRoot root;
       View panelParentView = null;    //該子窗口對應(yīng)地父窗口View
       
       synchronized (this) {
          
    	   ...//需要對傳遞過來地參數(shù)進(jìn)行檢測...
    	   
           //對每個(gè)窗口皆構(gòu)建一個(gè)ViewRoot對象
           root = new ViewRoot(view.getContext());
           root.mAddNesting = 1;
           //設(shè)置root View 的LayoutParams為wparams,即WindowManager.LayoutParams類型
           view.setLayoutParams(wparams);
           ...//對參數(shù)檢測,以及拷貝原有數(shù)組...
           
           //將窗口對應(yīng)地view、root、wparams保存在屬性集合中
           mViews[index] = view;
           mRoots[index] = root;
           mParams[index] = wparams;
       }
       // do this last because it fires off messages to start doing things
       // 調(diào)用ViewRoot對象去通知系統(tǒng)添加一個(gè)窗口
       root.setView(view, wparams, panelParentView);
   }
   ...
   //這三個(gè)數(shù)組分別保存了一個(gè)窗口對應(yīng)地屬性
   private View[] mViews;         //root View對象 , View類型
   private ViewRoot[] mRoots;     //ViewRoot類型 , 與WMS通信
   private WindowManager.LayoutParams[] mParams;  //窗口屬性
   
   //WindowManagerImpl實(shí)現(xiàn)了單例模式
   private static WindowManagerImpl mWindowManager = new WindowManagerImpl();
}
    


WindowManagerImpl類的三個(gè)數(shù)組集合保存了每個(gè)窗口相關(guān)屬性,這樣我們可以通過這些屬性去操作特定的
窗口(例如,可以根據(jù)View去更新/銷毀該窗口)。當(dāng)參數(shù)檢查成功時(shí),構(gòu)建一個(gè)ViewRoot對象,并且設(shè)置 設(shè)置root
View 的LayoutParams為wparams,即WindowManager.LayoutParams類型 。最后調(diào)用root.setView()方法去通知
系統(tǒng)需要?jiǎng)?chuàng)建該窗口。我們接下來往下看看ViewRoot類相關(guān)操作。
Step 3、
      public final class ViewRoot extends Handler implements ViewParent,View.AttachInfo.Callbacks {
	   
	View mView;	  //所有窗口地root View   
	final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();  

	...
	 /**
     * We have one child
     */
    public void setView(View view, WindowManager.LayoutParams attrs,
            View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                mWindowAttributes.copyFrom(attrs); //保存WindowManager.LayoutParams屬性值
                attrs = mWindowAttributes;
                ...
                
                mAdded = true;
                int res; /* = WindowManagerImpl.ADD_OKAY; */

                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                requestLayout();   //請求UI開始繪制。
                mInputChannel = new InputChannel();  //創(chuàng)建一個(gè)InputChannel對象,接受消息
                try {
                    //通知WindowManagerService添加一個(gè)窗口
                	res = sWindowSession.add(mWindow, mWindowAttributes,
                            getHostVisibility(), mAttachInfo.mContentInsets,
                            mInputChannel);
                } 
                ...
                view.assignParent(this);  //將root View的父View設(shè)置為該ViewRoot對象(實(shí)現(xiàn)了ViewParent接口)
                ...
            }
        }
    }
}
    
說明:ViewRoot類繼承了Handler,實(shí)現(xiàn)了ViewParent接口

setView()方法地主要功能如下:
1、保存相關(guān)屬性值,例如:mView、mWindowAttributes等;
2、調(diào)用requestLayout()方法請求UI繪制,由于ViewRoot是個(gè)Handler對象,異步請求;
3、通知WindowManagerService添加一個(gè)窗口;
4、注冊一個(gè)事件監(jiān)聽管道,用來監(jiān)聽:按鍵(KeyEvent)和觸摸(MotionEvent)事件。
我們這兒重點(diǎn)關(guān)注 requestLayout()方法請求UI繪制地流程。

Step 4、異步調(diào)用請求UI繪制
         /**
    * {@inheritDoc}
    */
   public void requestLayout() {
       checkThread();        //檢查是不是UI線程調(diào)用,如果不是UI線程,會報(bào)異常
       mLayoutRequested = true;   //置為真,表示需要進(jìn)行measure和layout過程
       scheduleTraversals();  
   }
   //開始UI繪制流程
   public void scheduleTraversals() {
       if (!mTraversalScheduled) {
           mTraversalScheduled = true;       //防止多次調(diào)用
           sendEmptyMessage(DO_TRAVERSAL);   //異步請求UI繪制
       }
   }
   @Override
   public void handleMessage(Message msg) {
	   switch (msg.what) {
           case DO_TRAVERSAL:
                performTraversals();  //開始UI繪制
                break;
	   }
   }
    
由于performTraversals()方法比較復(fù)雜,我們側(cè)重于第一次設(shè)置root View的widhtSpecSize以及
heightSpecSize值。
         private void performTraversals() {
       // cache mView since it is used so much below...
       final View host = mView;

       mTraversalScheduled = false;         
       boolean surfaceChanged = false;
       WindowManager.LayoutParams lp = mWindowAttributes;  

       int desiredWindowWidth;              //表示該窗口期望width值
       int desiredWindowHeight;             //表示該窗口期望width值
       int childWidthMeasureSpec;           //保存root View的widthMeasureSpec
       int childHeightMeasureSpec;          //保存root View的heightMeasureSpec

       final View.AttachInfo attachInfo = mAttachInfo;

       final int viewVisibility = getHostVisibility();
       boolean viewVisibilityChanged = mViewVisibility != viewVisibility
               || mNewSurfaceNeeded;

       float appScale = mAttachInfo.mApplicationScale;

       WindowManager.LayoutParams params = null;
       if (mWindowAttributesChanged) {
           mWindowAttributesChanged = false;
           surfaceChanged = true;
           params = lp;
       }
       Rect frame = mWinFrame;
       if (mFirst) {   //mFirst表示是否是第一次繪制該Window
           fullRedrawNeeded = true;
           mLayoutRequested = true;

           DisplayMetrics packageMetrics =
               mView.getContext().getResources().getDisplayMetrics();
           //第一次繪制時(shí)desiredWindowWidth,desiredWindowHeight 值大小為屏幕大小
           desiredWindowWidth = packageMetrics.widthPixels;
           desiredWindowHeight = packageMetrics.heightPixels;
           ...
       } else {   //不是第一次繪制,則desiredWindowWidth值為frame保存大小,frame值會由WMS填充
           desiredWindowWidth = frame.width();
           desiredWindowHeight = frame.height();
           ...
       }
       ...
       boolean insetsChanged = false;

       if (mLayoutRequested) {
           ...//獲得root View的widthMeasureSpec 和 heightMeasureSpec值
           childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
           childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
           //開始measure過程
           host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
       }
       ...
       final boolean didLayout = mLayoutRequested;
       
       boolean triggerGlobalLayoutListener = didLayout
               || attachInfo.mRecomputeGlobalAttributes;
       if (didLayout) {
           ... //layout過程
    	   host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);
           ...
       }
       ...
       if (!cancelDraw && !newSurface) {
           mFullRedrawNeeded = false;
           draw(fullRedrawNeeded);
           ...
   }
    
        /**
    * @param windowSize  The available width or height of the window
    *
    * @param rootDimension The layout params for one dimension (width or height) of the window.
   */
   private int getRootMeasureSpec(int windowSize, int rootDimension) {
       int measureSpec;
       switch (rootDimension) {
       case ViewGroup.LayoutParams.MATCH_PARENT:
           // Window can't resize. Force root view to be windowSize.
           measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
           break;
       case ViewGroup.LayoutParams.WRAP_CONTENT:
           // Window can resize. Set max size for root view.
           measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
           break;
       default:
           // Window wants to be an exact size. Force root view to be that size.
           measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
           break;
       }
       return measureSpec;
   }       
    


調(diào)用root View的measure()方法時(shí),其參數(shù)是由getRootMeasureSpec()設(shè)置的,基本思路同我們前面描述的
差不多。貼出來的代碼只是簡簡單單列出了measure 、layout 、 draw 過程的調(diào)用點(diǎn),里面有很多邏輯處理,
閱讀起來比較費(fèi)勁,我也只能算是個(gè)囫圇吞棗水平。大家有興趣地可以看看源碼,加深理解。


最后,由于小子理解水平有限,可能很多地方讓大家“丈二和尚--摸不著頭腦”,給大家兩個(gè)小建議吧:
1、仔細(xì)鉆研源碼 ;
2、想認(rèn)真系統(tǒng)性研讀UI繪制原理的話,建議詳細(xì)閱讀<< Android內(nèi)核剖析 >>第十三章 < UI繪制原理 >



Android中measure過程、WRAP_CONTENT詳解以及xml布局文件解析流程淺析(下)


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 日韩中文字幕一区 | 日本高清中文字幕一区二区三区 | 97人人在线 | 四虎精品影院 | 国产农村妇女毛片精品久久久 | 久久久久欧美精品 | 奇米色在线视频 | 欧洲a老妇女黄大片 | www成人网 | a一级毛片免费高清在线 | 日日日操 | 欧美成人免费tv在线播放 | 伊人爱爱网 | 91福利国产在线观看一区二区 | 99福利在线观看 | 亚洲一级毛片免费看 | 久久伊人最新 | 黄色毛片大全 | 视频一区色眯眯视频在线 | 国产视频在线播放 | 欧美亚洲国产成人高清在线 | 国产一区二区高清在线 | 久久成人国产精品青青 | 免费看国产精品久久久久 | 日韩免费小视频 | 天天做.天天爱.天天综合网 | 成人a大片高清在线观看 | 99久久精品国产自免费 | 欧美大片天天免费看视频 | 国产二区三区毛片 | 在线观看免费精品国产 | 成人精品视频在线观看播放 | 国产三级在线精品男人的天堂 | 一区二区三区在线 | 网站 | 99ri国产| 久久精品成人一区二区三区 | 妖精视频在线看免费视频 | 日本国产成人精品视频 | 久久午夜综合久久 | 模特精品视频一区 | 中文字幕一区中文亚洲 |