Android 淺談View的繪制流程

ViewRoot

在介紹View的繪制前,首先我們需要知道是誰(shuí)負(fù)責(zé)執(zhí)行View繪制的整個(gè)流程御板。實(shí)際上,View的繪制是由ViewRoot來(lái)負(fù)責(zé)的。每個(gè)應(yīng)用程序窗口的decorView都有一個(gè)與之關(guān)聯(lián)的ViewRoot對(duì)象画髓,這種關(guān)聯(lián)關(guān)系是由WindowManager來(lái)維護(hù)的。
在Activity啟動(dòng)時(shí)平委,ActivityThread.handleResumeActivity()方法中建立了它們兩者的關(guān)聯(lián)關(guān)系奈虾。在ActivityThread中,當(dāng)Activity對(duì)象被創(chuàng)建完畢后廉赔,會(huì)將DecorView添加到Window中肉微,同時(shí)會(huì)創(chuàng)建ViewRootImpl對(duì)象,并將ViewRootImpl對(duì)象和DecorView建立關(guān)聯(lián)蜡塌,這個(gè)過(guò)程的源碼如下:

root = new ViewRootImpl(view.getContext(),display);
root.setView(view,wparams,panelParentView);

View繪制的起點(diǎn)

View繪制的起點(diǎn)是以ViewRootImpl的performTraversals()方法被調(diào)用開(kāi)始的碉纳。下面,我們以performTraversals()為起點(diǎn)馏艾,來(lái)分析View的整個(gè)繪制流程劳曹。他經(jīng)過(guò)measure layout draw三個(gè)過(guò)程才能最終將一個(gè)View繪制出來(lái)。
其大致流程見(jiàn)下圖:

還有這開(kāi)發(fā)藝術(shù)上的經(jīng)典:

MeasureSpec

在具體講解view繪制三大過(guò)程之前攒至,我們先看看MeasureSpec這個(gè)概念厚者。MeasureSpec代表一個(gè)32位的int值,高兩位代表SpecMode,低兩位代表SpecSize迫吐。SpecMode是指測(cè)量模式库菲,SpecSize是指在某種測(cè)量模式下的規(guī)格大小。下面我們看看具體的代碼(節(jié)省篇幅志膀,源碼中的注釋已刪除):

 public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

        /** @hide */
        @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
        @Retention(RetentionPolicy.SOURCE)
        public @interface MeasureSpecMode {}

       
        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;

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

    
        public static int makeSafeMeasureSpec(int size, int mode) {
            if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
                return 0;
            }
            return makeMeasureSpec(size, mode);
        }

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

         
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

這里MeasureSpec通過(guò)將SpecMode熙宇、SpecSiza打包成一個(gè)int值,并且提供了獲取SpecMode和SpecSize的方法溉浙,分別對(duì)應(yīng)的getMode和getSize方法烫止。可以看到一組SpecMode戳稽、SpecSize可以很容易打包成一個(gè)MeasureSpec馆蠕,而一個(gè)MeasureSpec也可以很容易得到他的測(cè)量模式以及view的規(guī)格大小。

SpecMode

簡(jiǎn)單介紹下SpecMode惊奇,有三種互躬,如下:

1. UNSPECIFIED

父布局不對(duì)View有任何限制,要多大有多大颂郎,這種情況下一般只使用于系統(tǒng)內(nèi)部吼渡,表示一種測(cè)量狀態(tài)

2. EXACTLY

父容器已經(jīng)檢測(cè)出View所需要的精確大小,這個(gè)時(shí)候View的最終的大小就是這個(gè)SpecSize所指定的值乓序。它對(duì)應(yīng)于LayoutParams中的match_parent和具體的數(shù)值這兩種模式

3. AT_MOST

父布局指定了一個(gè)可用大小即SpecSize寺酪,View的大小不能大于這個(gè)值坎背,具體是什么值要看不同的View的具體實(shí)現(xiàn)。他對(duì)應(yīng)于LayoutParams中的wrap_content.

Measure過(guò)程

