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

欧美三区_成人在线免费观看视频_欧美极品少妇xxxxⅹ免费视频_a级毛片免费播放_鲁一鲁中文字幕久久_亚洲一级特黄

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

系統 2319 0

本文原創, 轉載請注明出處 http://blog.csdn.net/qinjuning



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

了解了View樹的轉換過程以及如何設置View的LayoutParams的。本文繼續沿著既定軌跡繼續未完成的job。

主要知識點如下:
1、MeasureSpc類說明
2、measure過程詳解(揭秘其細節);
3、root View被添加至窗口時,UI框架是如何設置其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)要求。每個MeasureSpc實例代表寬度或者高度

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

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

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

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


常用的三個函數:

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

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

static int makeMeasureSpec(int size,int mode) : 根據提供的大小值和模式創建一個測量值(格式)


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

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

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

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

        //創建一個整形值,其高兩位代表mode類型,其余30位代表長或寬的實際值。可以是WRAP_CONTENT、MATCH_PARENT或具體大小exactly size
        public static int makeMeasureSpec(int size, int mode) {
            return size + mode;
        }
        //獲取模式  ,與運算
        public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
        }
        //獲取長或寬的實際值 ,與運算
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

    }
	...
}
  

MeasureSpec類的處理思路是:

①、右移運算,使int 類型的高兩位表示模式的實際值,其余30位表示其余30位代表長或寬的實際值----可以是

WRAP_CONTENT、MATCH_PARENT或具體大小exactly size。


②、通過掩碼MODE_MASK進行與運算 “&”,取得模式(mode)以及長或寬(value)的實際值。


2、measure過程詳解


2.1 measure過程深入分析


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

程。我們重點查看measure過程中地相關方法。

我們說過,當UI框架開始繪制時,皆是從ViewRoot.java類開始繪制的。


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

主要用來與遠端 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(){
    	...
    	//這兩個值我們在后面討論時,在回過頭來看看是怎么賦值的。現在只需要記住其值MeasureSpec.makeMeasureSpec()構建的。
    	int childWidthMeasureSpec; //其值由MeasureSpec類構建 , makeMeasureSpec
        int childHeightMeasureSpec;//其值由MeasureSpec類構建 , makeMeasureSpec
        

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

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

Step 2 、 調用measure()方法去做一些前期準備

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) {
   		//判斷是否為強制布局,即帶有“FORCE_LAYOUT”標記 以及 widthMeasureSpec或heightMeasureSpec發生了改變
        if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
                widthMeasureSpec != mOldWidthMeasureSpec ||
                heightMeasureSpec != mOldHeightMeasureSpec) {

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

            // measure ourselves, this should set the measured dimension flag back
            // 1、 測量該View本身的大小 ; 2 、 設置MEASURED_DIMENSION_SET標記,否則接寫來會報異常。
            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標記
        }

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


參數widthMeasureSpec和heightMeasureSpec 由父View構建,表示父View給子View的測量要求。其值地構建

會在下面步驟中詳解。

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

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

②、添加 LAYOUT_REQUIRED : 表示需要進行layout操作。

最后,保存當前的widthMeasureSpec和heightMeasureSpec值。


Step 3 、 調用onMeasure()方法去真正設置View的長寬值,其默認實現為:

     /**
    * 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
    */
   //設置該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參數一般表示設置了android:minHeight屬性或者該View背景圖片的大小值
   public static int getDefaultSize(int size, int measureSpec) {
       int result = size;  
       int specMode = MeasureSpec.getMode(measureSpec);
       int specSize =  MeasureSpec.getSize(measureSpec);

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

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

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

       mPrivateFlags |= MEASURED_DIMENSION_SET;  //設置了MEASURED_DIMENSION_SET標記
   }
  

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


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

碼表示為:

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


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

確定的呢?父View是如何設定其值的?

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

個子View的大小,基本思想也如同我們 之前描述的思想:遍歷所有子View,設置每個子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的布局要求
//遍歷每個子View,然后調用measureChild()方法去實現每個子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” 狀態
            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
 */
