安卓 View 的工作流程

View 的工作流程主要是指 measure、layout、draw 這三大流程,即測(cè)量、布局和繪制,其中 measure 確定 View 的測(cè)量寬/高,layout 確定 View 的最終寬/高和四個(gè)頂點(diǎn)的位置,而 draw 則將View繪制到屏幕上。

measure 的過(guò)程

measure過(guò)程要分情況來(lái)看,如果只是一個(gè)原始的View,那么通過(guò)measure方法就完成了其測(cè)量過(guò)程,如果是一個(gè)ViewGroup,除了完成自己的測(cè)量過(guò)程外,還會(huì)遍歷去調(diào)用所有子元素的measure方法,各個(gè)子元素再遞歸去執(zhí)行這個(gè)流程,下面針對(duì)這兩種情況分別討論。

  1. View的measure過(guò)程View的measure過(guò)程由其measure方法來(lái)完成,measure方法是一個(gè)final類(lèi)型的方法,這意味著子類(lèi)不能重寫(xiě)此方法,在View的measure方法中會(huì)去調(diào)用View的onMeasure方法,因此只需要看onMeasure的實(shí)現(xiàn)即可,View的onMeasure方法如下所示。
 /**
     * Measure the view and its content to determine the measured width and the
     * measured height. This method is invoked by {@link #measure(int, int)} and
     * should be overridden by subclasses to provide accurate and efficient
     * measurement of their contents.
     * </p>
     *
     * <p>
     * If this method is overridden, it is the subclass's responsibility to make
     * sure the measured height and width are at least the view's minimum height
     * and width ({@link #getSuggestedMinimumHeight()} and
     * {@link #getSuggestedMinimumWidth()}).
     * </p>
     *
     * @param widthMeasureSpec horizontal space requirements as imposed by the parent.
     *                         The requirements are encoded with
     *                         {@link android.view.View.MeasureSpec}.
     * @param heightMeasureSpec vertical space requirements as imposed by the parent.
     *                         The requirements are encoded with
     *                         {@link android.view.View.MeasureSpec}.
     *
     */
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

setMeasuredDimension方法會(huì)設(shè)置View寬/高的測(cè)量值,因此我們只需要看View.getDefaultSize這個(gè)靜態(tài)方法即可:

    /**
     * Utility to return a default size. Uses the supplied size if the
     * MeasureSpec imposed no constraints. Will get larger if allowed
     * by the MeasureSpec.
     *
     * @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;
        }
        return result;
    }

可以看出,getDefaultSize這個(gè)方法的邏輯很簡(jiǎn)單,對(duì)于我們來(lái)說(shuō),我們只需要看AT_MOST和.EXACTLY這兩種情況。簡(jiǎn)單地理解,其實(shí)getDefaultSize返回的大小就是measureSpec中的specSize,而這個(gè)specSize就是View測(cè)量后的大小,這里多次提到測(cè)量后的大小,是因?yàn)閂iew最終的大小是在layout階段確定的,所以這里必須要加以區(qū)分,但是幾乎所有情況下View的測(cè)量大小和最終大小是相等的。

至于UNSPECIFIED這種情況,一般用于系統(tǒng)內(nèi)部的測(cè)量過(guò)程,在這種情況下,View的大小為getDefaultSize的第一個(gè)參數(shù)size,即寬/高分別為getSuggestedMinimumWidth和getSuggestedMinimumHeight這兩個(gè)方法的返回值,看一下它們的源碼:

    /**
     * Returns the suggested minimum width that the view should use. This
     * returns the maximum of the view's minimum width
     * and the background's minimum width
     *  ({@link android.graphics.drawable.Drawable#getMinimumWidth()}).
     * <p>
     * When being used in {@link #onMeasure(int, int)}, the caller should still
     * ensure the returned width is within the requirements of the parent.
     *
     * @return The suggested minimum width of the view.
     */
    protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }

這里只分析getSuggestedMinimumWidth方法的實(shí)現(xiàn),getSuggestedMinimumHeight和它的實(shí)現(xiàn)原理是一樣的。從getSuggestedMinimumWidth的代碼可以看出,如果View沒(méi)有設(shè)置背景,那么View的寬度為mMinWidth,而mMinWidth對(duì)應(yīng)于android:minWidth這個(gè)屬性所指定的值,因此View的寬度即為android:minWidth屬性所指定的值。這個(gè)屬性如果不指定,那么mMinWidth則默認(rèn)為0;如果View指定了背景,則View的寬度為max(mMinWidth,mBackground.getMinimumWidth())。mMinWidth的含義我們已經(jīng)知道了,那么mBackground.getMinimumWidth()是什么呢?我們看一下Drawable的getMinimumWidth方法,如下所示。

public abstract class Drawable {

    ...

    /**
     * Returns the minimum width suggested by this Drawable. If a View uses this
     * Drawable as a background, it is suggested that the View use at least this
     * value for its width. (There will be some scenarios where this will not be
     * possible.) This value should INCLUDE any padding.
     *
     * @return The minimum width suggested by this Drawable. If this Drawable
     *         doesn't have a suggested minimum width, 0 is returned.
     */
    public int getMinimumWidth() {
        final int intrinsicWidth = getIntrinsicWidth();
        return intrinsicWidth > 0 ? intrinsicWidth : 0;
    }
    
    ...
    
}
    

可以看出,getMinimumWidth返回的就是Drawable的原始寬度,前提是這個(gè)Drawable有原始寬度,否則就返回0。那么Drawable在什么情況下有原始寬度呢?這里先舉個(gè)例子說(shuō)明一下,ShapeDrawable無(wú)原始寬/高,而B(niǎo)itmapDrawable有原始寬/高(圖片的尺寸),詳細(xì)內(nèi)容會(huì)在第6章進(jìn)行介紹。

這里再總結(jié)一下getSuggestedMinimumWidth的邏輯:如果View沒(méi)有設(shè)置背景,那么返回android:minWidth這個(gè)屬性所指定的值,這個(gè)值可以為0;如果View設(shè)置了背景,則返回android:minWidth和背景的最小寬度這兩者中的最大值,getSuggestedMinimumWidth和getSuggestedMinimumHeight的返回值就是View在UNSPECIFIED情況下的測(cè)量寬/高。

從getDefaultSize方法的實(shí)現(xiàn)來(lái)看,View的寬/高由specSize決定,所以我們可以得出如下結(jié)論:直接繼承View的自定義控件需要重寫(xiě)onMeasure方法并設(shè)置wrap_content時(shí)的自身大小,否則在布局中使用wrap_content就相當(dāng)于使用match_parent。為什么呢?這個(gè)原因需要結(jié)合上述代碼和表1才能更好地理解。從上述代碼中我們知道,如果View在布局中使用wrap_content,那么它的specMode是AT_MOST模式,在這種模式下,它的寬/高等于specSize;查表4-1可知,這種情況下View的specSize是parentSize,而parentSize是父容器中目前可以使用的大小,也就是父容器當(dāng)前剩余的空間大小。很顯然,View的寬/高就等于父容器當(dāng)前剩余的空間大小,這種效果和在布局中使用match_parent完全一致。如何解決這個(gè)問(wèn)題呢?也很簡(jiǎn)單,代碼如下所示。

表1 普通View的MeasureSpec的創(chuàng)建規(guī)則
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(mWidth,mHeight);                    
      } else if (widthSpecMode == AT_MOST) {
            setMeasureDimension(mWidth,heightSpecSize);
      } else if (heightSpecMode == AT_MOST){
            setMeasureDimension(widthSpecSize,mHeight);
      }
} 

在上面的代碼中,我們只需要給View指定一個(gè)默認(rèn)的內(nèi)部寬/高(mWidth和mHeight),并在wrap_content時(shí)設(shè)置此寬/高即可。對(duì)于非wrap_content情形,我們沿用系統(tǒng)的測(cè)量值即可,至于這個(gè)默認(rèn)的內(nèi)部寬/高的大小如何指定,這個(gè)沒(méi)有固定的依據(jù),根據(jù)需要靈活指定即可。如果查看TextView、ImageView等的源碼就可以知道,針對(duì)wrap_content情形,它們的onMeasure方法均做了特殊處理,讀者可以自行查看它們的源碼。

2. ViewGroup的measure過(guò)程

對(duì)于ViewGroup來(lái)說(shuō),除了完成自己的measure過(guò)程以外,還會(huì)遍歷去調(diào)用所有子元素的measure方法,各個(gè)子元素再遞歸去執(zhí)行這個(gè)過(guò)程。和View不同的是,ViewGroup是一個(gè)抽象類(lèi),因此它沒(méi)有重寫(xiě)View的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;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

從上述代碼來(lái)看,ViewGroup在measure時(shí),會(huì)對(duì)每一個(gè)子元素進(jìn)行measure,ViewGroup.measureChild這個(gè)方法的實(shí)現(xiàn)也很好理解,如下所示。

    /**
     * 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();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

很顯然,measureChild的思想就是取出子元素的LayoutParams,然后再通過(guò)getChildMeasureSpec來(lái)創(chuàng)建子元素的MeasureSpec,接著將MeasureSpec直接傳遞給View的measure方法來(lái)進(jìn)行測(cè)量。getChildMeasureSpec的工作過(guò)程已經(jīng)在上面進(jìn)行了詳細(xì)分析,通過(guò)表1可以更清楚地了解它的邏輯。我們知道,ViewGroup并沒(méi)有定義其測(cè)量的具體過(guò)程,這是因?yàn)閂iewGroup是一個(gè)抽象類(lèi),其測(cè)量過(guò)程的onMeasure方法需要各個(gè)子類(lèi)去具體實(shí)現(xiàn),比如LinearLayout、RelativeLayout等,為什么ViewGroup不像View一樣對(duì)其onMeasure方法做統(tǒng)一的實(shí)現(xiàn)呢?那是因?yàn)椴煌腣iewGroup子類(lèi)有不同的布局特性,這導(dǎo)致它們的測(cè)量細(xì)節(jié)各不相同,比如LinearLayout和RelativeLayout這兩者的布局特性顯然不同,因此ViewGroup無(wú)法做統(tǒng)一實(shí)現(xiàn)。下面就通過(guò)LinearLayout的onMeasure方法來(lái)分析ViewGroup的measure過(guò)程,其他Layout類(lèi)型讀者可以自行分析。

首先來(lái)看LinearLayout的onMeasure方法,如下所示。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }

上述代碼很簡(jiǎn)單,我們選擇一個(gè)來(lái)看一下,比如選擇查看豎直布局的LinearLayout的測(cè)量過(guò)程,即measureVertical方法,measureVertical的源碼比較長(zhǎng),下面只描述其大概邏輯,首先看一段代碼:

從上面這段代碼可以看出,系統(tǒng)會(huì)遍歷子元素并對(duì)每個(gè)子元素執(zhí)行measureChildBeforeLayout方法,這個(gè)方法內(nèi)部會(huì)調(diào)用子元素的measure方法,這樣各個(gè)子元素就開(kāi)始依次進(jìn)入measure過(guò)程,并且系統(tǒng)會(huì)通過(guò)mTotalLength這個(gè)變量來(lái)存儲(chǔ)LinearLayout在豎直方向的初步高度。每測(cè)量一個(gè)子元素,mTotalLength就會(huì)增加,增加的部分主要包括了子元素的高度以及子元素在豎直方向上的margin等。當(dāng)子元素測(cè)量完畢后,LinearLayout會(huì)測(cè)量自己的大小,源碼如下所示。

        // Add in our padding
        mTotalLength += mPaddingTop + mPaddingBottom;

        int heightSize = mTotalLength;

        // Check against our minimum height
        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
        
        // Reconcile our calculated size with the heightMeasureSpec
        int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
        heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
        
        ...
        
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
        heightSizeAndState);

這里對(duì)上述代碼進(jìn)行說(shuō)明,當(dāng)子元素測(cè)量完畢后,LinearLayout會(huì)根據(jù)子元素的情況來(lái)測(cè)量自己的大小。針對(duì)豎直的LinearLayout而言,它在水平方向的測(cè)量過(guò)程遵循View的測(cè)量過(guò)程,在豎直方向的測(cè)量過(guò)程則和View有所不同。具體來(lái)說(shuō)是指,如果它的布局中高度采用的是match_parent或者具體數(shù)值,那么它的測(cè)量過(guò)程和View一致,即高度為specSize;如果它的布局中高度采用的是wrap_content,那么它的高度是所有子元素所占用的高度總和,但是仍然不能超過(guò)它的父容器的剩余空間,當(dāng)然它的最終高度還需要考慮其在豎直方向的padding,這個(gè)過(guò)程可以進(jìn)一步參看如下源碼:

    /**
     * Utility to reconcile a desired size and state, with constraints imposed
     * by a MeasureSpec. Will take the desired size, unless a different size
     * is imposed by the constraints. The returned value is a compound integer,
     * with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and
     * optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the
     * resulting size is smaller than the size the view wants to be.
     *
     * @param size How big the view wants to be.
     * @param measureSpec Constraints imposed by the parent.
     * @param childMeasuredState Size information bit mask for the view's
     *                           children.
     * @return Size information bit mask as defined by
     *         {@link #MEASURED_SIZE_MASK} and
     *         {@link #MEASURED_STATE_TOO_SMALL}.
     */
    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 = 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);
    }

View的measure過(guò)程是三大流程中最復(fù)雜的一個(gè),measure完成以后,通過(guò)getMeasuredWidth/Height方法就可以正確地獲取到View的測(cè)量寬/高。需要注意的是,在某些極端情況下,系統(tǒng)可能需要多次measure才能確定最終的測(cè)量寬/高,在這種情形下,在onMeasure方法中拿到的測(cè)量寬/高很可能是不準(zhǔn)確的。一個(gè)比較好的習(xí)慣是在onLayout方法中去獲取View的測(cè)量寬/高或者最終寬/高

上面已經(jīng)對(duì)View的measure過(guò)程進(jìn)行了詳細(xì)的分析,現(xiàn)在考慮一種情況,比如我們想在Activity已啟動(dòng)的時(shí)候就做一件任務(wù),但是這一件任務(wù)需要獲取某個(gè)View的寬/高。讀者可能會(huì)說(shuō),這很簡(jiǎn)單啊,在onCreate或者onResume里面去獲取這個(gè)View的寬/高不就行了?讀者可以自行試一下,實(shí)際上在onCreate、onStart、onResume中均無(wú)法正確得到某個(gè)View的寬/高信息,這是因?yàn)閂iew的measure過(guò)程和Activity的生命周期方法不是同步執(zhí)行的,因此無(wú)法保證Activity執(zhí)行了onCreate、onStart、onResume時(shí)某個(gè)View已經(jīng)測(cè)量完畢了,如果View還沒(méi)有測(cè)量完畢,那么獲得的寬/高就是0。有沒(méi)有什么方法能解決這個(gè)問(wèn)題呢?答案是有的,這里給出四種方法來(lái)解決這個(gè)問(wèn)題:

  1. Activity/View#onWindowFocusChangedonWindowFocusChanged這個(gè)方法的含義是:View已經(jīng)初始化完畢了,寬/高已經(jīng)準(zhǔn)備好了,這個(gè)時(shí)候去獲取寬/高是沒(méi)問(wèn)題的。需要注意的是,onWindowFocusChanged會(huì)被調(diào)用多次,當(dāng)Activity的窗口得到焦點(diǎn)和失去焦點(diǎn)時(shí)均會(huì)被調(diào)用一次。具體來(lái)說(shuō),當(dāng)Activity繼續(xù)執(zhí)行和暫停執(zhí)行時(shí),onWindowFocusChanged均會(huì)被調(diào)用,如果頻繁地進(jìn)行onResumeonPause,那么onWindowFocusChanged也會(huì)被頻繁地調(diào)用。典型代碼如下:
    public void onWindowFocusChanged(boolean hasFocus){
        super.onWindowFocusChanged(hasFocus);
        if(hasFocus){
            int width = view.getMeasureWidth();
            int height = view.getMeasureHeight();
        }
    }
  1. view.post(runnable)
    通過(guò)post可以將一個(gè)runnable投遞到消息隊(duì)列的尾部,然后等待Looper調(diào)用此runnable的時(shí)候,View也已經(jīng)初始化好了。典型代碼如下:
    protected void onStart(){
        super.onStart();
        view.post(new Runnable(){
            @override
            public void run(){
                int width = view.getMeasuredWidth();
                int height = view.getMeasuredHeight();
            }
        });
    }
  1. ViewTreeObserver。使用 ViewTreeObserver 的眾多回調(diào)可以完成這個(gè)功能,比如使用OnGlobalLayoutListener這個(gè)接口,當(dāng)View樹(shù)的狀態(tài)發(fā)生改變或者View樹(shù)內(nèi)部的View的可見(jiàn)性發(fā)現(xiàn)改變時(shí),onGlobalLayout 方法將被回調(diào),因此這是獲取View的寬/高一個(gè)很好的時(shí)機(jī)。需要注意的是,伴隨著View樹(shù)的狀態(tài)改變等,onGlobalLayout會(huì)被調(diào)用多次。典型代碼如下:
    protected void onStart(){
        super.onStart();

        ViewTreeObserver observer = view.getViewTreeObserver();
        observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener(){
            @SuppressWarnings("deprecation");
            @override
            public void onGlobalLayout(){
                view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                int width = view.getMeasuredWidth();
                int height = view.getMeasuredHeight();
            }
        });
    }
  1. view.measure(int widthMeasureSpec,int heightMea-sureSpec)。通過(guò)手動(dòng)對(duì)View進(jìn)行measure來(lái)得到View的寬/高。這種方法比較復(fù)雜,這里要分情況處理,根據(jù)View的LayoutParams來(lái)分:
  • match_parent直接放棄,無(wú)法measure出具體的寬/高。原因很簡(jiǎn)單,根據(jù)View的measure過(guò)程,如表1所示,構(gòu)造此種MeasureSpec需要知道parentSize,即父容器的剩余空間,而這個(gè)時(shí)候我們無(wú)法知道parentSize的大小,所以理論上不可能測(cè)量出View的大小。
  • 具體的數(shù)值(dp/px)比如寬/高都是100px,如下measure:
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec, heightMeasureSpec);
  • wrap_content如下measure:
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
v_view1.measure(widthMeasureSpec, heightMeasureSpec);

注意到(1 << 30)-1,通過(guò)分析MeasureSpec的實(shí)現(xiàn)可以知道,View的尺寸使用30位二進(jìn)制表示,也就是說(shuō)最大是30個(gè)1(即2^30 – 1),也就是(1 << 30) – 1,在最大化模式下,我們用View理論上能支持的最大值去構(gòu)造MeasureSpec是合理的。

關(guān)于View的measure,網(wǎng)絡(luò)上有兩個(gè)錯(cuò)誤的用法。為什么說(shuō)是錯(cuò)誤的,首先其違背了系統(tǒng)的內(nèi)部實(shí)現(xiàn)規(guī)范(因?yàn)闊o(wú)法通過(guò)錯(cuò)誤的MeasureSpec去得出合法的SpecMode,從而導(dǎo)致measure過(guò)程出錯(cuò)),其次不能保證一定能measure出正確的結(jié)果。

  • 第一種錯(cuò)誤用法:
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(-1, MeasureSpec.UNSPECIFIED);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(-1, MeasureSpec.UNSPECIFIED);
view.measure(widthMeasureSpec, heightMeasureSpec);
  • 第二種錯(cuò)誤用法
view.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)      

layout 的過(guò)程

Layout 的作用是ViewGroup用來(lái)確定子元素的位置,當(dāng)ViewGroup的位置被確定后,它在onLayout中會(huì)遍歷所有的子元素并調(diào)用其layout方法,在layout方法中onLayout方法又會(huì)被調(diào)用。Layout過(guò)程和measure過(guò)程相比就簡(jiǎn)單多了,layout方法確定View本身的位置,而onLayout方法則會(huì)確定所有子元素的位置,先看View的layout方法。

draw 的過(guò)程

Draw過(guò)程就比較簡(jiǎn)單了,它的作用是將View繪制到屏幕上面。View的繪制過(guò)程遵循如下幾步:

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

這一點(diǎn)通過(guò)draw方法的源碼可以明顯看出來(lái),如下所示。

參考書(shū)目

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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