Android View绘制流程详解

137 阅读6分钟

Android View绘制流程详解

在Android开发中,理解View的绘制流程对于优化应用性能和解决UI相关问题至关重要。本文将按照正确的时序详细解析从Activity启动到测量(Measure)、布局(Layout)、绘制(Draw)的完整流程。

引言

Android应用的用户界面是由一个个View组成的,从简单的Button、TextView到复杂的自定义View,它们都需要经过一系列的流程才能呈现在用户面前。了解这些流程不仅有助于我们编写高效的UI代码,还能帮助我们解决一些棘手的界面问题。

View的绘制流程主要分为三个阶段:

  1. 测量阶段(Measure):确定View的大小
  2. 布局阶段(Layout):确定View的位置
  3. 绘制阶段(Draw):将View绘制到屏幕上

这三个阶段由ViewRootImpl统一管理,并按照一定的顺序执行。接下来我们将按照正确的时序深入探讨每个阶段的具体实现。

1. Activity启动流程

1.1 ActivityThread中的关键方法

在Activity的启动过程中,ActivityThread中的handleLaunchActivity、performLaunchActivity、handleResumeActivity这3个主要的方法完成了Activity的创建到启动工作。

handleLaunchActivity()
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...

    if (localLOGV) Slog.v(
        TAG, "Handling launch of " + r);
    
    //分析1 : 这里是创建Activity,并调用了Activity的onCreate()和onStart()
    Activity a = performLaunchActivity(r, customIntent);

    if (a != null) {
        r.createdConfig = new Configuration(mConfiguration);
        Bundle oldState = r.state;
        //分析2 : 这里调用Activity的onResume()
        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && !r.startsNotResumed);
    }
    ....
}
performLaunchActivity()
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ......
    //分析1 : 这里底层是通过反射来创建的Activity实例
    java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
    activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);

    //底层也是通过反射构建Application,如果已经构建则不会重复构建,毕竟一个进程只能有一个Application
    Application app = r.packageInfo.makeApplication(false, mInstrumentation);

    if (activity != null) {
        Context appContext = createBaseContextForActivity(r, activity);
        CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
        Configuration config = new Configuration(mCompatConfiguration);
        if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                + r.activityInfo.name + " with config " + config);
        //分析2 : 在这里实例化了PhoneWindow,并将该Activity设置为PhoneWindow的Callback回调,还初始化了WindowManager
        activity.attach(appContext, this, getInstrumentation(), r.token,
                r.ident, app, r.intent, r.activityInfo, title, r.parent,
                r.embeddedID, r.lastNonConfigurationInstances, config);

        //分析3 : 间接调用了Activity的performCreate方法,间接调用了Activity的onCreate方法.
        mInstrumentation.callActivityOnCreate(activity, r.state);
        
        //分析4: 这里和上面onCreate过程差不多,调用Activity的onStart方法
        if (!r.activity.mFinished) {
            activity.performStart();
            r.stopped = false;
        }
        ....
    }
}
handleResumeActivity()
final void handleResumeActivity(IBinder token,
        boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
    .....
    //分析1 : 在其内部调用Activity的onResume方法
    r = performResumeActivity(token, clearHide, reason);

    .....
    r.window = r.activity.getWindow();
    View decor = r.window.getDecorView();
    decor.setVisibility(View.INVISIBLE);
    //获取WindowManager
    ViewManager wm = a.getWindowManager();
    WindowManager.LayoutParams l = r.window.getAttributes();
    a.mDecor = decor;

    if (a.mVisibleFromClient) {
        .....
        //分析2 : WindowManager添加DecorView
        wm.addView(decor, l);
        ...
    }
    .....

}

在handleResumeActivity方法中,WindowManager会添加DecorView,这是触发View绘制的关键步骤。

2. setContentView()方法执行过程

当我们在Activity中调用setContentView()方法时,实际调用的是Window的setContentView()方法:

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

