一文彻底搞懂Android View的绘制流程

一文彻底搞懂Android View的绘制流程,第1张

概述前言成为一名优秀的Android开发,需要一份完备的知识体系,在这里,让我们一起成长为自己所想的那样~。上一篇中我们讲到了Android的触摸事件传递机制,除此之外,关于AndroidView的绘制流程这一块也是View相关的核心知识点。我们都知道,PhoneWindow是Android系统中最基本的窗口系统,每个Act 前言成为一名优秀的AndroID开发,需要一份完备的知识体系,在这里,让我们一起成长为自己所想的那样~。

上一篇中我们讲到了Android的触摸事件传递机制,除此之外,关于AndroID VIEw的绘制流程这一块也是VIEw相关的核心知识点。我们都知道,PhoneWindow是AndroID系统中最基本的窗口系统,每个Activity会创建一个。同时,PhoneWindow也是Activity和VIEw系统交互的接口。DecorVIEw本质上是一个FrameLayout,是Activity中所有VIEw的祖先。

一、开始:DecorVIEw被加载到Window中

从Activity的startActivity开始,最终调用到ActivityThread的handleLaunchActivity方法来创建Activity,相关核心代码如下:

private voID handleLaunchActivity(ActivityClIEntRecord r, Intent customIntent) {    ....    // 创建Activity,会调用Activity的onCreate方法    // 从而完成DecorVIEw的创建    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;    // 调用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是一个接口            // 并且继承了接口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的实现类是WindowManagerImpl,                // 所以实际调用的是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绘制的整体流程之前,我们必须先了解下VIEwRoot和DecorVIEw的概念。VIEwRoot对应于VIEwRootImpl类,它是连接WindowManager和DecorVIEw的纽带,VIEw的三大流程均是通过VIEwRoot来完成的。在ActivityThread中,当Activity对象被创建完毕后,会将DecorVIEw添加到Window中,同时会创建VIEwRootImpl对象,并将VIEwRootImpl对象和DecorVIEw建立关联,相关源码如下所示:

// WindowManagerGlobal的addVIEw方法public voID addVIEw(VIEw vIEw, VIEwGroup.LayoutParams params, display display, Window parentwindow) {    ...    VIEwRootImpl root;    VIEw pannelParentVIEw = null;    synchronized (mlock) {        ...        // 创建VIEwRootImpl实例        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;    }}
二、了解绘制的整体流程

绘制会从根视图VIEwRoot的performTraversals()方法开始,从上到下遍历整个视图树,每个VIEw控件负责绘制自己,而VIEwGroup还需要负责通知自己的子VIEw进行绘制 *** 作。performTraversals()的核心代码如下。

private voID performTraversals() {    ...    int chilDWIDthMeasureSpec = getRootMeasureSpec(mWIDth, lp.wIDth);    int childHeightmeasureSpec = getRootMeasureSpec(mHeight, lp.height);    ...    //执行测量流程    performMeasure(chilDWIDthMeasureSpec, childHeightmeasureSpec);    ...    //执行布局流程    performlayout(lp, desireDWindowWIDth, desireDWindowHeight);    ...    //执行绘制流程    performDraw();}

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

显示不出来的可点击这里查看

注意:

preformlayout和performDraw的传递流程和performMeasure是类似的,唯一不同的是,performDraw的传递过程是在draw方法中通过dispatchDraw来实现的,不过这并没有本质区别。

获取content:

VIEwGroup content = (VIEwGroup)findVIEwByID(androID.R.ID.content);

获取设置的VIEw:

content.getChildAt(0);

三、理解MeasureSpec1.MeasureSpec源码解析

MeasureSpec表示的是一个32位的int值,它的高2位表示测量模式SpecMode,低30位表示某种测量模式下的规格大小Specsize。MeasureSpec是VIEw类的一个静态内部类,用来说明应该如何测量这个VIEw。MeasureSpec的核心代码如下。

public static class MeasureSpec {    private static final int MODE_SHIFT = 30;    private static final int MODE_MASK = 0X3 << MODE_SHIFT;    // 不指定测量模式, 父视图没有限制子视图的大小,子视图可以是想要    // 的任何尺寸,通常用于系统内部,应用开发中很少用到。    public static final int UnspecIFIED = 0 << MODE_SHIFT;    // 精确测量模式,视图宽高指定为match_parent或具体数值时生效,    // 表示父视图已经决定了子视图的精确大小,这种模式下VIEw的测量    // 值就是Specsize的值。    public static final int EXACTLY = 1 << MODE_SHIFT;    // 最大值测量模式,当视图的宽高指定为wrap_content时生效,此时    // 子视图的尺寸可以是不超过父视图允许的最大尺寸的任何尺寸。    public static final int AT_MOST = 2 << MODE_SHIFT;    // 根据指定的大小和模式创建一个MeasureSpec    public static int makeMeasureSpec(int size, int mode) {        if (sUsebrokenMakeMeasureSpec) {            return size + mode;        } else {            return (size & ~MODE_MASK) | (mode & MODE_MASK);        }    }    // 微调某个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通过将SpecMode和Specsize打包成一个int值来避免过多的对象内存分配,为了方便 *** 作,其提供了打包和解包的方法,打包方法为上述源码中的makeMeasureSpec,解包方法源码如下:

public static int getMode(int measureSpec) {    return (measureSpec & MODE_MASK);}public static int getSize(int measureSpec) {    return (measureSpec & ~MODE_MASK);}
2.DecorVIEw的MeasureSpec的创建过程:
//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的创建过程
// VIEwGroup的measureChilDWithmargins方法protected voID measureChilDWithmargins(VIEw child,int parentWIDthMeasureSpec, int wIDthUsed,int parentHeightmeasureSpec, int heightUsed) {    final marginLayoutParams lp = (marginLayoutParams) child.getLayoutParams();    // 子元素的MeasureSpec的创建与父容器的MeasureSpec和子元素本身    // 的LayoutParams有关,此外还和VIEw的margin及padding有关    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 deter@R_301_6386@ 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 deter@R_301_6386@ 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 deter@R_301_6386@ its own size....                // find out how big it should be                resultSize = 0;                resultMode == MeasureSpec.UnspecIFIED;            }            break;        }    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);}

普通VIEw的MeasureSpec的创建规则如下:

注意:UnspecIFIED模式主要用于系统内部多次Measure的情形,一般不需关注。

结论:对于DecorVIEw而言,它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同决定;对于普通的VIEw,它的MeasureSpec由父视图的MeasureSpec和其自身的LayoutParams共同决定。

四、VIEw绘制流程之Measure1.Measure的基本流程

由前面的分析可知,页面的测量流程是从performMeasure方法开始的,相关的核心代码流程如下。

private voID perormMeasure(int chilDWIDthMeasureSpec, int childHeightmeasureSpec) {    ...    // 具体的测量 *** 作分发给VIEwGroup    mVIEw.measure(chilDWIDthMeasureSpec, childHeightmeasureSpec);    ...}// 在VIEwGroup中的measureChildren()方法中遍历测量VIEwGroup中所有的VIEwprotected 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];        // 当VIEw的可见性处于GONE状态时,不对其进行测量        if ((child.mVIEwFlags & VISIBIliTY_MASK) != GONE) {            measureChild(child, wIDthMeasureSpec, heightmeasureSpec);        }    }}// 测量某个指定的VIEwprotected voID measureChild(VIEw child, int parentWIDthMeasureSpec, int parentHeightmeasureSpec) {    final LayoutParams lp = child.getLayoutParams();    // 根据父容器的MeasureSpec和子VIEw的LayoutParams等信息计算    // 子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没有定义测量的具体过程,因为VIEwGroup是一个    // 抽象类,其测量过程的onMeasure方法需要各个子类去实现    onMeasure(wIDthMeasureSpec, heightmeasureSpec);    ...}// 不同的VIEwGroup子类有不同的布局特性,这导致它们的测量细节各不相同,如果需要自定义测量过程,则子类可以重写这个方法protected voID onMeasure(int wIDthMeasureSpec, int heightmeasureSpec) {    // setMeasureDimension方法用于设置VIEw的测量宽高    setMeasureDimension(getDefaultSize(getSuggestedMinimumWIDth(), wIDthMeasureSpec),     getDefaultSize(getSuggestedMinimumHeight(), heightmeasureSpec));}// 如果VIEw没有重写onMeasure方法,则会默认调用getDefaultSize来获得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.对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没有设置背景,那么返回androID:minWIDth这个属性所指定的值,这个值可以为0;如果VIEw设置了背景,则返回androID:minWIDth和背景的最小宽度这两者中的最大值。

3.自定义view时手动处理wrap_content时的情形

直接继承VIEw的控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则在布局中使用wrap_content就相当于使用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的情况下指定内部宽/高(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.linearLayout的onMeasure方法实现解析
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);    ...    // Deter@R_301_6386@ 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));}

系统会遍历子元素并对每个子元素执行measureChildBeforeLayout方法,这个方法内部会调用子元素的measure方法,这样各个子元素就开始依次进入measure过程,并且系统会通过mTotalLength这个变量来存储linearLayout在竖直方向的初步高度。每测量一个子元素,mTotalLength就会增加,增加的部分主要包括了子元素的高度以及子元素在竖直方向上的margin等。

// linearLayout测量自己大小的核心源码// Add in our paddingmTotalLength += mpaddingtop + mpaddingBottom;int heightSize = mTotalLength;// Check against our minimum heightheightSize = Math.max(heightSize, getSuggestedMinimumHeight());// Reconcile our calculated size with the heightmeasureSpecint 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:            // 高度不能超过父容器的剩余空间            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中获取某个VIEw的宽高

由于VIEw的measure过程和Activity的生命周期方法不是同步执行的,如果VIEw还没有测量完毕,那么获得的宽/高就是0。所以在onCreate、onStart、onResume中均无法正确得到某个VIEw的宽高信息。解决方式如下:

Activity/VIEw#onWindowFocusChanged

// 此时VIEw已经初始化完毕
// 当Activity的窗口得到焦点和失去焦点时均会被调用一次
// 如果频繁地进行onResume和onPause,那么onWindowFocusChanged也会被频繁地调用
public voID onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
int wIDth = vIEw.getMeasureWIDth();
int height = vIEw.getMeasuredHeight();
}
}

vIEw.post(runnable)

// 通过post可以将一个runnable投递到消息队列的尾部,// 然后等待Looper调用次runnable的时候,VIEw也已经初
// 始化好了
protected voID onStart() {
super.onStart();
vIEw.post(new Runnable() {

    @OverrIDe    public voID run() {        int wIDth = vIEw.getMeasureDWIDth();        int height = vIEw.getMeasuredHeight();    }});

}

VIEwTreeObserver

// 当VIEw树的状态发生改变或者VIEw树内部的VIEw的可见// 性发生改变时,onGlobalLayout方法将被回调
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的绘制流程之Layout1.Layout的基本流程
// VIEwRootImpl.javaprivate voID performlayout(WindowManager.LayoutParams lp, int desireDWindowWIDth, int desireDWindowHeight) {    ...    host.layout(0, 0, host.getMeasureDWIDth(), host.getMeasuredHeight());    ...}// VIEw.javapublic voID layout(int l, int t, int r, int b) {    ...    // 通过setFrame方法来设定VIEw的四个顶点的位置,即VIEw在父容器中的位置    boolean changed = isLayoutModeOptical(mParent) ?     set OpticalFrame(l, t, r, b) : setFrame(l, t, r, b);    ...    onLayout(changed, l, t, r, b);    ...}// 空方法,子类如果是VIEwGroup类型,则重写这个方法,实现VIEwGroup// 中所有VIEw控件布局流程protected voID onLayout(boolean changed, int left, int top, int right, int bottom) {}
2.linearLayout的onLayout方法实现解析
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;            // 为子元素确定对应的位置            setChildFrame(child, childleft, childtop + getLocationOffset(child), chilDWIDth, childHeight);            // childtop会逐渐增大,意味着后面的子元素会被            // 放置在靠下的位置            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的默认实现中,VIEw的测量宽/高和最终宽/高是相等的,只不过测量宽/高形成于VIEw的measure过程,而最终宽/高形成于VIEw的layout过程,即两者的赋值时机不同,测量宽/高的赋值时机稍微早一些。在一些特殊的情况下则两者不相等:

重写VIEw的layout方法,使最终宽度总是比测量宽/高大100px

public voID layout(int l, int t, int r, int b) {
super.layout(l, t, r + 100, b + 100);
}

VIEw需要多长measure才能确定自己的测量宽/高,在前几次测量的过程中,其得出的测量宽/高有可能和最终宽/高不一致,但最终来说,测量宽/高还是和最终宽/高相同六、VIEw的绘制流程之Draw1.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);    ...}// 绘制基本上可以分为六个步骤public voID draw(Canvas canvas) {    ...    // 步骤一:绘制VIEw的背景    drawBackground(canvas);    ...    // 步骤二:如果需要的话,保持canvas的图层,为fading做准备    saveCount = canvas.getSaveCount();    ...    canvas.saveLayer(left, top, right, top + length, null, flags);    ...    // 步骤三:绘制VIEw的内容    onDraw(canvas);    ...    // 步骤四:绘制VIEw的子VIEw    dispatchDraw(canvas);    ...    // 步骤五:如果需要的话,绘制VIEw的fading边缘并恢复图层    canvas.drawRect(left, top, right, top + length, p);    ...    canvas.restoretoCount(saveCount);    ...    // 步骤六:绘制VIEw的装饰(例如滚动条等等)    onDrawForeground(canvas)}
2.setwillNotDraw的作用
// 如果一个VIEw不需要绘制任何内容,那么设置这个标记位为true以后,// 系统会进行相应的优化。public voID setwillNotDraw(boolean willNotDraw) {    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);}
默认情况下,VIEw没有启用这个优化标记位,但是VIEwGroup会默认启用这个优化标记位。当我们的自定义控件继承于VIEwGroup并且本身不具备绘制功能时,就可以开启这个标记位从而便于系统进行后续的优化。当明确知道一个VIEwGroup需要通过onDraw来绘制内容时,我们需要显示地关闭WILL_NOT_DRAW这个标记位。七、总结