measure過(guò)程分為View和ViewGroup的measure過(guò)程寄雀。如果是一個(gè)原始的view得滤,那么通過(guò)measure方法就可以完成其測(cè)量過(guò)程。而ViewGroup的測(cè)量除了要完成自己的測(cè)量值外咙俩,還要遍歷所有子View并調(diào)用他們的measure耿戚,各個(gè)元素再遞歸執(zhí)行這個(gè)流程湿故。

1.ViewGroup的Measure流程

Android系統(tǒng)的視圖結(jié)構(gòu)的設(shè)計(jì)也采用了組合模式阿趁,即View作為所有圖形的基類(lèi),Viewgroup對(duì)View繼承擴(kuò)展為視圖容器類(lèi)坛猪,由此就得到了視圖部分的基本結(jié)構(gòu)--樹(shù)形結(jié)構(gòu)

ViewGroup是一個(gè)抽象類(lèi)脖阵,他沒(méi)有重寫(xiě)onMeasure方法,但提供了一個(gè)measureChildren的方法墅茉。源碼如下:

  /**
     * Ask all of the children of this view to measure themselves, taking into
     * account both the MeasureSpec requirements for this view and its padding.
     * We skip children that are in the GONE state The heavy lifting is done in
     * getChildMeasureSpec.
     *
     * @param widthMeasureSpec The width requirements for this view
     * @param heightMeasureSpec The height requirements for this view
     */
    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        //對(duì)所有子View進(jìn)行遍歷
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            //當(dāng)View不為GONE狀態(tài)時(shí)命黔,進(jìn)行測(cè)量
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

這個(gè)方法就實(shí)現(xiàn)了遍歷子View的大小,再看看上面代碼最后調(diào)用的measureChild方法:

  /**
     * Ask one of the children of this view to measure itself, taking into
     * account both the MeasureSpec requirements for this view and its padding.
     * The heavy lifting is done in getChildMeasureSpec.
     *
     * @param child The child to measure
     * @param parentWidthMeasureSpec The width requirements for this view
     * @param parentHeightMeasureSpec The height requirements for this view
     */
    protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();
//可以看出子View的MeasureSpec由父View及其自己的MeasureSpec組成就斤,而且還加入了padding值悍募,這是因?yàn)?//要考慮到父View被占的大小,這樣最終的大小才組成了子View的MeasureSpec
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);
//這里又調(diào)用了子View的measure方法洋机,使子view繼續(xù)遍歷測(cè)量它的子view坠宴,這樣就實(shí)現(xiàn)了遍歷測(cè)量了
//整個(gè)ViewGroup里的所有View
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

這里有出現(xiàn)了兩個(gè)新方法,不急我們一個(gè)個(gè)的來(lái)看看绷旗,先看getChildrenMeasureSpec方法喜鼓,剛剛我們知道了子View的MeasureSpec由父View及其自己的MeasureSpec組成,那到底是根據(jù)是很么樣的規(guī)則決定的呢衔肢。我們一起看看這個(gè)方法的源碼:

   public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
//這里表示父View可用大小為父View的尺寸減去padding值(父View被占用的大凶)的結(jié)果
        int size = Math.max(0, specSize - padding);
        
        int resultSize = 0;
        int resultMode = 0;
//這里的specMode是父View的,也就是說(shuō)先根據(jù)父View的測(cè)量模式再對(duì)應(yīng)子View的測(cè)量模式?jīng)Q定
//子View的specMode和specSize角骤。
        switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
            //如果父View是EXACTLY模式隅忿,而子View的大小設(shè)置值不小于0,那么子View的specSize
            //就為子View的大小設(shè)置值邦尊,specMode就為EXACTLY模式
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
            //如果父View是EXACTLY模式背桐,而子View設(shè)置的是MACTH_PARENT,那么子View的specSize就為父
             //View的值胳赌,specMode設(shè)置為EXACTLY模式
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            //如果父View是EXACTLY模式牢撼,而子View設(shè)置的是WRAP_CONTENT,那么子View的specSize就為父
            //View的值疑苫,specMode設(shè)置為AT_MOST模式
                // 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
    //最后將子view的measureSpec打包完成
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

