View繪製源碼淺析(二)佈局的測量、佈局、繪製

前言

在第一篇View繪製源碼淺析(一)佈局的加載我們知道了setContentView()完成了DecorView的創建,並且將xml中的佈局標籤轉換成了對應的View、屬性轉換成了對應的LayoutParams然後添加到了id爲content的佈局上,也就是說完成了佈局對象的創建並且和DecorView關聯上了。

那麼第二篇將介紹View是如何顯示出來的,主體流程可分爲測量、佈局、繪製這三步。

本文源碼基於API27,接下來我們開始吧。

概述

繪製的開始是在Activity收到AMS的Resume事件,然後給DecorView設置上ViewRootImpl這個視圖結構的頂部對象作爲DecorViewparent,然後通過調用ViewRootImplrequestLayout()觸發測量、佈局、繪製的流程。

對於ViewRootImpl來說是一個包含了父佈局功能的視圖頂層對象(因爲每個View都有parent屬性指向父佈局,而DecorView已經是最外層的佈局了是沒有父佈局的,所以指向的是ViewRootImpl),不過需要注意它不是一個View。

Activity收到Resume事件後最終會走道ViewRootImplsetView()

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        ...
        requestLayout();//觸發測量、佈局、繪製
        res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                                          getHostVisibility(), mDisplay.getDisplayId(),
                                          mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                                          mAttachInfo.mOutsets, mInputChannel);//在window上顯示
        ...
    }

requestLayout()最終會走到performTraversals()這個方法賊雞兒長,我們只看重點

    private void performTraversals() {
        ...
        measureHierarchy(host, lp, res,
                         desiredWindowWidth, desiredWindowHeight);//會調到performMeasure(childWidthMeasureSpec, childHeightMeasureSpec)進行測量
        performLayout(lp, mWidth, mHeight);//佈局
        performDraw();//繪製
        ...
    }

這裏也就引出了我們的重點方法

  • performMeasure()測量
  • performLayout()佈局
  • performDraw()繪製

接下來我們分別介紹這個過程。

測量

看測量前,我們得先了解一個概念measureSpec,它是一個32位的int值,是父佈局的寬高約束和要測量的View的LayoutParams寬高共同作用生成的,作爲測量方法的形參傳入指導View的測量。其中高2位用於存儲測量的模式,低30位用於存儲具體的數值。然後爲了方便生成和取出這個32位的int值,提供了一個工具類MeasureSpec

    public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;//高兩位的掩碼
		//下面是3種模式
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;
        public static final int EXACTLY     = 1 << MODE_SHIFT;
        public static final int AT_MOST     = 2 << MODE_SHIFT;

      	//傳入size和mode生成MeasureSpec
        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

		//拿到mode
        @MeasureSpecMode
        public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }

		//拿到size
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

    }

模式一共是有三種,這裏先簡單介紹下

  1. UNSPECIFIED
    未指定的,大小根據size來決定

  2. EXACTLY
    大小有明確的值,比如width爲32或者macth_parent都適用該模式

  3. AT_MOST
    對應上wrap_content這種情況,size爲View可用的最大值

通過makeMeasureSpec()可生成32位的measureSpecgetMode()getSize()可拿到mode和size。

準備工作做完了,接下來回到主線看測量方法measureHierarchy()

    private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
            final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
        int childWidthMeasureSpec;
        int childHeightMeasureSpec;
        boolean windowSizeMayChange = false;
		...
        if (!goodMeasure) {
                childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);//獲取根佈局的MeasureSpec
                childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);//獲取根佈局的MeasureSpec
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//執行測量方法
                if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) 			{
                    windowSizeMayChange = true;
                }
        }    
		...

        return windowSizeMayChange;
    }

前面不是說measureSpec是根據父佈局的寬高約束和要測量的View的LayoutParams寬高共同作用生成的。而這裏稍有不同因爲DecorView是最外層的佈局了,沒有父佈局給他生成measureSpec參數所以用window去生成一個。

desiredWindowWidth和desiredWindowHeight是屏幕的寬高,lp.width和lp.height默認是match_parent,接下來看下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;
    }

可以看到生成的measureSpec的size爲屏幕的尺寸,mode爲MeasureSpec.EXACTLY,。然後將measureSpec作爲performMeasure()的形參傳入。

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);//這裏mView爲DecorView,即調用DecorView.measure
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