//測量每個子View高寬時,清楚了該View本身的邊距大小,即android:padding屬性 或android:paddingLeft等屬性標記
protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams(); // LayoutParams屬性
    //設置子View的childWidthMeasureSpec屬性,去除了該父View的邊距值  mPaddingLeft + mPaddingRight
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    //設置子View的childHeightMeasureSpec屬性,去除了該父View的邊距值  mPaddingTop + mPaddingBottom
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
  

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

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

設置子View的實際寬高值。

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參數                                    表示該父View本身所占的widthMeasureSpec 或  heightMeasureSpec值
// padding參數                          表示該父View的邊距大小,見于android:padding屬性 或android:paddingLeft等屬性標記
// childDimension參數  表示該子View內部LayoutParams屬性的值,可以是wrap_content、match_parent、一個精確指(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的實際值

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

    int resultSize = 0;    //子View對應地 size 實際值 ,由下面的邏輯條件賦值
    int resultMode = 0;    //子View對應地 mode 值 , 由下面的邏輯條件賦值

    switch (specMode) {
    // Parent has imposed an exact size on us
    //1、父View是EXACTLY的 !
    case MeasureSpec.EXACTLY: 
    	//1.1、子View的width或height是個精確值 (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是個精確值 (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是個精確值 (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;
    }
    //根據上面邏輯條件獲取的mode和size構建MeasureSpec對象。
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
  

為了便于分析,我將上面的邏輯判斷語句使用列表項進行了說明.


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


根據父View的measureSpec值(widthMeasureSpec,heightMeasureSpec)值以及子View的子View內部

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

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

2、2.1等。


例如,分析列表3:假設當父View為MeasureSpec.UNSPECIFIED類型,即未定義時,只有當子View的width

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

,即處于未指定狀態。

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

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

由以下幾個方面影響:

1、父View的 MeasureSpec屬性;

2、子View的 LayoutParams屬性 ;

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

setMeasuredDimension()原型:

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

       mPrivateFlags |= MEASURED_DIMENSION_SET;  //設置了MEASURED_DIMENSION_SET標記
   }
  


將上面列表項轉換為表格為:

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

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


為了幫助大家理解,下面我們分析某個窗口使用地xml布局文件,我們弄清楚該xml布局文件中每個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>   
  

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

②、id為tv的 TextView 控件。


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

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


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

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

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


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

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

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

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


我們繼續窺測下LinearLayout類是如何進行measure過程的:

       public class LinearLayout extends ViewGroup {
		...
		@Override  //onMeasure方法。
		protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		    //判斷是垂直方向還是水平方向,這兒我們假設是VERTICAL垂直方向,
			if (mOrientation == VERTICAL) {
		        measureVertical(widthMeasureSpec, heightMeasureSpec);
		    } else {
		        measureHorizontal(widthMeasureSpec, heightMeasureSpec);
		    }
		}
		//垂直方向布局
	    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
	        mTotalLength = 0;         //該LinearLayout測量子View時的總高度。
	    	float totalWeight = 0;    //所有子View的權重和 , android:layout_weight
	    	int maxWidth = 0;         //保存子View中最大width值
	        ...
	        final int count = getVirtualChildCount();  //子View的個數
	        
	        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有剩余高度時,才真正調用measure()
	            if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
	                ...
	            } else {
	                int oldHeight = Integer.MIN_VALUE;
	                //如果View的hight值為0,并且設置了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).
	                //對每個子View調用measure()方法
	                measureChildBeforeLayout(
	                       child, i, widthMeasureSpec, 0, heightMeasureSpec,
	                       totalWeight == 0 ? mTotalLength : 0);
	                
	                //這三行代碼做了如下兩件事情:
	                //1、獲得該View的measuredHeight值,每個View都會根據他們地屬性正確設置值  > 0 ;
	                //2、更新mTotalLength值:取當前高度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);
	            ...
	        }
            //后續還有很多處理,包括繼續measure()某些符合條件地子View
	        ...
	    }
	    void measureChildBeforeLayout(View child, int childIndex,
	            int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
	            int totalHeight) {
	    	//調用measureChildWithMargins()方法去設置子View大小
	        measureChildWithMargins(child, widthMeasureSpec, totalWidth,
	                heightMeasureSpec, totalHeight);
	    }
		...
	}
  

