Android View的繪制流程

前言

上一篇中我們講到了Android的觸摸事件傳遞機(jī)制梦鉴,除此之外肥橙,關(guān)于Android View的繪制流程這一塊也是View相關(guān)的核心知識(shí)點(diǎn)存筏。我們都知道,PhoneWindowAndroid系統(tǒng)中最基本的窗口系統(tǒng)搏色,每個(gè)Activity會(huì)創(chuàng)建一個(gè)继榆。同時(shí)略吨,PhoneWindow也是ActivityView系統(tǒng)交互的接口翠忠。DecorView本質(zhì)上是一個(gè)FrameLayout秽之,是Activity中所有View的祖先考榨。

一河质、開(kāi)始:DecorView被加載到Window

ActivitystartActivity開(kāi)始掀鹅,最終調(diào)用到ActivityThreadhandleLaunchActivity方法來(lái)創(chuàng)建Activity乐尊,相關(guān)核心代碼如下:

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {

    ....
    // 創(chuàng)建Activity扔嵌,會(huì)調(diào)用Activity的onCreate方法
    // 從而完成DecorView的創(chuàng)建
    Activity a = performLaunchActivity(r, customIntent);
    if (a != null) {
        r.createdConfig = new Configuration(mConfiguration);
        Bundle oldState = r.state;
        handleResumeActivity(r.tolen, false, r.isForward, !r.activity..mFinished && !r.startsNotResumed);
    }
}

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) {
    unscheduleGcIdler();
    mSomeActivitiesChanged = true;
    // 調(diào)用Activity的onResume方法
    ActivityClientRecord r = performResumeActivity(token, clearHide);
    if (r != null) {
        final Activity a = r.activity;
        ...
        if (r.window == null &&& !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            // 得到DecorView
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            // 得到了WindowManager对人,WindowManager是一個(gè)接口
            // 并且繼承了接口ViewManager
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (a.mVisibleFromClient) {
                a.mWindowAdded = true;
                // WindowManager的實(shí)現(xiàn)類是WindowManagerImpl姻几,
                // 所以實(shí)際調(diào)用的是WindowManagerImpl的addView方法
                wm.addView(decor, l);
            }
        }
    }
}

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    ...

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }
    ...
}

在了解View繪制的整體流程之前,我們必須先了解下ViewRootDecorView的概念咱台。ViewRoot對(duì)應(yīng)于ViewRootImpl類回溺,它是連接WindowManagerDecorView的紐帶遗遵,View的三大流程均是通過(guò)ViewRoot來(lái)完成的车要。在ActivityThread中翼岁,當(dāng)Activity對(duì)象被創(chuàng)建完畢后琅坡,會(huì)將DecorView添加到Window中榆俺,同時(shí)會(huì)創(chuàng)建ViewRootImpl對(duì)象谴仙,并將ViewRootImpl對(duì)象和DecorView建立關(guān)聯(lián)晃跺,相關(guān)源碼如下所示:

// WindowManagerGlobal的addView方法
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
    ...
    ViewRootImpl root;
    View pannelParentView = null;
    synchronized (mLock) {
        ...
        // 創(chuàng)建ViewRootImpl實(shí)例
        root = new ViewRootImpl(view..getContext(), display);
        view.setLayoutParams(wparams);
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
    }
    try {
        // 把DecorView加載到Window中
        root.setView(view, wparams, panelParentView);
    } catch (RuntimeException e) {
        synchronized (mLock) {
            final int index = findViewLocked(view, false);
            if (index >= 0) {
                removeViewLocked(index, true);
            }
        }
        throw e;
    }
}

二、了解繪制的整體流程

繪制會(huì)從根視圖ViewRootperformTraversals()方法開(kāi)始烹玉,從上到下遍歷整個(gè)視圖樹(shù)二打,每個(gè)View控件負(fù)責(zé)繪制自己继效,而ViewGroup還需要負(fù)責(zé)通知自己的子View進(jìn)行繪制操作瑞信。performTraversals()的核心代碼如下凡简。

private void performTraversals() {
    ...
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    ...
    //執(zhí)行測(cè)量流程
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
    //執(zhí)行布局流程
    performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    ...
    //執(zhí)行繪制流程
    performDraw();
}

performTraversals的大致工作流程圖如下所示:

image

注意:

  • preformLayoutperformDraw的傳遞流程和performMeasure是類似的帜乞,唯一不同的是挖函,performDraw的傳遞過(guò)程是在draw方法中通過(guò)dispatchDraw來(lái)實(shí)現(xiàn)的怨喘,不過(guò)這并沒(méi)有本質(zhì)區(qū)別必怜。
  • 獲取content
ViewGroup content = (ViewGroup)findViewById(android.R.id.content);

  • 獲取設(shè)置的View
content.getChildAt(0);

三梳庆、理解MeasureSpec

1.MeasureSpec源碼解析

MeasureSpec表示的是一個(gè)32位的整形值膏执,它的高2位表示測(cè)量模式SpecMode更米,低30位表示某種測(cè)量模式下的規(guī)格大小SpecSize征峦。MeasureSpecView類的一個(gè)靜態(tài)內(nèi)部類栏笆,用來(lái)說(shuō)明應(yīng)該如何測(cè)量這個(gè)View蛉加。MeasureSpec的核心代碼如下针饥。

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

    // 不指定測(cè)量模式, 父視圖沒(méi)有限制子視圖的大小打厘,子視圖可以是想要
    // 的任何尺寸户盯,通常用于系統(tǒng)內(nèi)部,應(yīng)用開(kāi)發(fā)中很少用到硫眨。
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;

    // 精確測(cè)量模式礁阁,視圖寬高指定為match_parent或具體數(shù)值時(shí)生效姥闭,
    // 表示父視圖已經(jīng)決定了子視圖的精確大小棚品,這種模式下View的測(cè)量
    // 值就是SpecSize的值铜跑。
    public static final int EXACTLY = 1 << MODE_SHIFT;

    // 最大值測(cè)量模式锅纺,當(dāng)視圖的寬高指定為wrap_content時(shí)生效伞广,此時(shí)
    // 子視圖的尺寸可以是不超過(guò)父視圖允許的最大尺寸的任何尺寸嚼锄。
    public static final int AT_MOST = 2 << MODE_SHIFT;

    // 根據(jù)指定的大小和模式創(chuàng)建一個(gè)MeasureSpec
    public static int makeMeasureSpec(int size, int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }

    // 微調(diào)某個(gè)MeasureSpec的大小
    static int adjust(int measureSpec, int delta) {
        final int mode = getMode(measureSpec);
        if (mode == UNSPECIFIED) {
            // No need to adjust size for UNSPECIFIED mode.
            return make MeasureSpec(0, UNSPECIFIED);
        }
        int size = getSize(measureSpec) + delta;
        if (size < 0) {
            size = 0;
        }
        return makeMeasureSpec(size, mode);
    }
}

MeasureSpec通過(guò)將SpecModeSpecSize打包成一個(gè)int值來(lái)避免過(guò)多的對(duì)象內(nèi)存分配,為了方便操作沧侥,其提供了打包和解包的方法宴杀,打包方法為上述源碼中的makeMeasureSpec旺罢,解包方法源碼如下:

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

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

2.DecorViewMeasureSpec的創(chuàng)建過(guò)程:
//desiredWindowWidth和desiredWindowHeight是屏幕的尺寸
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

private static int getRootMeaureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {
        case ViewGroup.LayoutParams.MATRCH_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;
}

3.子元素的MeasureSpec的創(chuàng)建過(guò)程
// ViewGroup的measureChildWithMargins方法
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

    // 子元素的MeasureSpec的創(chuàng)建與父容器的MeasureSpec和子元素本身
    // 的LayoutParams有關(guān),此外還和View的margin及padding有關(guān)
    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);
}

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

    // padding是指父容器中已占用的空間大小炉旷,因此子元素可用的
    // 大小為父容器的尺寸減去padding
    int size = Math.max(0, specSize - padding);

    int resultSize = 0;
    int resultMode = 0;

    switch (sepcMode) {
        // 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 (childDimesion == 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 = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size....
                // find out how big it should be
                resultSize = 0;
                resultMode == MeasureSpec.UNSPECIFIED;
            }
            break;
        }
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

普通ViewMeasureSpec的創(chuàng)建規(guī)則如下:

image

注意:UNSPECIFIED模式主要用于系統(tǒng)內(nèi)部多次Measure的情形抽高,一般不需關(guān)注翘骂。

結(jié)論:對(duì)于DecorView而言碳竟,它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同決定莹桅;對(duì)于普通的View诈泼,它的MeasureSpec由父視圖的MeasureSpec和其自身的LayoutParams共同決定铐达。

四瓮孙、View繪制流程之Measure

1.Measure的基本流程

由前面的分析可知杭抠,頁(yè)面的測(cè)量流程是從performMeasure方法開(kāi)始的偏灿,相關(guān)的核心代碼流程如下忿墅。

private void perormMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    ...
    // 具體的測(cè)量操作分發(fā)給ViewGroup
    mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
}

// 在ViewGroup中的measureChildren()方法中遍歷測(cè)量ViewGroup中所有的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];
        // 當(dāng)View的可見(jiàn)性處于GONE狀態(tài)時(shí),不對(duì)其進(jìn)行測(cè)量
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}

// 測(cè)量某個(gè)指定的View
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();

    // 根據(jù)父容器的MeasureSpec和子View的LayoutParams等信息計(jì)算
    // 子View的MeasureSpec
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height);
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

// View的measure方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    // ViewGroup沒(méi)有定義測(cè)量的具體過(guò)程,因?yàn)閂iewGroup是一個(gè)
    // 抽象類疟游,其測(cè)量過(guò)程的onMeasure方法需要各個(gè)子類去實(shí)現(xiàn)
    onMeasure(widthMeasureSpec, heightMeasureSpec);
    ...
}

// 不同的ViewGroup子類有不同的布局特性颁虐,這導(dǎo)致它們的測(cè)量細(xì)節(jié)各不相同另绩,如果需要自定義測(cè)量過(guò)程笋籽,則子類可以重寫(xiě)這個(gè)方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // setMeasureDimension方法用于設(shè)置View的測(cè)量寬高
    setMeasureDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), 
    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

// 如果View沒(méi)有重寫(xiě)onMeasure方法,則會(huì)默認(rèn)調(diào)用getDefaultSize來(lái)獲得View的寬高
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 = sepcSize;
            break;
    }
    return result;
}

2.對(duì)getSuggestMinimumWidth的分析
protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinmumWidth());
}

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

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

如果View沒(méi)有設(shè)置背景,那么返回android:minWidth這個(gè)屬性所指定的值州叠,這個(gè)值可以為0留量;如果View設(shè)置了背景楼熄,則返回android:minWidth和背景的最小寬度這兩者中的最大值可岂。

3.自定義View時(shí)手動(dòng)處理wrap_content時(shí)的情形

直接繼承View的控件需要重寫(xiě)onMeasure方法并設(shè)置wrap_content時(shí)的自身大小缕粹,否則在布局中使用wrap_content就相當(dāng)于使用match_parent平斩。解決方式如下:

protected void onMeasure(int widthMeasureSpec, 
int height MeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int widtuhSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    // 在wrap_content的情況下指定內(nèi)部寬/高(mWidth和mHeight)
    int heightSpecSize = MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(mWidth, mHeight);
    } else if (widthSpecMode == MeasureSpec.AT_MOST) {
        setMeasureDimension(mWidth, heightSpecSize);
    } else if (heightSpecMode == MeasureSpec.AT_MOST) {
        setMeasureDimension(widthSpecSize, mHeight);
    }
}

4.LinearLayoutonMeasure方法實(shí)現(xiàn)解析
protected void onMeasure(int widthMeasureSpec, int hegithMeasureSpec) {
    if (mOrientation == VERTICAL) {
        measureVertical(widthMeasureSpec, heightMeasureSpec);
    } else {
        measureHorizontal(widthMeasureSpec, heightMeasureSpec);
    }
}

// measureVertical核心源碼
// See how tall everyone is. Also remember max width.
for (int i = 0; i < count; ++i) {
    final View child = getVirtualChildAt(i);
    ...
    // Determine how big this child would like to be. If this or 
    // previous children have given a weight, then we allow it to 
    // use all available space (and we will shrink things later 
    // if need)
    measureChildBeforeLayout(
            child, i, widthMeasureSpec, 0, heightMeasureSpec,
            totalWeight == 0 ? mTotalLength : 0);

    if (oldHeight != Integer.MIN_VALUE) {
        lp.height = oldHeight;
    }

    final int childHeight = child.getMeasuredHeight();
    final int totalLength = mTotalLength;
    mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + 
    lp.bottomMargin + getNextLocationOffset(child));
}

系統(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等院崇。

// LinearLayout測(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(resolveSizeAndSize(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);

public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
    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:
            // 高度不能超過(guò)父容器的剩余空間
            if (specSize < size) {
                result = specSize | MEASURED_STATE_TOO_SMALL;
            } else {
                result = size;
            }
            break;
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
    }
    return result | (childMeasuredState & MEASURED_STATE_MASK);
}

5.在Activity中獲取某個(gè)View的寬高

由于Viewmeasure過(guò)程和Activity的生命周期方法不是同步執(zhí)行的亚脆,如果View還沒(méi)有測(cè)量完畢做院,那么獲得的寬/高就是0濒持。所以在onCreate键耕、onStartonResume中均無(wú)法正確得到某個(gè)View的寬高信息柑营。解決方式如下:

  • Activity/ViewonWindowFocusChanged
// 此時(shí)View已經(jīng)初始化完畢
// 當(dāng)Activity的窗口得到焦點(diǎn)和失去焦點(diǎn)時(shí)均會(huì)被調(diào)用一次
// 如果頻繁地進(jìn)行onResume和onPause屈雄,那么onWindowFocusChanged也會(huì)被頻繁地調(diào)用
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    if (hasFocus) {
        int width = view.getMeasureWidth();
        int height = view.getMeasuredHeight();
    }
}

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

  • ViewTreeObserver
// 當(dāng)View樹(shù)的狀態(tài)發(fā)生改變或者View樹(shù)內(nèi)部的View的可見(jiàn)// 性發(fā)生改變時(shí)酒奶,onGlobalLayout方法將被回調(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();
        }
    });
}

  • View.measure(int widthMeasureSpec, int heightMeasureSpec)

五携御、View的繪制流程之Layout

1.Layout的基本流程
// ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
    ...
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    ...
}

// View.java
public void layout(int l, int t, int r, int b) {
    ...
    // 通過(guò)setFrame方法來(lái)設(shè)定View的四個(gè)頂點(diǎn)的位置贮匕,即View在父容器中的位置
    boolean changed = isLayoutModeOptical(mParent) ? 
    set OpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

    ...
    onLayout(changed, l, t, r, b);
    ...
}

// 空方法,子類如果是ViewGroup類型,則重寫(xiě)這個(gè)方法捷雕,實(shí)現(xiàn)ViewGroup
// 中所有View控件布局流程
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

}

2.LinearLayoutonLayout方法實(shí)現(xiàn)解析
protected void onlayout(boolean changed, int l, int t, int r, int b) {
    if (mOrientation == VERTICAL) {
        layoutVertical(l, t, r, b);
    } else {
        layoutHorizontal(l,)
    }
}

// layoutVertical核心源碼
void layoutVertical(int left, int top, int right, int bottom) {
    ...
    final int count = getVirtualChildCount();
    for (int i = 0; i < count; i++) {
        final View child = getVirtualChildAt(i);
        if (child == null) {
            childTop += measureNullChild(i);
        } else if (child.getVisibility() != GONE) {
            final int childWidth = child.getMeasureWidth();
            final int childHeight = child.getMeasuredHeight();

            final LinearLayout.LayoutParams lp = 
                    (LinearLayout.LayoutParams) child.getLayoutParams();
            ...
            if (hasDividerBeforeChildAt(i)) {
                childTop += mDividerHeight;
            }

            childTop += lp.topMargin;
            // 為子元素確定對(duì)應(yīng)的位置
            setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight);
            // childTop會(huì)逐漸增大际跪,意味著后面的子元素會(huì)被
            // 放置在靠下的位置
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

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

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

注意:在View的默認(rèn)實(shí)現(xiàn)中宪睹,View的測(cè)量寬/高和最終寬/高是相等的团搞,只不過(guò)測(cè)量寬/高形成于Viewmeasure過(guò)程,而最終寬/高形成于Viewlayout過(guò)程摆尝,即兩者的賦值時(shí)機(jī)不同温艇,測(cè)量寬/高的賦值時(shí)機(jī)稍微早一些。在一些特殊的情況下則兩者不相等:

  • 重寫(xiě)Viewlayout方法,使最終寬度總是比測(cè)量寬/高大100px
public void layout(int l, int t, int r, int b) {
    super.layout(l, t, r + 100, b + 100);
}

  • View需要多長(zhǎng)measure才能確定自己的測(cè)量寬/高,在前幾次測(cè)量的過(guò)程中堕汞,其得出的測(cè)量寬/高有可能和最終寬/高不一致勺爱,但最終來(lái)說(shuō),測(cè)量寬/高還是和最終寬/高相同

六讯检、View的繪制流程之Draw

1.Draw的基本流程
private void performDraw() {
    ...
    draw(fullRefrawNeeded);
    ...
}

private void draw(boolean fullRedrawNeeded) {
    ...
    if (!drawSoftware(surface, mAttachInfo, xOffest, yOffset, 
    scalingRequired, dirty)) {
        return;
    }
    ...
}

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, 
int xoff, int yoff, boolean scallingRequired, Rect dirty) {
    ...
    mView.draw(canvas);
    ...
}

// 繪制基本上可以分為六個(gè)步驟
public void draw(Canvas canvas) {
    ...
    // 步驟一:繪制View的背景
    drawBackground(canvas);

    ...
    // 步驟二:如果需要的話邻寿,保持canvas的圖層,為fading做準(zhǔn)備
    saveCount = canvas.getSaveCount();
    ...
    canvas.saveLayer(left, top, right, top + length, null, flags);

    ...
    // 步驟三:繪制View的內(nèi)容
    onDraw(canvas);

    ...
    // 步驟四:繪制View的子View
    dispatchDraw(canvas);

    ...
    // 步驟五:如果需要的話视哑,繪制View的fading邊緣并恢復(fù)圖層
    canvas.drawRect(left, top, right, top + length, p);
    ...
    canvas.restoreToCount(saveCount);

    ...
    // 步驟六:繪制View的裝飾(例如滾動(dòng)條等等)
    onDrawForeground(canvas)
}

2.setWillNotDraw的作用
// 如果一個(gè)View不需要繪制任何內(nèi)容,那么設(shè)置這個(gè)標(biāo)記位為true以后誊涯,
// 系統(tǒng)會(huì)進(jìn)行相應(yīng)的優(yōu)化挡毅。
public void setWillNotDraw(boolean willNotDraw) {
    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}

  • 默認(rèn)情況下,View沒(méi)有啟用這個(gè)優(yōu)化標(biāo)記位暴构,但是ViewGroup會(huì)默認(rèn)啟用這個(gè)優(yōu)化標(biāo)記位跪呈。
  • 當(dāng)我們的自定義控件繼承于ViewGroup并且本身不具備繪制功能時(shí),就可以開(kāi)啟這個(gè)標(biāo)記位從而便于系統(tǒng)進(jìn)行后續(xù)的優(yōu)化取逾。
  • 當(dāng)明確知道一個(gè)ViewGroup需要通過(guò)onDraw來(lái)繪制內(nèi)容時(shí)耗绿,我們需要顯示地關(guān)閉WILL_NOT_DRAW這個(gè)標(biāo)記位。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末砾隅,一起剝皮案震驚了整個(gè)濱河市误阻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌晴埂,老刑警劉巖究反,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異儒洛,居然都是意外死亡精耐,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門琅锻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)卦停,“玉大人向胡,你說(shuō)我怎么就攤上這事【辏” “怎么了僵芹?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)专执。 經(jīng)常有香客問(wèn)我淮捆,道長(zhǎng),這世上最難降的妖魔是什么本股? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任攀痊,我火速辦了婚禮,結(jié)果婚禮上拄显,老公的妹妹穿的比我還像新娘苟径。我一直安慰自己,他們只是感情好躬审,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布棘街。 她就那樣靜靜地躺著,像睡著了一般承边。 火紅的嫁衣襯著肌膚如雪遭殉。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,155評(píng)論 1 299
  • 那天博助,我揣著相機(jī)與錄音险污,去河邊找鬼。 笑死富岳,一個(gè)胖子當(dāng)著我的面吹牛蛔糯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播窖式,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蚁飒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了萝喘?” 一聲冷哼從身側(cè)響起淮逻,我...
    開(kāi)封第一講書(shū)人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎阁簸,沒(méi)想到半個(gè)月后弦蹂,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡强窖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年凸椿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片翅溺。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡脑漫,死狀恐怖髓抑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情优幸,我是刑警寧澤吨拍,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站网杆,受9級(jí)特大地震影響羹饰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜碳却,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一队秩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧昼浦,春花似錦馍资、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至使兔,卻和暖如春建钥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背虐沥。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工熊经, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人置蜀。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像悉盆,于是被迫代替她去往敵國(guó)和親盯荤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

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

  • View的加載流程view布局一直貫穿于整個(gè)android應(yīng)用中焕盟,不管是activity還是fragment都給我...
    ZEKI安卓學(xué)弟閱讀 263評(píng)論 0 0
  • 概述 本篇文章會(huì)從源碼(基于Android 6.0)角度分析Android中View的繪制流程秋秤,側(cè)重于對(duì)整體流程的...
    absfree閱讀 76,878評(píng)論 24 273
  • View的繪制和事件處理是兩個(gè)重要的主題,上一篇《圖解 Android事件分發(fā)機(jī)制》已經(jīng)把事件的分發(fā)機(jī)制講得比較詳...
    Kelin閱讀 119,433評(píng)論 100 845
  • 最近重新看了一下任玉剛大佬的《Android 開(kāi)發(fā)藝術(shù)探索》脚翘,寫(xiě)了篇筆記灼卢,分享給大家。 1. ViewRootIm...
    燈不利多閱讀 965評(píng)論 0 5
  • Android的UI管理系統(tǒng)層級(jí)關(guān)系 PhoneWindow是Adroid系統(tǒng)中最基本的窗口系統(tǒng)来农,每個(gè)Activi...
    凱玲之戀閱讀 1,884評(píng)論 0 2