這裏mView爲DecorView爲了簡便我們直接看FrameLayoutmeasure()方法,而FrameLayout並未重寫也沒法重寫因爲是final修飾的,所以最終走到View的measure()

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
		...
        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;//拿到緩存的key
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);//緩存容器

        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;//如果有PFLAG_FORCE_LAYOUT標記會強制測量

        final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
                || heightMeasureSpec != mOldHeightMeasureSpec;//和舊的MeasuerSpec相比有沒有變化
        final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;//模式是不是EXACTLY
        final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
                && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);//看size有沒有變化
        final boolean needsLayout = specChanged
                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);//如果MeasuerSpec有變化 並且 sAlwaysRemeasureExactly 爲true 或者 模式不是Exactly 或者 size有變化

        if (forceLayout || needsLayout) {//強制測量或者需要測量
            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);//如要強制測量就不用拿緩存了
            if (cacheIndex < 0 || sIgnoreMeasureCache) {//沒有緩存或者忽略緩存
                onMeasure(widthMeasureSpec, heightMeasureSpec);//onMeasure執行測量邏輯
                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;//添加需要進行layout的標記
        }

        mOldWidthMeasureSpec = widthMeasureSpec;//緩存widthMeasureSpec
        mOldHeightMeasureSpec = heightMeasureSpec;//緩存heightMeasureSpec

        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL);
    }

從代碼中可以看出measure()中進行了條件判斷需要滿足下列條件其中之一就會觸發測量

  • mPrivateFlags包含PFLAG_FORCE_LAYOUT
  • MeasureSpec和舊的相比有變化並且(sAlwaysRemeasureExactly爲true一般可以忽略默認是false的,MeasureSpec的mode不爲Exactly,MeasureSpec的size和之前測量的值相比有變化)三者滿足其一

真正執行測量的是在onMeasure()中,然後我們看到FrameLayoutonMeasure()

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();//拿到子view數量
        int maxHeight = 0;//最大高度
        int maxWidth = 0;//最大寬度
        int childState = 0;

        for (int i = 0; i < count; i++) {//遍歷子view
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {//子view不爲Gone
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);//測量子View
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);//儲存所有子View中最大的寬度
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);//儲存所有子View中最大的高度
                childState = combineMeasuredStates(childState, child.getMeasuredState());
                if (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }

        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();//最大寬度加上padding
        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();//最大高度加上padding

        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());//和minHeight屬性比較取最大值
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());//和minWidth屬性比較取最大值

        final Drawable drawable = getForeground();//拿到前景圖
        if (drawable != null) {
            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());//和前景圖比較取最大高度
            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());//和前景圖比較取最大寬度
        }
        //到這裏maxHeight和maxWidth所裝的值是該FrameLayout顯示所有內容需要的寬高

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

FrameLayoutViewGroup所以他的測量是遍歷所有子類調用measureChildWithMargins()先完成他們的測量,然後拿到最大的高度和寬度,再加上padding並和前景、min屬性做比較確保這個maxHeight和maxWidth拿到的值能顯示下所有內容,最後通過resolveSizeAndState()拿到最終的測量結果調用setMeasuredDimension()設置給FrameLayout

這裏我們先看他測量子類的measureChildWithMargins()方法。

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

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);//根據父MeasureSpec和子view的lp屬性共同生成子view的MeasureSpec
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);//根據父MeasureSpec和子view的lp屬性共同生成子view的MeasureSpec

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);//觸發View的measure方法
    }

可以看到在getChildMeasureSpec方法中傳入了parentMeasureSpec和子view的lp共同生成的MeasureSpec也印證了我們前面說的。

measureSpec,它是一個32位的int值,是父佈局的寬高約束和要測量的View的LayoutParams寬高共同作用生成的

