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

 

轉載請註明出處
http://www.cnblogs.com/crashmaker/p/3549365.html 
From crash_coder linguowu
linguowu0622@gamil.com

 

前言:html

  經過Android 自定義View及其在佈局文件中的使用示例Android 自定義View及其在佈局文件中的使用示例(二),咱們知道了如何使用自定義的View,以及Android繪製View的理論基礎,其包含三個過程,測量View大小(經過onMeasure()方法實現),計算View位置(經過onLayout()方法實現),最後開始繪製(經過onDraw()方法實現),本篇,咱們將結合Android 4.4.2_r1源碼詳細分析測量過程的具體實現.java

  在第一篇裏,咱們提供了一個自定義的View的源代碼,如今引用一下該代碼與測量相關的部分:android

 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)方法,看看它到底都作了些什麼事情:c#

由於咱們自定義的View是繼承自View,因此咱們進入View.java(源碼位置:/frameworks/base/core/java/android/view/View.java)去看看有沒有這個方法:api

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類中使用;好了,此時咱們該抽離出來,回到咱們出發的地方:app

setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));

有了上面的分析過程,咱們知道這個方法中的measureWidth(widthMeasureSpec)是做爲測量到的寬度,measureHeight(heightMeasureSpec)是做爲測量到的高度,而這兩個是須要咱們在自定義的View中去實現的,因爲測量寬度與高度的過程相似,咱們在此文中僅分析measureWidth()的過程,很天然地,咱們看看本地的measureWidth()是如何實現的:less

 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參數,接着ide

         int specMode = MeasureSpec.getMode(measureSpec);
         int specSize = MeasureSpec.getSize(measureSpec);
MeasureSpec.getMode(measureSpec),getMode()?咱們在上一篇文章中的最後,有以下描述:
MeasureSpec:

  該對象封裝了父容器傳遞給子元素的佈局要求,它有三種模式:

1)
UNSPECIFIED:父容器對子元素沒有要求,子元素能夠獲得任意值;
2)
EXACTLY:父窗口決定子元素的大小,子元素將被限定在給定的邊界裏而忽略它自己大小;
3)
AT MOST:子元素至多達到父窗口指定的大小,子元素不能超過這個邊界;

因此咱們會想,getMode()方法,應該就是獲取上述這三種模式之一吧?咱們跟進源碼,看看getMode()都作了哪些事情:oop

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,這樣就能取到它的值了.

 

 轉載請註明出處 http://www.cnblogs.com/crashmaker/p/3549365.html  From crash_coder linguowu linguowu0622@gamil.com
相關文章
相關標籤/搜索