看完這個(gè)方法纷责,再結(jié)合前面所講,我們已經(jīng)有了個(gè)明確的意識(shí)撼短,那就是ViewGroup遍歷測(cè)量子View時(shí)再膳,子View的大小,也就是測(cè)量結(jié)果不僅只是子View自身決定曲横,而是由父容器的MeasureSpec和子View的LayoutParams(當(dāng)然還要考慮到View的margin和padding值相關(guān))共同決定子View的MeasureSpec喂柒。上面代碼給我們展示了是如何共同決定的,如果還不直觀禾嫉,我們?cè)诳纯催@張圖:

對(duì)于DecorView來(lái)說(shuō)灾杰,它的MeasureSpec與普通View不太相同。它由窗口的尺寸和其自身的LayoutParams共同決定熙参。對(duì)于DecorView來(lái)說(shuō)艳吠,在ViewRootImpl中的measureHierarchy方法中有如下代碼:

       if (!goodMeasure) {
       //這里展示了DecorView的MeasureSpec的創(chuàng)建過(guò)程
       //desiredWindowWidth,desiredWindowHeight為屏幕的寬高
            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
                windowSizeMayChange = true;
            }
        }

再看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;
    }

這里的規(guī)則比較簡(jiǎn)單了:根據(jù)他的LayoutParams中的參數(shù)來(lái)劃分:

  1. LayoutParams.MATCH_PARENT:精確模式,大小就是窗口的尺寸
  2. LayoutParams.WRAP_CONTENT:最大模式孽椰,大小不定昭娩,都不能超過(guò)窗口的尺寸
  3. 固定大小:精確模式黍匾,大小就為指定大小

到這里我們就看完了ViewGroup的measureChildren方法栏渺,measureChild方法,以及getChildMeasureSpec方法锐涯,而這系列的方法簡(jiǎn)單的來(lái)說(shuō)就是實(shí)現(xiàn)了ViewGruop遍歷其下所有View并生成對(duì)應(yīng)View的MeasureSpec這個(gè)過(guò)程磕诊。那我們就想知道最后每一個(gè)View的測(cè)量過(guò)程如何實(shí)現(xiàn),那就看第二小點(diǎn)全庸,View的Measure過(guò)程秀仲。

2、View的Measure過(guò)程

View的masure過(guò)程由ViewGroup傳遞,這個(gè)方法叫measureChildWithMargins方法壶笼,顧名思義神僵,這個(gè)方法與margin值有關(guān)。我們還要看看這個(gè)方法:

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

這里與measureChild方法不同的只是加入了margin值而已覆劈,其他的都差不多保礼,就不多看了。這里調(diào)用了measure方法责语。View的measure方法不能重寫(xiě)炮障,并且View中會(huì)執(zhí)行View的onMeasure方法,所以在寫(xiě)自定義view時(shí)一定要重寫(xiě)onMeasure方法坤候。我們直接來(lái)看onMeasure方法的源碼:

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

這段代碼很短胁赢,但有好幾個(gè)方法,setMeasureDinmension方法白筹,getDefaultSize方法以及getSuggestedMinimumWidth方法智末。我們用一張圖更直觀的看看他的原理:

首先來(lái)看setMeasureDinmension方法谅摄,其實(shí)就是設(shè)置View的長(zhǎng)寬測(cè)量值,源碼如下:

 protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        //復(fù)雜的判斷
        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;
        }
        //調(diào)用方法將測(cè)量的長(zhǎng)寬設(shè)置為view的長(zhǎng)寬
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }

這里又調(diào)用了setMeasuredDimensionRaw方法:

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

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

顯然系馆,setMeasureDinmension方法并不是重點(diǎn)送漠,這個(gè)方法就是將測(cè)量的長(zhǎng)寬設(shè)置為view的長(zhǎng)寬,我們?cè)倏纯磄etDefaultSize方法是什么作用:


    /**
     * Utility to return a default size. Uses the supplied size if the
     * MeasureSpec imposed no constraints. Will get larger if allowed
     * by the MeasureSpec.
     * 實(shí)用程序返回默認(rèn)大小由蘑。使用所提供的大小闽寡,如果MeasureSpec沒(méi)有任何限制。如果允許的話(huà)會(huì)變大
     * 按比例計(jì)算尼酿。
     *
     * @param size Default size for this view
     * @param measureSpec Constraints imposed by the parent
     * @return The size this view should be.
     */
     
   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;
        }
        //返回一個(gè)specSize值爷狈,這個(gè)值就是view的測(cè)量大小,他真正的大小需要在Layout階段確定谓媒,但一般這兩個(gè)值相等
        return result;
    }