接下來看他如何生成MeasureSpec

    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);//拿到父佈局的mode
        int specSize = MeasureSpec.getSize(spec);//拿到父佈局的size

        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        case MeasureSpec.EXACTLY://父佈局爲EXACTLY
            if (childDimension >= 0) {//如果子View有明確的值 直接用該值 mode爲EXACTLY
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {//如果子view值爲match_parent則 resultSize爲父佈局的size mode爲EXACTLY
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {//如果子view爲WRAP_CONTENT resultSize爲父佈局的size mode爲AT_MOST告知size爲最大可用值
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        case MeasureSpec.AT_MOST://父佈局爲AT_MOST
            if (childDimension >= 0) {//如果子View有明確的值 直接用該值 mode爲EXACTLY
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {//如果子view值爲match_parent則 resultSize爲父佈局的size mode爲AT_MOST因爲父佈局大小不確定
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {//如果子view爲WRAP_CONTENT resultSize爲父佈局的size mode爲AT_MOST告知size爲最大可用值
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

       //父佈局爲UNSPECIFIED情況比較少這裏就不分析了
        }
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);//生成MeasureSpec
    }

從代碼中可以看到不同情況下生成的MeasureSpec會有差別,不過只要子view寬高有明確值的時候mode一定是Exactly,寬高爲wrap_content的時候mode一定是AT_MOST。然後給子View生成完MeasureSpec後調用的child.measure(childWidthMeasureSpec, childHeightMeasureSpec)這個方法我們前面看過,最終會調到View的onMeasure()方法。

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

並未做啥處理直接通過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://在mode爲AT_MOST和EXACTLY的時候都是直接取的
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

可以看到在mode爲AT_MOST和EXACTLY的時候默認取的是MeasureSpec的size,所以我們寫自定義view的時候需要重寫onMeasure()方法在AT_MOST這種情況下進行測量,不然wrap_content在默認情況下和match_parent沒區別,TextView等常用的View都重寫了該方法可自行查看。

獲取到具體的值後通過setMeasuredDimension()存儲。

    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
		...
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }

    private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;//存儲到成員變量
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

將值存儲到mMeasuredWidthmMeasuredHeight成員變量,這裏子view測量的流程就走完了。

我們在回過頭去看下FrameLayout還沒說完的那部分resolveSizeAndState()setMeasuredDimension()方法。

FrameLayoutViewGroup所以他的測量是遍歷所有子類調用measureChildWithMargins()先完成他們的測量,然後拿到最大的高度和寬度,再加上padding並和前景、min屬性做比較確保這個maxHeight和maxWidth拿到的值能顯示下所有內容,最後通過resolveSizeAndState()拿到最終的測量結果調用setMeasuredDimension()設置給FrameLayout

這裏我們先看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://如果爲Wrap_content則取size和specSize中最小值,即不能超過父佈局的大小
                if (specSize < size) {
                    result = specSize | MEASURED_STATE_TOO_SMALL;
                } else {
                    result = size;
                }
                break;
            case MeasureSpec.EXACTLY://確定的話直接拿specSize
                result = specSize;
                break;
            case MeasureSpec.UNSPECIFIED:
            default:
                result = size;
        }
        return result | (childMeasuredState & MEASURED_STATE_MASK);
    }

根據情況判斷需要拿到的size大小,然後通過setMeasuredDimension()設置,FrameLayout並未重寫該方法最後還是調的View的。

    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }

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

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

這裏測量流程就說完了,我們做一個簡單的總結:

  • 測量的最開始是在Measure()方法,這個方法是View中的final方法無法重寫,當中進行了一些是否需要測量的判斷,真正執行測量的是onMeasure()需要各個子類重寫。

  • 整個View樹的測量是通過遞歸的方式,父View去通知子View測量,當子View測量完成父View在進行自己的測量。

  • 測量完成後就可以通過getMeasuredHeight()getMeasuredWidth()獲取測量寬高。

佈局

佈局就是將測量的好的寬高設置到left、top、right、bottom屬性當中,起點是從ViewRootImpl的performLayout()開始。

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        	...
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());//這裏host既是DecorView
			...
    }

然後調到了DecorViewlayout(),爲了簡便我們還是看FrameLayoutlayout()


    @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);//調用View的layout()
        } 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) {
        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);//一般都是走的setFrame()

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {//判斷和之前有沒有變化,或者有PFLAG_LAYOUT_REQUIRED標記
            onLayout(changed, l, t, r, b);//執行佈局
        }
    }

代碼中可以看到通過setFrame()設置位置並且判斷有沒有變化。如果有變化或者有PFLAG_LAYOUT_REQUIRED標記則觸發onLayout()

先看setFrame()

    protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;
        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {//只要有一個值不同則發生了變化
            ...
            changed = true;
            mLeft = left;//存儲位置值
            mTop = top;
            mRight = right;
            mBottom = bottom;
            ...
        }
        return changed;
    }

可以看到只要left、top、right、bottom有一個值發生了變化則返回值爲true並存儲位置值。

然後看到View的onLayout()

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }

可以看到是空實現,因爲作爲view來說是不需要在處理子View的佈局。