2.1 PhoneWindow中的setContentView()

Window是一个抽象类,其唯一实现类是PhoneWindow。在PhoneWindow中,setContentView()方法主要完成以下工作:

  1. 创建DecorView
  2. 根据主题选择合适的布局
  3. 将我们设置的布局添加到content容器中
@Override
public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

2.2 DecorView的创建

installDecor()方法负责创建DecorView:

private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
        mDecor = generateDecor(-1);
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    } else {
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);
    }
}

DecorView继承于FrameLayout,是Activity的根布局。generateLayout()方法会根据主题选择不同的布局,但都会包含一个ID为com.android.internal.R.id.content的容器组件,我们通过setContentView()设置的布局会被添加到这个容器中。

3. View绘制的触发时机

3.1 WindowManagerImpl的addView()

当Activity执行到onResume之后,会调用WindowManager的addView方法将DecorView添加到窗口中:

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

我们看到,方法里面将逻辑交给了mGlobal,mGlobal是WindowManagerGlobal,WindowManagerGlobal是全局单例。WindowManagerImpl的方法都是由WindowManagerGlobal完成的。

3.2 WindowManagerGlobal的addView()

在WindowManagerGlobal中,会创建ViewRootImpl并调用其setView()方法:

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    ....

    ViewRootImpl root;

    synchronized (mLock) {
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);

        .....

        root.setView(view, wparams, panelParentView);
    }
}

在这里我们看到了,实例化ViewRootImpl,然后建立ViewRootImpl与View的联系。跟着进入setView方法:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    .....
    requestLayout();
    .....
}

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        //检查线程合法性
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        ......
    }
}

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

void doTraversal() {
    if (mTraversalScheduled) {
        ....
        performTraversals();
        ....
    }
}

一路下来,我们来到了熟悉的方法面前:performTraversals方法。这是整个View绘制流程的入口。

4. View绘制核心流程 - performTraversals

绘制的入口是performTraversals()方法:

private void performTraversals() {
    //分析1 : 这里面会调用performMeasure开始测量流程
    measureHierarchy(host, lp, res,
                    desiredWindowWidth, desiredWindowHeight);
    //分析2 : 开始布局流程
    performLayout(lp, mWidth, mHeight);
    //分析3 : 开始绘画流程
    performDraw();
}

4.1 performTraversals()方法详解

private void performTraversals() {
    // 获取MeasureSpec
    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width, lp.privateFlags);
    childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height,
            lp.privateFlags);
    
    // 测量
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    
    // 布局
    performLayout(lp, mWidth, mHeight);
    
    // 绘制
    performDraw();
}

整个View绘制流程按照"测量(Measure) -> 布局(Layout) -> 绘制(Draw)"的顺序执行。

5. 测量流程(Measure)

5.1 performMeasure()方法

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

测量过程从根View开始,递归遍历整个View树,计算每个View的尺寸。

5.2 View的measure方法

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ......
    //调用onMeasure方法  
    onMeasure(widthMeasureSpec, heightMeasureSpec);
    ......
}

measure方法里面有一些检测是否需要重新onMeasure的代码,被略去了。

onMeasure是View里面的方法,ViewGroup是一个抽象类并且没有重写onMeasure。因为onMeasure方法的实现,每个都是不一样的,比如LinearLayout和FrameLayout的onMeasure方法肯定是实现逻辑不一样的。

因为DecorView是FrameLayout,所以我们看看FrameLayout中的onMeasure。

5.3 FrameLayout的onMeasure()

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();

    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            //分析1 : 遍历所有子控件,测量每个子控件的大小
                //参数1:View控件
                //参数2:宽MeasureSpec
                //参数3:父容器在宽度上已经用了多少了,因为FrameLayout的规则是:前面已经放置的View并不会影响后面放置View的宽高,是直接覆盖到上一个View上的.所以这里传0
                //参数4:高MeasureSpec
                //参数5:父容器在高度上已经用了多少了
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
        }
    }
    ......

    //分析2 : 测量完所有的子控件的大小之后,才知道自己的大小  这很符合FrameLayout的规则嘛
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
            resolveSizeAndState(maxHeight, heightMeasureSpec,
                    childState << MEASURED_HEIGHT_STATE_SHIFT));
    ......
}