這里在MATCH_PARENT和WRAP_CONTANT模式下淆院,getDefaultSize返回的就是specSize的值何乎,而在UNSPECIFIED模式下句惯,寬高分別返回了getSuggestMinimumWidth和getSuggestMinimumHeight方法的返回值。我們看看這兩個(gè)方法的源碼:

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

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

以getSuggestedMinimumWidth方法為例支救,從代碼中可以看出抢野,如果View沒(méi)有設(shè)置背景,那么View的寬度為mMinWidth各墨,而這個(gè)mMinWidth值就是android:minwidth屬性所指的值指孤。如果這個(gè)屬性沒(méi)有指定值,那默認(rèn)值為0贬堵;如果View有背景恃轩,則View的寬度為個(gè)mMinWidth和mBackground.getMinimumWidth()的返回值的最大值。這里的mBackground.getMinimumWidth()是Drawable的getMinimumWidth方法里的黎做,我們看看源碼:

   public int getMinimumWidth() {
        final int intrinsicWidth = getIntrinsicWidth();
        return intrinsicWidth > 0 ? intrinsicWidth : 0;
    }

這段代碼返回的就是Drawable的原始高度叉跛,如果有原始高度就返回值,否則返回0蒸殿,比如BitmapDrawable就有原始高度筷厘,而ShapeDrawable就沒(méi)有。
最后總結(jié)一下getDefaultSize方法宏所,View的高/寬由specSize決定酥艳,而直接繼承View的自定義空白控件需要重寫(xiě)onMeasure方法并設(shè)置wrap_content時(shí)的自身大小,否則其wrap_content和match_parent是一樣的效果爬骤。至于原因充石,前面已經(jīng)講過(guò)了,就不在贅述了霞玄。我們這里提供了這個(gè)問(wèn)題的解決辦法:

private int mMinWidth = 250; // 指定默認(rèn)最小寬度
private int mMinHeight = 250; // 指定默認(rèn)最小高度

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
    if (widthSpecMode == MeasureSpec.AT_MOST
            && heightSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(mMinWidth, mMinHeight);
    } else if (widthSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(mMinWidth, heightSpecSize);
    } else if (heightSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(widthSpecSize, mMinHeight);
    }
}

上面這段代碼中骤铃,我們指定了一個(gè)默認(rèn)的寬高大信ň怠:mMinWidth、 mMinHeight劲厌,并在wrap_content模式下時(shí)設(shè)置此默認(rèn)大小值膛薛。而其他模式就跟原來(lái)一樣的。
到這里view的measure過(guò)程就簡(jiǎn)單介紹完了补鼻,在measure完成之后哄啄,我們可以通過(guò)getMeasuredWidth/Heigth方法獲取到View的測(cè)量寬/高,但在某些情況下风范,獲得的值并不準(zhǔn)確咨跌,所以建議在onLayout方法中去獲取View的最終寬/高。那么我們來(lái)看看Layout過(guò)程硼婿。

Layout過(guò)程

測(cè)量結(jié)束后锌半,視圖的大小就已經(jīng)測(cè)量好了,接下來(lái)就是 Layout 布局的過(guò)程寇漫。上文說(shuō)過(guò) ViewRoot 的 performTraversals 方法會(huì)在 measure 結(jié)束后刊殉,執(zhí)行 performLayout 方法,performLayout 方法則會(huì)調(diào)用 layout 方法開(kāi)始布局州胳,代碼如下

 private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        mLayoutRequested = false;
        mScrollMayChange = true;
        mInLayout = true;

        final View host = mView;
        if (host == null) {
            return;
        }
        if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
            Log.v(mTag, "Laying out " + host + " to (" +
                    host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
        }
 try {
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    //...省略代碼
    } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    mInLayout = false;

我們看看View 類(lèi)中 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;
        }
//記錄下View原始位置
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
//通過(guò)setFrame方法設(shè)置子元素的四個(gè)頂點(diǎn)的位置
//返回布爾值判斷View布局是否改變
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
//如果View位置改變记焊,調(diào)用onLayout方法
        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);
        }
    }

layout方法首先通過(guò)setFrame方法來(lái)設(shè)定View的四個(gè)頂點(diǎn)的位置,即初始化mLeft栓撞,mTop遍膜,mRight,mBottom這四個(gè)值瓤湘,View的四個(gè)頂點(diǎn)一確定瓢颅,那么它在父容器里的位置也就確定了,關(guān)鍵源碼如下:

mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);

接著又調(diào)用了onLayout方法弛说,這個(gè)方法確定了子元素的位置挽懦,onLayout 源碼如下:

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

看到這,是不是覺(jué)得不對(duì)剃浇,為什么是個(gè)空方法巾兆,沒(méi)錯(cuò),就是一個(gè)空方法虎囚,因?yàn)?onLayout 過(guò)程是為了確定視圖在布局中所在的位置角塑,而這個(gè)操作應(yīng)該是由布局來(lái)完成的,即父視圖決定子視圖的顯示位置淘讥,我們繼續(xù)看 ViewGroup 中的 onLayout 方法

@Override  
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);  

圃伶,ViewGroup 中的 onLayout 方法竟然是一個(gè)抽象方法,這就意味著所有 ViewGroup 的子類(lèi)都必須重寫(xiě)這個(gè)方法。像 LinearLayout窒朋、RelativeLayout 等布局搀罢,都是重寫(xiě)了這個(gè)方法,然后在內(nèi)部按照各自的規(guī)則對(duì)子視圖進(jìn)行布局的侥猩。接下來(lái)我們看看LinearLayout的onLayout方法榔至,源碼:

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mOrientation == VERTICAL) {
            layoutVertical(l, t, r, b);
        } else {
            layoutHorizontal(l, t, r, b);
        }
    }

這里分別有垂直和水平方向的兩個(gè)方法,我們選擇layoutVertical方法看看欺劳,主要源碼:

  void layoutVertical(int left, int top, int right, int bottom) {
        ···
        //childTop為View到Top的高度
        //循環(huán)遍歷子View
        for (int i = 0; i < count; i++) {
        //獲取指定View
            final View child = getVirtualChildAt(i);
            if (child == null) {
                childTop += measureNullChild(i);
            } else if (child.getVisibility() != GONE) {
            //如果View可見(jiàn)唧取,獲取子元素的測(cè)量寬高
            //在這里可以看出setChilFrame方法傳入的參數(shù)實(shí)際上就是子元素的測(cè)量寬高
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();
            //獲取子元素的LayoutParams參數(shù)
                final LinearLayout.LayoutParams lp =
                        (LinearLayout.LayoutParams) child.getLayoutParams();

              ···

                if (hasDividerBeforeChildAt(i)) {
                    childTop += mDividerHeight;
                }

                childTop += lp.topMargin;
                //設(shè)置子View位置
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);
                        //重新計(jì)算View到top的位置
                        //下一個(gè)子View的top位置就會(huì)相應(yīng)的增加
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

                i += getChildrenSkipCount(child, i);
            }
        }
    }

上面代碼主要完成了遍歷所有子元素并調(diào)用了setChildFrame方法來(lái)為子元素指定對(duì)應(yīng)的位置,其中childTop會(huì)逐漸增大划提,這樣后面的元素就會(huì)放在更靠下的位置枫弟,這也剛好符合垂直方向線(xiàn)性布局的特點(diǎn)。再看setChildFrame方法鹏往,代碼如下:

private void setChildFrame(View child, int left, int top, int width, int height) {
        child.layout(left, top, left + width, top + height);
    }

可以看到這個(gè)方法就是調(diào)用了子元素的layout方法淡诗,這樣子元素在確定了自己的位置后,又會(huì)調(diào)用onLayout方法繼續(xù)往下確定子元素的位置伊履。最后整個(gè)View樹(shù)的全部元素的位置就都確定了韩容。

Draw過(guò)程