VIEw的绘制流程和事件分发机制都是AndroID开发中的核心知识点,也是自定义view高手的内功心法。对于一名优秀的AndroID开发来说,主流三方源码分析和AndroID核心源码分析可以说是必修课,下一篇,将会带领大家更进一步深入AndroID。

参考链接:

1、AndroID开发艺术探索

2、AndroID进阶之光

3、AndroID高级进阶

4、Android应用层View绘制流程与源码分析

5、Android中View绘制流程浅析

赞赏

如果这个库对您有很大帮助,您愿意支持这个项目的进一步开发和这个项目的持续维护。你可以扫描下面的二维码,让我喝一杯咖啡或啤酒。非常感谢您的捐赠。谢谢!

<div align="center">
<img src="https://user-gold-cdn.xitu.io/2020/1/7/16f7dc32595031fa?w=1080&h=1457&f=jpeg&s=93345" wIDth=20%><img src="https://user-gold-cdn.xitu.io/2020/1/7/16f7dc3259518ecd?w=990&h=1540&f=jpeg&s=110691" wIDth=20%>
</div>


Contanct Me● 微信:

欢迎关注我的微信:bcce5360

● 微信群:

微信群如果不能扫码加入,麻烦大家想进微信群的朋友们,加我微信拉你进群。

<div align="center">
<img src="https://user-gold-cdn.xitu.io/2020/1/7/16f7dc352011e1fe?w=1013&h=1920&f=jpeg&s=86819" wIDth=35%>
</div>

● QQ群:

2千人QQ群,Awesome-AndroID学习交流群,QQ群号:959936182, 欢迎大家加入~

About meEmail: [chao.qu521@gmail.com]()Blog: https://jsonchao.github.io/掘金: https://juejin.im/user/5a3ba9375188252bca050ade很感谢您阅读这篇文章,希望您能将它分享给您的朋友或技术群,这对我意义重大。希望我们能成为朋友,在 Github、掘金上一起分享知识。 总结

以上是内存溢出为你收集整理的一文彻底搞懂Android View的绘制流程全部内容,希望文章能够帮你解决一文彻底搞懂Android View的绘制流程所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1067293.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-26
下一篇 2022-05-26

发表评论

登录后才能评论

评论列表(0条)

保存