FrameLayout的onMeasure方法中会遍历所有子控件,然后进行所有子控件的大小测量。最后才来设置自己的大小。注意,onMeasure方法的入参MeasureSpec是从父容器传过来的,意思就是给你个参考,你自己看着办吧。

5.4 measureChildWithMargins方法

在测量子控件大小的时候会调用ViewGroup的measureChildWithMargins方法:

protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpec, int widthUsed,
        int parentHeightMeasureSpec, int heightUsed) {
    //获取子控件的LayoutParams
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    
    //分析1: 计算子控件在宽上的MeasureSpec  
        //参数1:父容器的MeasureSpec
        //参数2:这里官方的入参名称是padding,从下面这个传值的形式来看,显然是子控件在宽上不能利用的空间(ViewGroup的左右两边padding+子控件的左右margin+父容器在宽度上已经使用了并且不能再使用的空间)
        //参数3:子控件想要的宽度
    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);
    
    //分析2: 将measure过程传递给子控件  如果子控件又是一个ViewGroup,那么继续向下传递
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

在measureChildWithMargins方法里我们首先是看到根据子控件的LayoutParams和父容器的MeasureSpec计算子控件的MeasureSpec,然后将计算出的MeasureSpec通过子控件的measure方法传递下去。如果子控件又是一个ViewGroup,那么它又会重复的measure流程,一直向下传递这个过程,直接最后的那个是View为止。因为View没有子控件,它就不能向下传递了。

5.5 MeasureSpec介绍

在测量过程中,Android使用MeasureSpec类来封装测量规格。MeasureSpec包含两个信息:

  1. 测量模式(Mode):UNSPECIFIED、EXACTLY、AT_MOST
  2. 测量大小(Size):具体的尺寸值

MeasureSpec代表的是32位的int值,它的高2位是SpecMode(也是一个int),低30位是SpecSize(也是一个int),SpecMode是测量模式,SpecSize是测量大小。MeasureSpec相当于是两者的结合。系统封装了如何从MeasureSpec中提取SpecMode和SpecSize,也封装了用SpecMode和SpecSize组合成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;
    public static final int EXACTLY     = 1 << MODE_SHIFT;
    public static final int AT_MOST     = 2 << MODE_SHIFT;

    public static int makeMeasureSpec(int size,int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }

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

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

SpecMode有3种,分别是:

  • UNSPECIFIED:父容器对View不会有任何限制,要多大给多大,一般是用在系统内部使用,我们开发的APP用不到。
  • EXACTLY:这种情况对应于match_parent和具体数值这两种模式,父容器已经检测出View需要的精确大小。
  • AT_MOST:这种情况对应于wrap_content,父容器指定了一个最大值,View不能超过这个值。

测量过程通过measure()方法传递MeasureSpec,每个View根据自己的LayoutParams和父容器的MeasureSpec来决定自己的测量规格。

普通View的MeasureSpec的创建规则.png

5.6 MeasureSpec的创建规则

MeasureSpec的创建规则如下:

  1. 如果View的宽高是固定的值,那么不管父容器的MeasureSpec是什么,View的MeasureSpec都是EXACTLY
  2. 如果View的宽高是wrap_content,那么不管父容器的MeasureSpec是EXACTLY还是AT_MOST,最终View的MeasureSpec都是AT_MOST,而且View最终的大小不能超过父容器的剩余空间
  3. 如果View的宽高是match_parent,那么要分两种情况:
    • 如果父容器是EXACTLY,那么View就是EXACTLY
    • 如果父容器是AT_MOST,那么View也是AT_MOST