那麼我們看到FrameLayout.onLayout()是如何處理子View的佈局的。

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }

    void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
        final int count = getChildCount();//拿到子View數量

        final int parentLeft = getPaddingLeftWithForeground();//獲取左內邊距
        final int parentRight = right - left - getPaddingRightWithForeground();//獲取右內邊距

        final int parentTop = getPaddingTopWithForeground();//獲取上內邊距
        final int parentBottom = bottom - top - getPaddingBottomWithForeground();//獲取下內邊距
        //其實上面就是拋開padding,子view可以擺放的區域

        for (int i = 0; i < count; i++) {//遍歷子view
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {//不爲gone
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();//拿到子view的lp

                final int width = child.getMeasuredWidth();//拿到子view寬
                final int height = child.getMeasuredHeight();//拿到子view高

                int childLeft;
                int childTop;

                int gravity = lp.gravity;//獲取gravity屬性值
                if (gravity == -1) {
                    gravity = DEFAULT_CHILD_GRAVITY;
                }

                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
				//根據gravity計算left的值 我們直接看最簡單的default分支
                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                        lp.leftMargin - lp.rightMargin;
                        break;
                    case Gravity.RIGHT:
                        if (!forceLeftGravity) {
                            childLeft = parentRight - width - lp.rightMargin;
                            break;
                        }
                    case Gravity.LEFT:
                    default:
                        childLeft = parentLeft + lp.leftMargin;//拿到內邊距parentLeft+leftMargin即爲view的left
                }
				//根據gravity計算top的值 我們直接看最簡單的default分支
                switch (verticalGravity) {
                    case Gravity.TOP:
                        childTop = parentTop + lp.topMargin;
                        break;
                    case Gravity.CENTER_VERTICAL:
                        childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                        lp.topMargin - lp.bottomMargin;
                        break;
                    case Gravity.BOTTOM:
                        childTop = parentBottom - height - lp.bottomMargin;
                        break;
                    default:
                        childTop = parentTop + lp.topMargin;//拿到內邊距parentTop+topMargin即爲view的top
                }

                child.layout(childLeft, childTop, childLeft + width, childTop + height);//計算出子view的right和bottom,調用view.layout通知他執行佈局邏輯
            }
        }
    }

可以看到FrameLayoutonLayout中遍歷子View計算出他們的left、top、right、bottom,然後調用View.layout()設置給他們位置值完成了子View的佈局。

簡單總結下:

  • 佈局的開始是在ViewRootImpl.performLayout()方法。
  • 佈局的過程就是DecorView.layout()中算出子View的位置然後通知子View.layout()不斷遞歸的過程。
  • View.layout()此方法判斷是否需要進行佈局一般不重寫它,如果需要佈局它會調用onLayout(),ViewGroup都會重寫onLayout()完成子View的佈局,View不需要重寫。
  • 佈局完成後即可通過getLeft()getTop()getRight()getBottom()獲取到對應的位置值。

繪製

繪製由ViewRootImpl.performDraw()開始,最後會調用DecorView.draw(),不過DecorView並未重寫draw()最終還是調到View.draw()

    public void draw(Canvas canvas) {
        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

        // Step 1, draw the background, if needed
        int saveCount;

        if (!dirtyOpaque) {
            drawBackground(canvas);//繪製背景
        }

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);//繪製內容

            // Step 4, draw the children
            dispatchDraw(canvas);//讓子View繪製

            drawAutofilledHighlight(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);//繪製前景

            // Step 7, draw the default focus highlight
            drawDefaultFocusHighlight(canvas);

            if (debugDraw()) {
                debugDrawFocus(canvas);
            }

            // we're done...
            return;
        }
		...
    }

可以看到在View.draw()方法中進行了七步,註釋已經很清楚了就不細述了。

我們重點要看的是:

  • onDraw()繪製內容,一般View會重寫該方法完成內容的繪製
  • dispatchDraw()分發繪製讓子View執行繪製,在ViewGroup中重寫了

我們先看到ViewGroup.dispatchDraw()

    @Override
    protected void dispatchDraw(Canvas canvas) {
        int clipSaveCount = 0;
        final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
        if (clipToPadding) {//根據佈局得到的值裁剪畫布
            clipSaveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
            canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
                            mScrollX + mRight - mLeft - mPaddingRight,
                            mScrollY + mBottom - mTop - mPaddingBottom);
        }

        final ArrayList<View> preorderedList = usingRenderNodeProperties
            ? null : buildOrderedChildList();
        final boolean customOrder = preorderedList == null
            && isChildrenDrawingOrderEnabled();
        for (int i = 0; i < childrenCount; i++) {
            while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
                final View transientChild = mTransientViews.get(transientIndex);
                if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                    transientChild.getAnimation() != null) {
                    more |= drawChild(canvas, transientChild, drawingTime);//遍歷執行子View繪製
                }
                transientIndex++;
                if (transientIndex >= transientCount) {
                    transientIndex = -1;
                }
            }

            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
            final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                more |= drawChild(canvas, child, drawingTime);
            }
        }
    }

通過第二步佈局得到的值裁剪畫布,然後遍歷調用drawChild()使子View執行繪製

    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);//最終會調到view.draw()
    }

最終會調到View.draw(),然後又會調用onDraw()dispatchDraw(),像TextView這種都實現了onDraw()完成了自己的繪製。

那麼通過這種遞歸方式就完成了整體的繪製。

簡單總結下繪製:

  • 繪製的起點是在ViewRootImpl.performDraw()
  • 繪製的過程就是觸發View.draw()方法然後其中會調用onDraw()繪製自己的內容,dispatchDraw()觸發子View繪製。

至此,整個View的繪製流程我們就分析完了。若文中有敘述不清晰或是不準確的地方,希望大家能夠指出,謝謝大家:)

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