Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 自定義View及其在布局文件中的使用示例(三):結合Android 4.4.2_r1源碼分析onMeasure過程

Android 自定義View及其在布局文件中的使用示例(三):結合Android 4.4.2_r1源碼分析onMeasure過程

編輯:關於Android編程

前言:     通過Android 自定義View及其在布局文件中的使用示例和Android 自定義View及其在布局文件中的使用示例(二),我們知道了如何使用自定義的View,以及Android繪制View的理論基礎,其包含三個過程,測量View大小(通過onMeasure()方法實現),計算View位置(通過onLayout()方法實現),最後開始繪制(通過onDraw()方法實現),本篇,我們將結合Android 4.4.2_r1源碼詳細分析測量過程的具體實現.     在第一篇裡,我們提供了一個自定義的View的源代碼,現在引用一下該代碼與測量相關的部分:   復制代碼  1  @Override  2     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  3         setMeasuredDimension(measureWidth(widthMeasureSpec),  4                 measureHeight(heightMeasureSpec));  5     }  6   7     /**  8      * Determines the width of this view  9      *  10      * @param measureSpec 11      *            A measureSpec packed into an int 12      * @return The width of the view, honoring constraints from measureSpec 13      */ 14     private int measureWidth(int measureSpec) { 15         int result = 0; 16         int specMode = MeasureSpec.getMode(measureSpec); 17         int specSize = MeasureSpec.getSize(measureSpec); 18  19         if (specMode == MeasureSpec.EXACTLY) { 20             // We were told how big to be 21             result = specSize; 22         } else { 23             // Measure the text 24             result = (int) mTextPaint.measureText(mText) + getPaddingLeft() 25                     + getPaddingRight(); 26             if (specMode == MeasureSpec.AT_MOST) { 27                 // Respect AT_MOST value if that was what is called for by 28                 // measureSpec 29                 result = Math.min(result, specSize); 30             } 31         } 32  33         return result; 34     } 35  36     /** 37      * Determines the height of this view 38      *  39      * @param measureSpec 40      *            A measureSpec packed into an int 41      * @return The height of the view, honoring constraints from measureSpec 42      */ 43     private int measureHeight(int measureSpec) { 44         int result = 0; 45         int specMode = MeasureSpec.getMode(measureSpec); 46         int specSize = MeasureSpec.getSize(measureSpec); 47  48         mAscent = (int) mTextPaint.ascent(); 49         if (specMode == MeasureSpec.EXACTLY) { 50             // We were told how big to be 51             result = specSize; 52         } else { 53             // Measure the text (beware: ascent is a negative number) 54             result = (int) (-mAscent + mTextPaint.descent()) + getPaddingTop() 55                     + getPaddingBottom(); 56             if (specMode == MeasureSpec.AT_MOST) { 57                 // Respect AT_MOST value if that was what is called for by 58                 // measureSpec 59                 result = Math.min(result, specSize); 60             } 61         } 62         return result; 63     } 復制代碼 我們可以看到:protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)是一個override的方法,它接收兩個參數,通過字面意思,我們知道,這兩個參數分別為寬度測量規格,高度測量規格,此時,我們會有一個疑問,這兩個參數是從哪裡來的?這個疑問咱們先記下來,給它編個號:Q01,暫時略過,到本文下一部分,我們就知道它的來龍去脈了.接著,我們來看onMeasure方法在本地的實現: setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec)); 我們跟進setMeasuredDimension(int,int)方法,看看它到底都做了些什麼事情:   因為我們自定義的View是繼承自View,所以我們進入View.java(源碼位置:/frameworks/base/core/java/android/view/View.java)去看看有沒有這個方法:   復制代碼 16575    /** 16576     * <p>This method must be called by {@link #onMeasure(int, int)} to store the 16577     * measured width and measured height. Failing to do so will trigger an 16578     * exception at measurement time.</p> 16579     * 16580     * @param measuredWidth The measured width of this view.  May be a complex 16581     * bit mask as defined by {@link #MEASURED_SIZE_MASK} and 16582     * {@link #MEASURED_STATE_TOO_SMALL}. 16583     * @param measuredHeight The measured height of this view.  May be a complex 16584     * bit mask as defined by {@link #MEASURED_SIZE_MASK} and 16585     * {@link #MEASURED_STATE_TOO_SMALL}. 16586     */ 16587    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { 16588        boolean optical = isLayoutModeOptical(this); 16589        if (optical != isLayoutModeOptical(mParent)) { 16590            Insets insets = getOpticalInsets(); 16591            int opticalWidth  = insets.left + insets.right; 16592            int opticalHeight = insets.top  + insets.bottom; 16593 16594            measuredWidth  += optical ? opticalWidth  : -opticalWidth; 16595            measuredHeight += optical ? opticalHeight : -opticalHeight; 16596        } 16597        mMeasuredWidth = measuredWidth; 16598        mMeasuredHeight = measuredHeight; 16599 16600        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; 16601    } 復制代碼 果然,我們在View.java中找到了這個方法的具體實現,通過方法說明,得知此方法必須被onMeasure()方法調用 ,來保存測量到的寬度和高度,否則的話,會在測量時引發異常.通過代碼主線 ,我們知道它將傳進去的兩個參數賦給本地的mMeasuredWidth和mMeasuredHeight變量,以便在View類中使用;好了,此時我們該抽離出來,回到我們出發的地方:   setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec)); 有了上面的分析過程,我們知道這個方法中的measureWidth(widthMeasureSpec)是作為測量到的寬度,measureHeight(heightMeasureSpec)是作為測量到的高度,而這兩個是需要我們在自定義的View中去實現的,由於測量寬度與高度的過程類似,我們在此文中僅分析measureWidth()的過程,很自然地,我們看看本地的measureWidth()是如何實現的:   復制代碼  1 /**  2      * Determines the width of this view  3      *   4      * @param measureSpec  5      *            A measureSpec packed into an int  6      * @return The width of the view, honoring constraints from measureSpec  7      */  8     private int measureWidth(int measureSpec) {  9         int result = 0; 10         int specMode = MeasureSpec.getMode(measureSpec); 11         int specSize = MeasureSpec.getSize(measureSpec); 12  13         if (specMode == MeasureSpec.EXACTLY) { 14             // We were told how big to be 15             result = specSize; 16         } else { 17             // Measure the text 18             result = (int) mTextPaint.measureText(mText) + getPaddingLeft() 19                     + getPaddingRight(); 20             if (specMode == MeasureSpec.AT_MOST) { 21                 // Respect AT_MOST value if that was what is called for by 22                 // measureSpec 23                 result = Math.min(result, specSize); 24             } 25         } 26  27         return result; 28     } 復制代碼 該方法用來確定我們自定義的這個View的寬度,它接收onMeasure()的widthMeasureSpec參數,接著            int specMode = MeasureSpec.getMode(measureSpec);          int specSize = MeasureSpec.getSize(measureSpec); MeasureSpec.getMode(measureSpec),getMode()?我們在上一篇文章中的最後,有如下描述: 復制代碼 MeasureSpec:     該對象封裝了父容器傳遞給子元素的布局要求,它有三種模式:   1) UNSPECIFIED:父容器對子元素沒有要求,子元素可以得到任意值; 2) EXACTLY:父窗口決定子元素的大小,子元素將被限定在給定的邊界裡而忽略它本身大小; 3) AT MOST:子元素至多達到父窗口指定的大小,子元素不能超過這個邊界; 復制代碼 所以我們會想,getMode()方法,應該就是獲取上述這三種模式之一吧?我們跟進源碼,看看getMode()都做了哪些事情:   復制代碼 18341        /** 18342         * Extracts the mode from the supplied measure specification. 18343         * 18344         * @param measureSpec the measure specification to extract the mode from 18345         * @return {@link android.view.View.MeasureSpec#UNSPECIFIED}, 18346         *         {@link android.view.View.MeasureSpec#AT_MOST} or 18347         *         {@link android.view.View.MeasureSpec#EXACTLY} 18348         */ 18349        public static int getMode(int measureSpec) { 18350            return (measureSpec & MODE_MASK); 18351        } 復制代碼 由此方法的文字描述部分,我們得知,該方法從接收的參數measureSpec中,獲取到對應的三種模式之一,即返回measureSpec & MODE_MASK,這裡的MODE_MASK又是個什麼東西呢?在View.java中,我們找到在View這個類中,有個內部類MeasureSpec類   復制代碼 18289    public static class MeasureSpec { 18290        private static final int MODE_SHIFT = 30; 18291        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;         ..............................................................  18297 public static final int UNSPECIFIED = 0 << MODE_SHIFT;  18298   18299 /**   18300 * Measure specification mode: The parent has determined an exact size   18301 * for the child. The child is going to be given those bounds regardless   18302 * of how big it wants to be.   18303 */   18304 public static final int EXACTLY = 1 << MODE_SHIFT;   18305   18306 /**   18307 * Measure specification mode: The child can be as large as it wants up   18308 * to the specified size.   18309 */   18310 public static final int AT_MOST = 2 << MODE_SHIFT;             ................................       } 復制代碼 所以,MODE_MASK的值為0x3左移了MODE_SHIFT(30)位,那麼,用32位的二進制來表示的話,MODE_MASK為:1100 0000 0000 0000 0000 0000 0000 0000;如果非要探究此時的measureSpec & MODE_MASK後的值是多少,那麼我們不妨用Debug模式調試一下我們的代碼來獲取getMode方法中傳進來的參數measureSpec是什麼值, 首先,從上面的源碼中,可以知道三種MeasureSpec三種模式的值:   復制代碼 UNSPECIFIED = 0 << MODE_SHIFT;即:UNSPECIFIED為:0000 0000 0000 0000 0000 0000 0000 0000   其實我們也可以參閱官方文檔對此值的定義: public static final int UNSPECIFIED Added in API level 1 Measure specification mode: The parent has not imposed any constraint on the child. It can be whatever size it wants.   Constant Value: 0 (0x00000000) 注:只不過官方文檔此處用十六進制表示而已,以下兩個模式也都用十六進制表示而已.   復制代碼 復制代碼 EXACTLY = 1 << MODE_SHIFT;即 EXACTLY為:0100 0000 0000 0000 0000 0000 0000 0000   public static final int EXACTLY Added in API level 1 Measure specification mode: The parent has determined an exact size for the child. The child is going to be given those bounds regardless of how big it wants to be.   Constant Value: 1073741824 (0x40000000)   復制代碼 復制代碼 AT_MOST = 2 << MODE_SHIFT;即 AT_MOST為:1000 0000 0000 0000 0000 0000 0000 0000   public static final int AT_MOST Added in API level 1 Measure specification mode: The child can be as large as it wants up to the specified size.   Constant Value: -2147483648 (0x80000000) 復制代碼 MODE_MASK為:1100 0000 0000 0000 0000 0000 0000 0000     好,我們來看一下debug前,自定義的View在布局文件中的layout_width的配置及我所調試的設備的屏幕像素為480*800,也就是我的顯示屏寬為480像素;   復制代碼 <com.project.summary.customview.CustomView android:id="@+id/customView" android:layout_width="wrap_content" android:layout_height="wrap_content" app:colorValue="@color/textRed" app:textSize="20sp" app:textString="This the Custom View1!!!" /> 復制代碼 調試結果出來,此時傳入的measureSpec的值是-2147483648,到了這裡,我們又會產生一個疑問 ,為什麼是它?為什麼是這個值?我們先把這個疑問做個標記:Q02;到了文章最後,這個疑問就能解開了,這裡先把思路跳出來,繼續分析我們的measureWidth()這個本地方法的代碼;   復制代碼  1 /**  2      * Determines the width of this view  3      *   4      * @param measureSpec  5      *            A measureSpec packed into an int  6      * @return The width of the view, honoring constraints from measureSpec  7      */  8     private int measureWidth(int measureSpec) {  9         int result = 0; 10         int specMode = MeasureSpec.getMode(measureSpec); 11         int specSize = MeasureSpec.getSize(measureSpec); 12  13         if (specMode == MeasureSpec.EXACTLY) { 14             // We were told how big to be 15             result = specSize; 16         } else { 17             // Measure the text 18             result = (int) mTextPaint.measureText(mText) + getPaddingLeft() 19                     + getPaddingRight(); 20             if (specMode == MeasureSpec.AT_MOST) { 21                 // Respect AT_MOST value if that was what is called for by 22                 // measureSpec 23                 result = Math.min(result, specSize); 24             } 25         } 26  27         return result; 28     } 復制代碼 上面我們已經分析到第10行,由於第11行是獲取傳入的measureSpec的大小,過程與獲取傳入的measureSpec的模式類似,這裡暫時先略過,接下來看第13行代碼,這裡要對獲取到的模式進行判斷,由上一篇文章,我們知道,如果自定義的View在布局文件中指定固定大小,那麼,它的模式就是屬於MeasureSepc.EXACTLY,此時,measureWidth()這個本地方法就返回11行所得的大小,否則進入另外一個分支,因為本系列中我們實現的實現上是一個類似於TextView的自定義控件,那麼,這個View的大小就應該由它所繪制的文字長度來決定,此時,我們先計算出文字的寬度,然後再對其模式進行判斷,如果模式是屬於measureSpec.AT_MOST,我們通過數學運算,比較文字長度與通過傳入的measureSpec所包含的大小,它們之中更小的那個做為我們控件的寬度.     文章開頭的相關代碼中,本地方法:getMeasureHeight()的過程與本地方法getMeasureWidth()類似,在此不再分析.     在此總結一下,文章開頭引用的代碼是我們在編寫自定義View時,在重寫onMeasure()這個方法時的一般步驟,那麼,本文中的分析過程中還留有兩個疑問:       Q01: protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)是一個override的方法,它接收兩個參數,通過字面意思,我們知道,這兩個參數分別為寬度測量規格,高度測量規格,此時,我們會有一個疑問,這兩個參數是從哪裡來的? Q02: 調試結果出來,此時傳入的measureSpec的值是-2147483648,到了這裡,我們又會產生一個疑問 ,為什麼是它?為什麼是這個值? 要探究這兩個疑問,我們在本系列第二篇文章中,曾經提過Android繪制View的理論基礎,從那篇文章中,我們明白,Android要繪制View的時候,必須要先遍歷View的樹形結構,並且先從最頂端的結點開始遍歷,通過查找官方文檔,我們進入   ViewRootImpl.java(文件位於:/frameworks/base/core/java/android/view/ViewRootImpl.java),一起找出上面的那兩個疑問.........   /*********************************友情提醒:開始下面的探究前,最好先休息一下*********************************/   我們先大致浏覽一下ViewRootImpl.java,這個文件代碼有6707行有沒有,不用怕,我們先找到一個叫performtraversals()的方法,看這字面意思,它是要開始遍歷的節奏啊,果斷跟進去看一下,順便找找幾個有用的干貨:   復制代碼 private void performTraversals() { ....................................... 1122     WindowManager.LayoutParams lp = mWindowAttributes;//詳見分析PERFORMTRAVERSALS()點1   ......................................................... 1155 Rect frame = mWinFrame;//詳見分析PERFORMTRAVERSALS()點2 ....................................................... 1563 if (mWidth != frame.width() || mHeight != frame.height()) { 1564 mWidth = frame.width(); 1565 mHeight = frame.height(); 1566 } 1567 ....................................................................... PERFORMTRAVERSALS()點3: 1634 if (!mStopped) { 1635 boolean focusChangedDueToTouchMode = ensureTouchModeLocally( 1636 (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0); 1637 if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() 1638 || mHeight != host.getMeasuredHeight() || contentInsetsChanged) { 1639 int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);//詳見getRootMeasureSpec()方法的分析 1640 int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); 1641 1642 if (DEBUG_LAYOUT) Log.v(TAG, "Ooops, something changed! mWidth=" 1643 + mWidth + " measuredWidth=" + host.getMeasuredWidth() 1644 + " mHeight=" + mHeight 1645 + " measuredHeight=" + host.getMeasuredHeight() 1646 + " coveredInsetsChanged=" + contentInsetsChanged); 1647 1648 // Ask host how big it wants to be 1649 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); } 復制代碼       /************************************分析PERFORMTRAVERSALS()點1 開始**********************************/   這裡的lp用得還挺多,也許對我們有用,   因為   WindowManager.LayoutParams lp = mWindowAttributes; 所以我們分析一下這個mWindowAttributes是何方神聖: 分析PERFORMTRAVERSALS()點1:mWindowAttributes相關代碼:   final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams(); 我們進入WindowManager類的內部類LayoutParams的構造方法   1 public LayoutParams() { 2 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 3 type = TYPE_APPLICATION; 4 format = PixelFormat.OPAQUE; 5 } 其中有這麼一句:注意兩個參數都為LayoutParams.MATCH_PARENT   super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 因為WindowManager類的內部類LayoutParams繼承自ViewGroup.LayoutParams,所以進入ViewGroup的內部類LayoutParams看一下 /frameworks/base/core/java/android/view/ViewGroup.java:   復制代碼 5829 public static class LayoutParams { 5830 /** 5831 * Special value for the height or width requested by a View. 5832 * FILL_PARENT means that the view wants to be as big as its parent, 5833 * minus the parent's padding, if any. This value is deprecated 5834 * starting in API Level 8 and replaced by {@link #MATCH_PARENT}. 5835 */ 5836 @SuppressWarnings({"UnusedDeclaration"}) 5837 @Deprecated 5838 public static final int FILL_PARENT = -1;   ..........................................     5918 public LayoutParams(int width, int height) { 5919 this.width = width; 5920 this.height = height; 5921 } 復制代碼 分析總結:這裡的width與height,都被賦為LayoutParams.MATCH_PARENT,所以這裡的lp的寬與高,都為LayoutParams.MATCH_PARENT   /************************************分析PERFORMTRAVERSALS()點1 結束**********************************/   ######################################################################################################################   /************************************分析PERFORMTRAVERSALS()點2 開始**********************************/   1563 if (mWidth != frame.width() || mHeight != frame.height()) { 1564 mWidth = frame.width(); 1565 mHeight = frame.height(); 1566 }) 此時的mWidth為ViewRootImpl的變量,在這裡使它的值為frame.width()的值; frame又是從哪裡來的呢?在performTraversals()方法中,1155行,原來它只是個局部變量,   1155        Rect frame = mWinFrame; 到了這裡,關鍵就是找出mWinFrame了,繼續找mWinFrame: 在ViewRootImpl的變量聲明中:   256 final Rect mWinFrame; // frame given by window manager. 在ViewRootImpl這個類的構造方法中:   360 mWinFrame = new Rect(); frame given by window manager?那大概就是說mWinFrame是由窗口管理類來賦值的了,那麼這麼裡mWinFrame應該就是屏幕的窗口大小了.我們這裡先這麼假設,後續文章再進行驗證.   /**********************************************分析PERFORMTRAVERSALS()點2 結束**************************************/     /**********************************************分析PERFORMTRAVERSALS()點3開始**************************************/ PERFORMTRAVERSALS()點3:performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); 1,兩個參數:childWidthMeasureSpec,childHeightMeasureSpec分析 a)childWidthMeasureSpec: int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); 先分析getRootMeasureSpec的兩個參數: 1)mWidth: 見分析點2( 1563 if (mWidth != frame.width() || mHeight != frame.height()) { 1564 mWidth = frame.width(); 1565 mHeight = frame.height(); 1566 }) 所以猜想mWidth就是窗口的初始寬度(本文暫未驗證) 2)lp.width:這裡的lp就是分析點1中的 WindowManager.LayoutParams lp = mWindowAttributes;即:lp.width為LayoutParams.MATCH_PARENT; 由以上1)和2),我們先搞定了getRootMeasureSpec(mWidth,lp.width)這個方法的兩個參數的意義,接下來,我們進入getRootMeasureSpec(mWidth,lp.width)這個方法   b)childHeightMeasureSpec: int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); 先分析getRootMeasureSpec的兩個參數: 1)mHeight:類似上述的猜想,這裡的mHeight就是窗口的初始高度 2)lp.height:這裡的lp就是分析點1中的 WindowManager.LayoutParams lp = mWindowAttributes;即:lp.height為LayoutParams.MATCH_PARENT;   因為上述a)與b)的調用過程類似,只不過a)是獲取寬度的規格,b)是獲取高度的規格,所以以下分析只以獲取寬度規格的過程來分析 *******************************************************************進入getRootMeasureSpec()方法的分析**********************************************   復制代碼 1924 private static int getRootMeasureSpec(int windowSize, int rootDimension) { 1925 int measureSpec; 1926 switch (rootDimension) { 1927 1928 case ViewGroup.LayoutParams.MATCH_PARENT: 1929 // Window can't resize. Force root view to be windowSize. 1930 measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); 1931 break; 1932 case ViewGroup.LayoutParams.WRAP_CONTENT: 1933 // Window can resize. Set max size for root view. 1934 measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); 1935 break; 1936 default: 1937 // Window wants to be an exact size. Force root view to be that size. 1938 measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); 1939 break; 1940 } 1941 return measureSpec; 1942 } 復制代碼 此方法接收的第二個參數rootDimension,就是lp.width,通過上面的分析,lp.width=LayoutParams.MATCH_PARENT,所以,進入第一個switch分支 此方法的返回值measureSpec=MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); 所以,分析此方法,我們也知道,當我們的自定義View的layout_width/layout_height設置成MATCH_PARENT時,MODE 為MeasureSpec.EXACTLY;當設置成WRAP_CONTENT時,MODE為MeasureSpec.AT_MOST;   接下來我們分析1938行:   1938 measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);   ***********************************************************************************************************************************************************   *******************************************************************進入MeasureSpec.makeMeasureSpec()方法的分析**********************************************   復制代碼 17245 /** 17246 * Creates a measure specification based on the supplied size and mode. 17247 * 17248 * The mode must always be one of the following: 17249 * <ul> 17250 * <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li> 17251 * <li>{@link android.view.View.MeasureSpec#EXACTLY}</li> 17252 * <li>{@link android.view.View.MeasureSpec#AT_MOST}</li> 17253 * </ul> 17254 * 17255 * @param size the size of the measure specification 17256 * @param mode the mode of the measure specification 17257 * @return the measure specification based on size and mode 17258 */ 17259 public static int makeMeasureSpec(int size, int mode) { 17260 return size + mode; 17261 } 復制代碼   此方法在/frameworks/base/core/java/android/view/View.java中的內部類MeasureSpec中的方法,該方法返回兩個參數size+mode之和,參數size對應我們傳進來的windowSize,即:窗口的初始寬度(當傳進來的是mHeight時,為窗口的初始高度); 參數mode對應我們傳進來的MeasureSpec.EXACTLY   ************************************************************************************************************************************************************************** 有了上面這些分析之後,我們可以進入performMeasure(childWidthMeasureSpec, childHeightMeasureSpec)的分析了:   復制代碼 1913 private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { 1914 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); 1915 try { 1916 mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); 1917 } finally { 1918 Trace.traceEnd(Trace.TRACE_TAG_VIEW); 1919 } 1920 } 復制代碼     *************************************mView.measure(childWidthMeasureSpec, childHeightMeasureSpec)的分析************************************************************   復制代碼 16450    /** 16451     * <p> 16452     * This is called to find out how big a view should be. The parent 16453     * supplies constraint information in the width and height parameters. 16454     * </p> 16455     * 16456     * <p> 16457     * The actual measurement work of a view is performed in 16458     * {@link #onMeasure(int, int)}, called by this method. Therefore, only 16459     * {@link #onMeasure(int, int)} can and must be overridden by subclasses. 16460     * </p> 16461     * 16462     * 16463     * @param widthMeasureSpec Horizontal space requirements as imposed by the 16464     *        parent 16465     * @param heightMeasureSpec Vertical space requirements as imposed by the 16466     *        parent 16467     * 16468     * @see #onMeasure(int, int) 16469     */ 16470    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {          ..................................................... 16496                // measure ourselves, this should set the measured dimension flag back 16497                onMeasure(widthMeasureSpec, heightMeasureSpec); 16498                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;   16522    } 復制代碼 這裡的measure()方法是個final方法,結合該方法的說明,   The actual measurement work of a view is performed in onMeasure() 並且measure的兩個參數同時傳入onMeasure()中,   所以,才有了文章開頭時引用的代碼,在自定義的View中,重寫onMeasure()方法,那麼,本文上部分遺留下來的兩個問題,至此就有了答案:   Q01: protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)是一個override的方法,它接收兩個參數,通過字面意思,我們知道,這兩個參數分別為寬度測量規格,高度測量規格,此時,我們會有一個疑問,這兩個參數是從哪裡來的?   通過:1639 int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);//詳見getRootMeasureSpec()方法的分析,onMeasure的第一個參數widthMeasureSpec就是這裡的childWidthMeasureSpec,heightMeasureSpec對應 childHeightMeasureSpec; 復制代碼 Q02: 調試結果出來,此時傳入的measureSpec的值是-2147483648,到了這裡,我們又會產生一個疑問 ,為什麼是它?為什麼是這個值?   那麼這裡的measureSpec就是MeasureSpec.makeMeasureSpec()方法的分析中,返回的size+mode;size是手機顯示屏的像素寬或者高,文章上半部分中,我調試的手機像素寬是480,而且在自定義的View的布局文件中,layout_width設置成wrap_content,通過上面的分析,當設置成wrap_content時,模式為AT_MOST模式,通過文檔描述,它的十進制值是-2147483648,那麼size+mode就是480+(-2147483648)=-2147483168,也就是我們調試出來時,所得到的值-2147483648 ) 復制代碼 另外,我們或許還會有一個疑問 :為什麼MODE_MASK是1100 0000 0000 0000 0000 0000 0000 0000?EXACTLY為:0100 0000 0000 0000 0000 0000 0000 0000?   AT_MOST為:1000 0000 0000 0000 0000 0000 0000 0000?   其實對於這個問題,我們想,既然android規定了MODE必須是EXACTLY,AT_MOST,UNSPECIFIED這三種模式之一,那麼,就可以用32位二進制的最高兩位來表示,它有00,01,10,11這四種情況,那麼它的MODE_MASK取值為   1100 0000 0000 0000 0000 0000 0000 0000就能很方便地取到它的模式了,由getMode()的實現:   return (measureSpec & MODE_MASK); 我們就可以取到它的最高兩位,由此來確定它是哪種模式;同理對於getSize():     public static int getSize(int measureSpec) {             return (measureSpec & ~MODE_MASK);         } 對於屏幕寬度,再大的屏幕也用不了32位二進制來表示其尺寸,所以才有measureSpec & ~MODE_MASK,這樣就能取到它的值了.
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved