• 一. viewMeasureSpec测量取值
  1. 对于DecorView其大小要求要ViewRootImpl中测量

    1. 如果要求MACTH_PARENT,就设置为窗口的大小windowSize, 模式为精确EXACTLY
    2. 如果要求为内容大小WRAP_CONTENT, 就设置为窗口大小windowSize, 模式为至多AT_MOST, 表示不能超过子view这个大小
    3. 默认设置为要求的大小rootDimension, 模式为精确EXACTLY
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
   // ViewRootImpl#getRootMeasureSpec
   private static 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;
       }
  1. 普通子view的大小要受到父ViewGroupMeasureSpec.getMode的值(UNSPECIFIED, EXACTLY, AT_MOST)影响.子view的大小指子view内容+左右margin+左右padding

子view的MeasureSpec

view的测量要从父ViewGroup开始, 在ViewGroup#measureChildWithMargins中, 如果这个view支持margin, padding的话

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// ViewGroup#measureChildWithMargins

protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        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);
    }

// 在计算 margin, padding 后, 调用 getChildMeasureSpec 来获得子 view 大小和模式
// 然后进行子 view 测量循环

// ViewGroup#getChildMeasureSpec
// 该方法结果取值为上表

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

		// 去掉 margin 和 padding 之后的大小, 负值表示超出父 ViewGroup, 则取 0
        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } 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;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }
  • 二. View的工作流程

view的测量分两种情况, 没有子view的测量, 另一种是测量ViewGroup, 此时需要再递归测量

  1. 当测量view时会调用View#measure, 该方法是final不能重写. 如果view没有测量过或者是需要重新测量的, 该方法会调用View#onMeasure, 如有需要可以重写View#onMeasure, 注意, 如果需要view支持padding属性则需要在此处理
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// View#onMeasure

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  	// getSuggestMinimumXXX 主要是检测是否设置了背景, 如果设置了取
  	// mMinXXX 和 background.mMinXXX 的中的最大值. 没有设置则取 mMinXXX
  	// mMinXXX 由 xml 中的 android:minHeight, android:minWidth 来设置

	setMeasuredDimension(
      	getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
		getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

// View#getDefaultSize

public static int getDefaultSize(int size, int measureSpec) {
      int result = size;
      int specMode = MeasureSpec.getMode(measureSpec);
      int specSize = MeasureSpec.getSize(measureSpec);

      switch (specMode) {
        // 该值多用于系统控件的测量
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        // 一般自定义 view 时使用这两个选项
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
      }
      return result;
  }

// View#getSuggestMinimumWidth

// 如果 view 没有背景, 返回 android:minWidth, 默认为0
// 如果 view 有背景, 返回 背景的最小宽度 和 android:minWidth 中的最大值
// 高度下同
protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth :
  			max(mMinWidth, mBackground.getMinimumWidth());
}

// View#getSuggestMinimumHeight

protected int getSuggestedMinimumHeight() {
	return (mBackground == null) ? mMinHeight :
  			max(mMinHeight, mBackground.getMinimumHeight());
}

一般自定义view多使用AT_MOST, EXACTLY来指定大小

这样其specWidth, specHeight就决定其宽/高度的大小. 所以当我们直接继承View来自定义view时, 如果设置view的宽/高为wrap_content时, 结合上表中的值, 可以知道如果我们不测量该view内容的大小, 其最终的大小为父ViewGroup的大小, 和设置match_parent一样. 可以使用以下的方法解决

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 重写该 view 的 onMeasure, 测量内容大小
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 	super.onMeasure(widthMeasureSpec, heightMeasureSpec);

  	int widthMeasureMode = MeasureSpec.getMode(widthMeasureSpec);
  	int widthMeasureSize = MeasureSpec.getSize(widthMeasureSpec);

  	int heightMeasureMode = MeasureSpec.getMode(heightMeasureSpec);
  	int heightMeasureSize = MeasureSpec.getSize(heightMeasureSpec);

  	// mWidth, mHeight 是自己设定的一个默认的宽/高度
  	ViewGroup.LayoutParams lp = getLayoutParams();
  	int width = (widthSpecMode == MeasureSpec.AT_MOST &&
        lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) ? mWidth : widthMeasureSize;
	int height = (heightSpecMode == MeasureSpec.AT_MOST &&
		lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) ? mHeight : heightMeasureSize;
	setMeasuredDimension(width, height)
}
  1. ViewGroupmeasure过程. 对于ViewGroup来说, 除了要测量自己的大小外, 还有递归的测量各个子元素的大小. 由于不同的布局其测量方法定义不同, 所以ViewGroup没有默认的onMeasure方法, 而是需要具体的继承类来实现该方法. onMeasure中调用measureChildren或是measureChildWithMargins来测量子类

    需要注意的是如果不需要子view支持margin时, 使用ViewGroup#measureChildren来测量, 否则应当使用ViewGroup#measureChildWithMargins, 自定义的ViewGroup在重写onMeasure时需要特别注意

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    // ViewGroup#measureChildren
    
    // measureChild 主要递归的调用 view#measure 来测量子 view
    // 的大小, 如果 view 不为 View.GONE 的话
    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) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }
    
    protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();
    
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);
    
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    

    layout过程. 当ViewGroup的位置确定后, 其会调用layout, 并在其中调用 onLayout来遍历子viewlayout方法来确定子view的位置. 子viewlayout会调用自己的onLayout方法. ViewGroup没有默认实现onLayout, 交给具体的布局来实现, 以方法实现不同的布局

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    
    // ViewGroup#layout
    @Override
    public final void layout(int l, int t, int r, int b) {
        if (!mSuppressLayout && (mTransition == null ||
                !mTransition.isChangingLayout())) {
            if (mTransition != null) {
                mTransition.layoutChange(this);
            }
            super.layout(l, t, r, b);
        } else {
            // record the fact that we noop'd it; request layout when transition finishes
            mLayoutCalledWhileSuppressed = true;
        }
    }
    
    // View#layout
    public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }
    
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
    
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);
    
            if (shouldDrawRoundScrollbar()) {
                if(mRoundScrollbarRenderer == null) {
                    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
                }
            } else {
                mRoundScrollbarRenderer = null;
            }
    
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
    
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }
    
        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    
        if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
            mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
            notifyEnterOrExitForAutoFillIfNeeded(true);
        }
    }
    

​ 绘制过程draw. 绘制过程主要分为 1.绘制背景background.draw(canvas), 2.绘制自己onDraw, 3.绘制children.dispatchDraw方法, 4. 绘制装饰onDrawScrollBars. 主要调用View#draw方法

​ 在View中有一个View#setWillNotDraw方法, 设置true表示不绘制该view. 默认为false. 当继承ViewGroup时, 需要手动设置为false关闭该标志位以便通过onDraw来绘制view