相比前面兩個(gè)過(guò)程,Draw過(guò)程已經(jīng)簡(jiǎn)單了許多了湾碎,它主要有如下幾步:

  1. 繪制背景background宙攻。draw(canvas)
  2. 繪制自己(onDraw)
  3. 繪制children(dispatchDraw)
  4. 繪制裝飾(onDrawForeground)

前面說(shuō)過(guò)Draw過(guò)程通過(guò)performDraw方法發(fā)現(xiàn)它調(diào)用了draw方法,所以我們看看到底draw方法是如何實(shí)現(xiàn)的:

    public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

      

        // 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 繪制自己 調(diào)用onDraw方法
            //onDraw是一個(gè)空方法介褥,這是因?yàn)闆](méi)個(gè)視圖的內(nèi)容部分都不太相同
            //自定義View就必須重寫(xiě)這個(gè)方法來(lái)實(shí)現(xiàn)View的繪制
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children 分發(fā)繪制子元素
            //ViewGroup的dispatchDraw方法有具體的繪制邏輯
            dispatchDraw(canvas);

            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的繪制過(guò)程的傳遞是在dispatchDraw方法中實(shí)現(xiàn)的,它會(huì)遍歷所有子元素递惋,然后調(diào)用draw方法柔滔,這樣view的draw事件就一層一層的傳遞下去了∑妓洌看看ViewGroup中的dispatchDraw方法的代碼:

 @Override
    protected void dispatchDraw(Canvas canvas) {
       boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
        final int childrenCount = mChildrenCount;
        final View[] children = mChildren;
        int flags = mGroupFlags;
            ···
                  // draw reordering internally
        final ArrayList<View> preorderedList = usingRenderNodeProperties
                ? null : buildOrderedChildList();
        final boolean customOrder = preorderedList == null
                && isChildrenDrawingOrderEnabled();
                //對(duì)子元素進(jìn)行遍歷睛廊,同時(shí)調(diào)用了drawChild方法
        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);
                }
                transientIndex++;
                if (transientIndex >= transientCount) {
                    transientIndex = -1;
                }
            }
            ···
}

在看看drawChild方法的源碼:

   protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }

顯然,View的draw過(guò)程就要完成了杉编,這里又調(diào)用了draw方法實(shí)現(xiàn)了每個(gè)子元素的繪制超全。
也就是說(shuō),到了dispatchDraw方法這里邓馒,Draw過(guò)程就完成了嘶朱。View的繪制過(guò)程也就全部完成了。

站在巨人的肩膀上

本文參考了《Android開(kāi)發(fā)藝術(shù)探索》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末光酣,一起剝皮案震驚了整個(gè)濱河市疏遏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖财异,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件倘零,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡戳寸,警方通過(guò)查閱死者的電腦和手機(jī)呈驶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)疫鹊,“玉大人俐东,你說(shuō)我怎么就攤上這事《┥危” “怎么了虏辫?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)锈拨。 經(jīng)常有香客問(wèn)我砌庄,道長(zhǎng),這世上最難降的妖魔是什么奕枢? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任娄昆,我火速辦了婚禮,結(jié)果婚禮上缝彬,老公的妹妹穿的比我還像新娘萌焰。我一直安慰自己,他們只是感情好谷浅,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布扒俯。 她就那樣靜靜地躺著,像睡著了一般一疯。 火紅的嫁衣襯著肌膚如雪撼玄。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 50,050評(píng)論 1 291
  • 那天墩邀,我揣著相機(jī)與錄音掌猛,去河邊找鬼。 笑死眉睹,一個(gè)胖子當(dāng)著我的面吹牛荔茬,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播竹海,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼慕蔚,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了站削?” 一聲冷哼從身側(cè)響起坊萝,我...
    開(kāi)封第一講書(shū)人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后十偶,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體菩鲜,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年惦积,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了接校。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡狮崩,死狀恐怖蛛勉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情睦柴,我是刑警寧澤诽凌,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站坦敌,受9級(jí)特大地震影響侣诵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜狱窘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一杜顺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蘸炸,春花似錦躬络、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至仗嗦,卻和暖如春膘滨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背稀拐。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留丹弱,地道東北人德撬。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像躲胳,于是被迫代替她去往敵國(guó)和親蜓洪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容