View的繪製流程分析之二 -- measure

measure - 測量

確定View的測量寬高

上面說到 performTraversals() 函數的時候,內部調用了 performMeasure()

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

又調用了View中的 measure() 函數!

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {

        // 計算key值
        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
        // 初始化mMeasureCache對象,用來緩存測量結果
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

        // ...

        if (forceLayout || needsLayout) {
            // ...
            // 嘗試去查找緩存
            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            //沒有讀取到緩存或者忽略緩存時
            if (cacheIndex < 0 || sIgnoreMeasureCache) { 
                // 測量自己
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else { // 讀取緩存
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }

           // ...

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }

        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;

        // 緩存
        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
    }
  • 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

來看 onMeasure() 函數

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
  • 1
  • 2
  • 3

setMeasureDimension() 函數倒是很簡單!目的就是存儲計算出來的測量寬高~

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

現在來主要關注 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;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

當specMode 是 UNSPECIFIED 的時候,View的寬/高爲getDefaultSize()的第一個參數,也就是getSuggestedMinimumWidth() 或者 getSuggestedMinimumHeight()

protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }
  • 1
  • 2
  • 3

當View沒有背景時,返回mMinWidth,該變量默認值是0,對應android:minWidth這個屬性。所以,如果不指定該屬性的話,就是0。

如果View有背景,則返回背景的原始寬度。

getSuggestedMinimumHeight()的內部邏輯與getSuggestedMinimumWidth()類似。

所以,當SpecMode是UNSPECIFIED的時候,View的測量寬/高 就是 getSuggestedMinimumWidth() 和 getSuggestedMinimumHeight() 兩個方法的返回值!

當SpecMode是AT_MOST 或者 EXACTLY 時,View的測量 寬/高 就是 measureSpec 中的SpecSize ,也就是測量後的大小~


分析到這裏,View的measure過程也就分析完了~


那麼ViewGroup的measure是什麼時候開始的呢???

還得從DecorView來說起!

在 Activity.attach() 函數中創建了PhoneWindow對象!

public PhoneWindow(Context context, Window preservedWindow) {
        this(context);
        // ...
        if (preservedWindow != null) {
            mDecor = (DecorView) preservedWindow.getDecorView();

            // ... 
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在PhoneWindow的構造函數中,創建了DecorView對象!

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks
  • 1

DecorView繼承FrameLayout,所以它是一個ViewGroup!

所以整個window的繪製是從DecorView這個ViewGroup開始的!

mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  • 1

這裏的mView就是DecorView

其實ViewGroup 是一個抽象類,並沒有重寫onMeasure()函數!ViewGroup的子類們重寫了onMeasure()函數!

既然DecorView繼承了FrameLayout,那就拿FrameLayout來分析一下它的 measure過程。


FrameLayout的measure流程

從decorView的measure() 方法體內看出,內部調用了onMeasure()。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 1. 獲取子View的個數
        int count = getChildCount();
        // 2. 如果寬/高的SpecMode有一個不是EXACTLY,則爲true
        final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
        // 3. 清空集合
        mMatchParentChildren.clear();

        int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;

        // 4. 遍歷子view
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            // 5. GONE類型的view不測量
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                // 6. 測量子view的寬高
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                // 7. 計算最大寬度
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                // 8. 計算最大高度
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
                // 9. 如果measureMatchParentChildren 爲true,並且子view設置的寬/高屬性是match_parent,就把這個子view添加到集合中
                if (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }

        // 10. 補充計算最大寬度,最大高度
        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

        // 11. 再次比較計算最大寬度,最大高度
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

        // 12. 如果有背景,則需要再次與背景圖的寬高相比較得出最大寬高
        final Drawable drawable = getForeground();
        if (drawable != null) {
            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
        }

        // 13. 設置當前ViewGroup的測量寬高
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));

        // 14. 遍歷需要二次測量的子view
        count = mMatchParentChildren.size();
        if (count > 1) {
            for (int i = 0; i < count; i++) {
                final View child = mMatchParentChildren.get(i);
                final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

                final int childWidthMeasureSpec;
                if (lp.width == LayoutParams.MATCH_PARENT) { // 子view寬度設置的是MATCH_PARENT
                    final int width = Math.max(0, getMeasuredWidth()
                            - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
                            - lp.leftMargin - lp.rightMargin);
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                            width, MeasureSpec.EXACTLY);
                } else { // 子view寬度設置的不是MATCH_PARENT
                    childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                            lp.leftMargin + lp.rightMargin,
                            lp.width);
                }

                final int childHeightMeasureSpec;
                if (lp.height == LayoutParams.MATCH_PARENT) { // 子view高度設置的是MATCH_PARENT
                    final int height = Math.max(0, getMeasuredHeight()
                            - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
                            - lp.topMargin - lp.bottomMargin);
                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            height, MeasureSpec.EXACTLY);
                } else { // 子view高度設置不是MATCH_PARENT
                    childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                            getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                            lp.topMargin + lp.bottomMargin,
                            lp.height);
                }
                // 15. 測量子view
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    }
  • 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
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98