5.7 获取根View的MeasureSpec

measure是从上往下执行的,widthMeasureSpec和heightMeasureSpec通常情况下是由父容器传递给子视图的。但是最外层的根视图,怎么拿到MeasureSpec呢?在执行performMeasure方法之前,我们需要拿到最外层的视图的MeasureSpec:

private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {
    //如果是MATCH_PARENT,那么就是EXACTLY
    case ViewGroup.LayoutParams.MATCH_PARENT:
        // Window can't resize. Force root view to be windowSize.
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
        break;
    //如果是WRAP_CONTENT,就是AT_MOST
    case ViewGroup.LayoutParams.WRAP_CONTENT:
        // Window can resize. Set max size for root view.
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
        break;
    default:
        //如果是固定的值,也是EXACTLY
        // Window wants to be an exact size. Force root view to be that size.
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
        break;
    }
    return measureSpec;
}

最外层的根视图的MeasureSpec只由自己的LayoutParams决定,做自己的主人,舒服。

5.8 getChildMeasureSpec方法

//这里来自ViewGroup的getChildMeasureSpec方法,无删减
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    //根据父容器的MeasureSpec获取父容器的SpecMode和SpecSize
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);
    
    //剩下的size
    int size = Math.max(0, specSize - padding);

    //最终的size和mode
    int resultSize = 0;
    int resultMode = 0;

    switch (specMode) {
    // Parent has imposed an exact size on us
    //父容器有一个确定的大小
    case MeasureSpec.EXACTLY:
        if (childDimension >= 0) {
            //子控件也是确定的大小,那么最终的大小就是子控件设置的大小,SpecMode为EXACTLY
            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 (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size. It can't be
            // bigger than us.
            //子控件想要自己定义大小,但是不能超过剩余空间 size
            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
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

这段代码对应着下面这段总结:

  • 如果View的宽高是固定的值,那么不管父容器的MeasureSpec是什么,View的MeasureSpec都是EXACTLY
  • 如果View的宽高是wrap_content,那么不管父容器的MeasureSpec是EXACTLY还是AT_MOST,最终View的MeasureSpec都是AT_MOST,这里暂时不用管UNSPECIFIED(我们用不到)。而且View最终的大小不能超过父容器的剩余空间
  • 如果View的宽高是match_parent,那么要分两种情况:
    • 如果父容器是EXACTLY,那么View就是EXACTLY
    • 如果父容器是AT_MOST,那么View也是AT_MOST

5.9 measure小结

从ViewRootImpl的performTraversals方法开始进入View的绘制过程,performTraversals方法里面会有一个performMeasure方法。这个performMeasure方法是专门拿来测量View的大小的。而且会遍历整个View树,全部进行测量。

在performMeasure里面会调用measure方法,然后measure会调用onMeasure方法,而在onMeasure方法中则会对所有的子元素进行measure过程。这相当于完成了一次从父元素到子元素的measure传递过程,如果子元素是一个ViewGroup,那么继续向下传递,直到所有的View都已测量完成。测量完成之后,我们可以根据getMeasureHeight和getMeasureWidth方法获取该View的高度和宽度。

6. 布局流程(Layout)

6.1 performLayout()方法

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) {
    .....
    //这里的host其实是根视图(DecorView)
        //参数:left,top,right,bottom  这些位置都是相对于父容器而言的
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    .....
}

在performLayout方法里面调用了DecorView的layout方法,然后我发现:layout方法其实是View这个父类里面的,然后ViewGroup继承了View之后重写了一下,只是调了一下super.layout(l, t, r, b);,相当于实现还是在View的layout里面。而且ViewGroup的layout方法是final修饰的,意味着子类不能再重写这个方法了。

6.2 View的layout()方法

//以下是View的layout方法
public void layout(int l, int t, int r, int b) {
    ......
    onLayout(changed, l, t, r, b);
    ......
}

layout方法其实就是调用onLayout方法,如果这里子控件是一个View的话,那么onLayout其实是空实现。onLayout在ViewGroup是一个抽象方法,如果是一个ViewGroup的话,比如FrameLayout,那么onLayout是需要自己实现的。

//View中的定义
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}

//ViewGroup中的定义,没错,这是抽象方法,具体的实现交由实现类去实现
@Override
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);

因为我们的根视图是DecorView,也就是FrameLayout,那么我们来看一下FrameLayout的onLayout实现:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {   
    //布局子控件  我没看懂这个changed参数是拿来干什么的,好像并没有用上(这里已经是FrameLayout的onLayout方法的全部代码了)
    layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}

void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
    final int count = getChildCount();
    
    //最左侧
    final int parentLeft = getPaddingLeftWithForeground();
    //最右侧
    final int parentRight = right - left - getPaddingRightWithForeground();
    //最顶部
    final int parentTop = getPaddingTopWithForeground();
    //最底部
    final int parentBottom = bottom - top - getPaddingBottomWithForeground();

    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            
            //因为已经measure流程走完了,所以这里是能通过getMeasuredWidth方法获取测量宽度的
            final int width = child.getMeasuredWidth();
            final int height = child.getMeasuredHeight();

            //实际子控件的left
            int childLeft;
            int childTop;

            int gravity = lp.gravity;
            if (gravity == -1) {
                gravity = DEFAULT_CHILD_GRAVITY;
            }

            final int layoutDirection = getLayoutDirection();
            final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
            final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                //水平居中
                case Gravity.CENTER_HORIZONTAL:
                    childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                    lp.leftMargin - lp.rightMargin;
                    break;
                case Gravity.RIGHT:  //子控件在父容器的最右侧
                    if (!forceLeftGravity) {
                        childLeft = parentRight - width - lp.rightMargin;
                        break;
                    }
                case Gravity.LEFT: //子控件在父容器的最左侧
                default:
                    childLeft = parentLeft + lp.leftMargin;
            }
            
            //竖直方向上的gravity
            switch (verticalGravity) {
                case Gravity.TOP:  //位于父容器的顶部
                    childTop = parentTop + lp.topMargin;
                    break;
                case Gravity.CENTER_VERTICAL:  //垂直居中
                    childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                    lp.topMargin - lp.bottomMargin;
                    break;
                case Gravity.BOTTOM:  //位于父容器底部
                    childTop = parentBottom - height - lp.bottomMargin;
                    break;
                default:
                    childTop = parentTop + lp.topMargin;
            }
            
            //最后给这个子控件一个最终的left,top,right,bottom值
            //把这个子控件放在这里
            child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
    }
}

不同的ViewGroup的实现类的onLayout方法实现是不一样的,是根据自身情况来决定将子控件放在那里的,比如FrameLayout和LinearLayout的onLayout是不一样的实现,但是onLayout这个方法最终是将各个子控件有条不紊的放在对应的位置上。

我们看到在onLayout方法的最后,调用了子控件的layout方法,其实就是将layout流程向下进行传递了。如果子控件还是ViewGroup的话,那么它又会对它自己所有的子控件进行布局,放置。最后一层一层的往下,直到全部都layout完成。每个View都知道自己的left,top,right,bottom。这个时候是可以通过View的getWidth和getHeight来获取最终的宽高的。

public final int getWidth() {
    return mRight - mLeft;
}

public final int getHeight() {
    return mBottom - mTop;
}

6.3 layout小结

layout主要是为了确定该控件以及其子控件的位置和大小。在performLayout中,主要是确定每个控件的left+top+right+bottom,performLayout之后它们的位置就已经被确定了,就只剩下最后一步绘制了。

7. 绘制流程(Draw)

7.1 performDraw()方法

private void performDraw() {
    draw(fullRedrawNeeded);
}

还是从ViewRootImpl的performTraversals方法开始分析:

private void performTraversals() {
    //开始绘画流程
    performDraw();
}

private void performDraw() {
    ......
    draw(fullRedrawNeeded);
    ......
}

private void draw(boolean fullRedrawNeeded){
    .....
    drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty);
    .....
}

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

随着方法的调用深入,发现来到了View的draw方法。

7.2 View的draw()方法

public void draw(Canvas canvas) {
    .....

    /*
        注意了这是官方给的注释,谷歌工程师还真是贴心,把draw步骤写的详详细细,给力,点赞
     * Draw traversal performs several drawing steps which must be executed
     * in the appropriate order:
     *
     *      1. Draw the background
     *      2. If necessary, save the canvas' layers to prepare for fading
     *      3. Draw view's content
     *      4. Draw children
     *      5. If necessary, draw the fading edges and restore layers
     *      6. Draw decorations (scrollbars for instance)
     */

    // Step 1, draw the background, if needed
    //1. 绘制背景
    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
        //3. 绘制自己的内容
        if (!dirtyOpaque) onDraw(canvas);

        // Step 4, draw the children
        //4. 绘制子控件  如果是View的话这个方法是空实现,如果是ViewGroup则绘制子控件
        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)
        //6. 绘制装饰和前景
        onDrawForeground(canvas);

        // Step 7, draw the default focus highlight
        //7. 绘制默认焦点高亮显示
        drawDefaultFocusHighlight(canvas);

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

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

draw的基本步骤如下:

  1. 绘制背景
  2. 绘制控件自己本身的内容
  3. 绘制子控件
  4. 绘制装饰(比如滚动条)和前景

这里简单提一下dispatchDraw方法,在这个方法里面会去调用drawChild方法,在drawChild里面会调用子控件的draw方法,这相当于完成了draw的传递过程,通知子控件去绘制它自己。然后如果子控件是ViewGroup,它又会重复上面这个递推。

7.3 Draw流程详解

绘制流程是View显示到屏幕上的最后一步,它决定了View的最终外观:

  1. 绘制背景 (drawBackground):绘制View的背景色或背景图片
  2. 保存画布状态:保存当前Canvas的状态,便于后续恢复
  3. 绘制内容 (onDraw):这是开发者最常重写的方法,用于绘制View的具体内容
  4. 绘制子View (dispatchDraw):对于ViewGroup,需要遍历绘制所有子View
  5. 绘制装饰 (onDrawForeground):绘制滚动条、前景等装饰元素
  6. 恢复画布状态:恢复之前保存的Canvas状态

需要注意的是,绘制顺序遵循"后绘制的覆盖先绘制的"原则,因此子View会覆盖父View的内容。

measure流程图.png

draw的流程比测量和布局要简单一些,但是需要注意的是,View绘制过程是通过dispatchDraw来传递的。

8. 总结

Android View的绘制流程按照正确的时序可以概括为以下步骤:

  1. Activity启动阶段:Activity通过ActivityThread的handleLaunchActivity、performLaunchActivity、handleResumeActivity方法完成创建和启动
  2. setContentView阶段:在Activity的onCreate方法中调用setContentView,创建PhoneWindow和DecorView,并将布局添加到content容器中
  3. 添加到WindowManager阶段:Activity启动时,在onResume之后通过WindowManager将DecorView添加到系统窗口管理器中
  4. 触发绘制阶段:通过ViewRootImpl触发测量、布局、绘制流程
  5. 测量阶段:从根View开始递归计算每个View的尺寸
  6. 布局阶段:确定每个View在屏幕上的位置
  7. 绘制阶段:将View内容绘制到屏幕上

理解这个流程有助于我们:

  • 优化布局性能,减少不必要的measure/layout
  • 自定义View时正确实现measure、layout、draw方法
  • 解决UI相关的问题,如布局错乱、绘制异常等

通过深入理解View的绘制机制,开发者可以更好地进行性能调优和自定义控件开发,从而构建更流畅、用户体验更好的Android应用。