繼續看看measureChildWithMargins()方法,該方法定義在ViewGroup.java內,基本流程同于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參數  表示該父View已經使用的寬度
	//heightUsed參數  表示該父View已經使用的高度
	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()過程時,LinearLayout類做了如下事情 :

1、遍歷每個子View,對其調用measure()方法;

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

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

2.2 WRAP_CONTENT、MATCH_PARENT以及measure動機揭秘


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

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

確定是有三個部分組成地:

①、父View的MeasureSpec屬性;

②、子View的 LayoutParams屬性 ;

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

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

    //自定義View	
public Class MyView extends View {
	
	 //針對不同地mode值,設置本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()方法,根據傳遞過來的widthMeasureSpec和heightMeasureSpec簡單設置了

View的 mMeasuredWidth 和 mMeasuredHeight值。

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

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

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

Android框架中提供地一系列View/ViewGroup都需要去進行這個measure()過程地 ,因為在layout()過程中,父

View需要調用getMeasuredWidth()或getMeasuredHeight()去為每個子View設置他們地布局坐標,只有確定布局

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

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();
        }
    }
	//對每個子View調用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測量時的實際寬高值,
                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);
    }
	...
}	
  

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

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

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


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

重寫onLayout() 操作即可,因為我們知道如何layout每個子View。 如下代碼所示:


    //自定義ViewGroup , 包含了三個LinearLayout控件,存放在不同的布局位置
public class MultiViewGroup extends ViewGroup {
	private void init() {
		// 初始化3個 LinearLayout控件
		LinearLayout oneLL = new LinearLayout(mContext);
		oneLL.setBackgroundColor(Color.RED);
        addView(oneLL);
        ...
	}
	@Override
	// 我們知曉每個子View的layout布局大小,因此我們不需要為每個子View進行measure()操作了。
//	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//		setMeasuredDimension(width, height);
//		// 設置該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);
//			// 設置每個子視圖的大小 , 即全屏
//			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; // 每個子視圖的起始布局坐標
		int startTop = 10; // 間距設置為10px 相當于 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 ; //校準每個子View的起始布局位置
			//三個子視圖的在屏幕中的分布如下 [0 , 320] / [320,640] / [640,960]
		}
	}
}  
  

更多關于自定義ViewGroup無須重寫measure動作的,可以參考 Android API :

< < Optimizing the View >>

中文翻譯見于:<< Android中View繪制優化之三---- 優化View >>


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


老子道德經有言:“道生一,一生二,二生三,三生萬物。” UI繪制也就是個遞歸過程。理解其基本架構后,
也就“掌握了一個中心點”了。在第一節中,我們沒有說明開始UI繪制時 ,沒有說明mView.measure()參數地由來,
參數也就是我們本節需要弄懂的“道” --- 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>

    
當使用LayoutInflater類解析成View時 ,LinearLayout對象的LayoutParams參數為null 。具體原因請參考上篇博文

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

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

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

    
WindowManager是個接口,具體返回對象則是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屬性添加一個窗口
   public void addView(View view, ViewGroup.LayoutParams params)
   {
       addView(view, params, false);
   }
   //參數nest表示該窗口是不是一個字窗口
   private void addView(View view, ViewGroup.LayoutParams params, boolean nest)
   {   ...
       final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
       
       ViewRoot root;
       View panelParentView = null;    //該子窗口對應地父窗口View
       
       synchronized (this) {
          
    	   ...//需要對傳遞過來地參數進行檢測...
    	   
           //對每個窗口皆構建一個ViewRoot對象
           root = new ViewRoot(view.getContext());
           root.mAddNesting = 1;
           //設置root View 的LayoutParams為wparams,即WindowManager.LayoutParams類型
           view.setLayoutParams(wparams);
           ...//對參數檢測,以及拷貝原有數組...
           
           //將窗口對應地view、root、wparams保存在屬性集合中
           mViews[index] = view;
           mRoots[index] = root;
           mParams[index] = wparams;
       }
       // do this last because it fires off messages to start doing things
       // 調用ViewRoot對象去通知系統添加一個窗口
       root.setView(view, wparams, panelParentView);
   }
   ...
   //這三個數組分別保存了一個窗口對應地屬性
   private View[] mViews;         //root View對象 , View類型
   private ViewRoot[] mRoots;     //ViewRoot類型 , 與WMS通信
   private WindowManager.LayoutParams[] mParams;  //窗口屬性
   
   //WindowManagerImpl實現了單例模式
   private static WindowManagerImpl mWindowManager = new WindowManagerImpl();
}
    