從上面FrameLayout的onMeasure()方法體可以看出來,對子view進行了兩次測量,準確的來說不是所有的子view都進行了二次測量~

這是爲什麼呢?

來往下看~

mMatchParentChildren 是一個集合,是用來存儲需要二次測量的子view的!

private final ArrayList<View> mMatchParentChildren = new ArrayList<>(1);
  • 1

那麼都有哪些子view需要放到這個集合裏面進行二次測量呢?

當 measureMatchParentChildren == true 的時候!

final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
  • 1
  • 2
  • 3

那麼什麼情況下 FrameLayout這個ViewGroup 的寬或者高的SpecMode不爲EXACTLY呢?

我在 View的繪製流程分析之一 已經進行了分析!

再來一張圖(轉載~):

這裏寫圖片描述

拋去UNSPECIFIED 這個mode不管,當前FrameLayout這個ViewGroup的測量模式不爲EXACTLY有三種情況!

  1. FrameLayout的父容器的SpecMode爲AT_MOST,並且這個FrameLayout的 寬 / 高 屬性是match_parent
  2. FrameLayout的 寬 / 高 屬性是wrap_content

總而言之,就是FrameLayout這個ViewGroup的寬或高不是一個固定的值,也就是不是EXACTLY模式!

這種情況下,再去判斷子view(FrameLayout的子view)的寬高屬性是否是match_parent,如果是則把這個子view添加到集合中去!

其實很容易理解!通過第一次遍歷所有(不是GONE)的子view之後,就把父佈局,也就是這個FrameLayout的寬高給測量出來了。

但是對於那些寬高設置爲match_parent,它們的寬高依賴於父容器的大小,所以需要再次遍歷它們設置它們的寬高~


measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);

先來看看第一次遍歷時,對子view的測量過程

直接調用的父類ViewGroup中的方法

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);
        // 調用子view的measure()函數進行測量
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

在計算子view的寬度或高度的測量規格時,把父容器的padding值,和子view的margin值 加了上去!

關於getChildMeasureSpec() 這個函數的分析,在上一篇blog已經說過了! getChildMeasureSpec()

那麼得到了寬高的測量規格之後,就可以調用measure進行測量了!

child.measure() 調用了View中的measure() 函數,此函數是final類型!不允許子類重寫!

其實也很容易理解, 所有的容器測量都要先遍歷子view進行測量,然後在確定容器的大小,所以最終實際的任務大多在一個個子view的測量上,容器的大小只需要針對這些子view的測量大小加加減減而已 

view的measure過程在本篇blog上面已經進行了分析!本質上還是調用onMeasure() 函數!

如果這個View是一個ViewGroup,則會回調具體的容器類的onMeasure() 函數,如果不是則調用View或者它的子類(如果重寫了)的onMeasure() 函數!


此時回過頭來再看FrameLayout.onMeasure() 方法體下面這一句

// 13. 設置當前ViewGroup的測量寬高
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
  • 1
  • 2
  • 3

當對所有的子view測量完畢之後,調用這個函數把計算得來的父容器的測量結果進行保存!

重點在 resolveSizeAndState() 函數!

public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
        final int specMode = MeasureSpec.getMode(measureSpec);
        final int specSize = MeasureSpec.getSize(measureSpec);
        final int result;
        switch (specMode) {
            case MeasureSpec.AT_MOST:
                if (specSize < size) { // 父容器給的尺寸小於測量出來的尺寸,改變result值!
                    result = specSize | MEASURED_STATE_TOO_SMALL;
                } else {
                    result = size;
                }
                break;
            case MeasureSpec.EXACTLY: // 精確模式下,結果就是測量的值
                result = specSize;
                break;
            case MeasureSpec.UNSPECIFIED:
            default:
                result = size;
        }
        return result | (childMeasuredState & MEASURED_STATE_MASK);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

計算出來size之後,就通過 setMeasuredDimension() 函數保存測量寬高!

這樣從ViewGroup到一個個子View的測量,保存每個view/viewGroup的測量寬高!

分析到這裏,performTraversals() 函數裏面 performMeasure() 也就執行完畢了!


http://blog.csdn.net/crazy1235/article/details/72633385

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章