WindowManagerImpl類的三個數組集合保存了每個窗口相關屬性,這樣我們可以通過這些屬性去操作特定的
窗口(例如,可以根據View去更新/銷毀該窗口)。當參數檢查成功時,構建一個ViewRoot對象,并且設置 設置root
View 的LayoutParams為wparams,即WindowManager.LayoutParams類型 。最后調用root.setView()方法去通知
系統需要創建該窗口。我們接下來往下看看ViewRoot類相關操作。
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();  //創建一個InputChannel對象,接受消息
                try {
                    //通知WindowManagerService添加一個窗口
                	res = sWindowSession.add(mWindow, mWindowAttributes,
                            getHostVisibility(), mAttachInfo.mContentInsets,
                            mInputChannel);
                } 
                ...
                view.assignParent(this);  //將root View的父View設置為該ViewRoot對象(實現了ViewParent接口)
                ...
            }
        }
    }
}
    
說明:ViewRoot類繼承了Handler,實現了ViewParent接口

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

Step 4、異步調用請求UI繪制
         /**
    * {@inheritDoc}
    */
   public void requestLayout() {
       checkThread();        //檢查是不是UI線程調用,如果不是UI線程,會報異常
       mLayoutRequested = true;   //置為真,表示需要進行measure和layout過程
       scheduleTraversals();  
   }
   //開始UI繪制流程
   public void scheduleTraversals() {
       if (!mTraversalScheduled) {
           mTraversalScheduled = true;       //防止多次調用
           sendEmptyMessage(DO_TRAVERSAL);   //異步請求UI繪制
       }
   }
   @Override
   public void handleMessage(Message msg) {
	   switch (msg.what) {
           case DO_TRAVERSAL:
                performTraversals();  //開始UI繪制
                break;
	   }
   }
    
由于performTraversals()方法比較復雜,我們側重于第一次設置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();
           //第一次繪制時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;
   }       
    


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


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



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


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

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

【本文對您有幫助就好】

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

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 99在线视频精品 | 四虎影视永久免费观看网址 | 色一欲一性一乱一区二区三区 | 狠狠色丁香婷婷综合久久来 | 视频在线亚洲 | 2021精品国产综合久久 | 亚洲国产一区二区三区四区 | 天天爱天天操 | 欧美亚洲理伦电影毛片在线播放 | 日韩日韩日韩日韩 | 亚洲国产综合精品中文第一区 | 一级毛片真人免费观看 | 亚洲精品福利在线 | 国产精品尤物在线 | 成人高清视频免费观看 | xx00视频| 欧美视频精品 | 亚洲一二三区精品 | 欧美黑人激情 | 日韩高清一区二区 | 国产麻豆一区二区三区 | 免费中文视频 | 黄色资源在线 | 日韩在线无 | 91中文在线观看 | 一区二区三区网站在线免费线观看 | 毛片特级| 亚洲 欧美 精品 | 国产精品成人不卡在线观看 | 极品逼| 欧美日韩一区二区不卡 | 免费日本毛片 | 欧美电影在线观看网站 | 久久天天躁狠狠躁夜夜躁2014 | 伊人国产精品 | 一男一女的一级毛片 | 欧美精品www | 久草在线网址 | 青草草在线观看免费视频 | 久久综合热 | 91在线视频播放 |