Android View的绘制流程

本文源码基于Android 11

初学者可能认为执行SetContentView(int layoutResID)之后就马上开始执行绘制流程了,其实并没有,我们来看看SetContentView(int layoutResID)做了什么:

java 复制代码
public class Activity{

    private Window mWindow;

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

	final void attach(...) {
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
    }

    public Window getWindow() {
        return mWindow;
    }
}

其中mWindow是PhoneWindow的实例,接下来调用PhoneWindow的setContentView()方法:

java 复制代码
public class PhoneWindow extends Window{

    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

    // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;

    @Override
    public void setContentView(int layoutResID) {
        ...
        // 第一次mContentParent为null
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        //hasFeature()方法默认返回false
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            //把layoutResID对应的布局添加到mContentParent中
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        ...
    }

    private void installDecor() {
        //mDecor即DecorView
        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()
            mContentParent = generateLayout(mDecor);
        }
        ...
    }

    protected ViewGroup generateLayout(DecorView decor) {
        ...
        int layoutResource;
        if ((features & (1 << FEATURE_NO_TITLE)) == 0)){
            ...
        }else if ((features & (1 << FEATURE_ACTION_BAR)) != 0)){
            ...
        }else{
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;
        }
        mDecor.startChanging();
        //将layoutResource对应的布局添加到DecorView中
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

        ...

        //mContentParent对应的布局id为com.android.internal.R.id.content;
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

        ...

        return contentParent;
    }
}

其中R.layout.screen_simple的布局如下:

xml 复制代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />

    <!--这个就是上面的mContentParent-->
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

由以上代码可知,setContentView(int layoutResID)只是把layoutResID对应的布局添加到DecorView中id为content的FrameLayout里面,还没有开始执行绘制流程。

那么执行绘制流程是从什么时候开始的呢?真正执行绘制流程在Activity的onResume()生命周期方法执行之后,我们来看看ActivityThread中的代码:

java 复制代码
public final class ActivityThread{

    @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {

        ...
        //performResumeActivity()里面执行了Activity的onResume()方法
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);

        ...

        final Activity a = r.activity;
        //decor即DecorView, 每个PhoneWindow对应一个DecorView
        View decor = r.window.getDecorView();
        ViewManager wm = a.getWindowManager();
        wm.addView(decor, l);

        ...

    }
}

上面的代码中执行了wm.addView(decor, l),wm来源于Activity的getWindowManager()方法:

java 复制代码
public class Activity{

    private WindowManager mWindowManager;

    final void attach(...){
        mWindowManager = mWindow.getWindowManager();
    }

    /** Retrieve the window manager for showing custom windows. */
    public WindowManager getWindowManager() {
        return mWindowManager;
    }

}

最终追溯到Window的getWindowManager()方法:

java 复制代码
public abstract class Window{

    private WindowManager mWindowManager;

    /**
     * Set the window manager for use by this Window to, for example,
     * display panels.  This is <em>not</em> used for displaying the
     * Window itself -- that must be done by the client.
     *
     * @param wm The window manager for adding new windows.
     */
     public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
                boolean hardwareAccelerated) {
        if (wm == null) {
             wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }

    /**
     * Return the window manager allowing this Window to display its own
     * windows.
     *
     * @return WindowManager The ViewManager.
     */
    public WindowManager getWindowManager() {
        return mWindowManager;
    }

}

由以上代码可知wm即WindowManagerImpl,所以实际调用的是WindowManagerImpl的addView()方法:

java 复制代码
public final class WindowManagerImpl implements WindowManager {

    //mGlobal是WindowManagerGlobal的单例
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {

        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }
}

接着执行WindowManagerGlobal的addView()方法:

java 复制代码
public final class WindowManagerGlobal {

    private final ArrayList<View> mViews = new ArrayList<View>();

    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();

    private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();

    private static WindowManagerGlobal sDefaultWindowManager;

    public static WindowManagerGlobal getInstance() {
        synchronized (WindowManagerGlobal.class) {
            if (sDefaultWindowManager == null) {
                sDefaultWindowManager = new WindowManagerGlobal();
            }
            return sDefaultWindowManager;
        }
    }

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

        ViewRootImpl root;

        ...

        //创建ViewRootImpl实例
        root = new ViewRootImpl(view.getContext(), display);

        view.setLayoutParams(wparams);

        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);

        // do this last because it fires off messages to start doing things
        try {
            root.setView(view, wparams, panelParentView, userId);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            if (index >= 0) {
                removeViewLocked(index, true);
            }
            throw e;
        }
    }
}

接下来执行ViewRootImpl的setView()方法:

java 复制代码
public final class ViewRootImpl{

	final IWindowSession mWindowSession;

   public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
       ...
       // Schedule the first layout -before- adding to the window
       // manager, to make sure we do the relayout before receiving
       // any other events from the system.
       //在添加到Window Manager之前执行绘制流程
       requestLayout();
       ...
       res = mWindowSession.addToDisplayAsUser(...) //通过Binder将View添加到WMS中
       ...

	}

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

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

	void scheduleTraversals() {
        //添加了mTraversalScheduled标志位,所以连续两次setText()只会执行一次绘制流程
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //添加同步屏障
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // 提交给编舞者,会在下一帧绘制时调用mTraversalRunnable,运行其run()方法
            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) {
            mTraversalScheduled = false;
            //移除同步屏障
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            ...
            performTraversals();
        }
    }

	private void performTraversals() {
        ......
        int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
        int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
        ...
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); //测量
        ...
        performLayout(lp, mWidth, mHeight); //布局
        ...
        performDraw(); //绘制
        ...

	}

}

在performTraversals()方法中,首先通过getRootMeasureSpec()方法获取最外层根视图的childWidthMeasureSpec和childHeightMeasureSpec。

什么是MeasureSpec? MeasureSpec是一个32位的整形,高2位表示测试模式SpecMode,低30位表示测量规格SpecSize,它是View的静态内部类,用来说明如何测量这个View,核心代码如下:

java 复制代码
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 {
            //MODE_MASK为:11000000 00000000 00000000 00000000
            //~MODE_MASK为:00111111 11111111 11111111 11111111
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }
}

makeMeasureSpec()通过size和mode来构造MeasureSpec,当然也可以通过MeasureSepc来获取size和mode,代码如下:

java 复制代码
public static int getMode(int measureSpec) {
    return (measureSpec & MODE_MASK);
}

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

前面通过getRootMeasureSpec()方法来获取DecorView的MeasureSpec,代码如下:

java 复制代码
    /**
     * Figures out the measure spec for the root view in a window based on it's
     * layout params.
     *
     * @param windowSize
     *            The available width or height of the window
     *
     * @param rootDimension
     *            The layout params for one dimension (width or height) of the
     *            window.
     *
     * @return The measure spec to use to measure the root view.
     */
     //这个方法是来获取RootView的MeasureSpec的
    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {
            case ViewGroup.LayoutParams.MATCH_PARENT:
                // Window can't resize. Force root view to be windowSize.
                measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
                break;
            case ViewGroup.LayoutParams.WRAP_CONTENT:
                // Window can resize. Set max size for root view.
                measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
                break;
            default:
                // Window wants to be an exact size. Force root view to be that size.
                measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
                break;
        }
        return measureSpec;
    }

由以上代码可知,DecorView的MeasureSpec由窗口尺寸(windowSize)和自身的LayoutParams共同决定。

接下来把DecorView的MeasureSpec传给了performMeasure()方法,代码如下:

java 复制代码
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
	 ...
    try {
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

接下来执行View的measure()方法:

java 复制代码
public class View{

    //final方法,子类不可重写
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        ...
    }
}

由于DecorView是FrameLayout的子类,接下来执行FrameLayout的onMeasure()方法(LinearLayout、RelativeLayout等ViewGroup的子类都会根据自己的特性重写onMeasure()方法):

java 复制代码
public class FrameLayout extends ViewGroup {

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

	     int count = getChildCount();

        int maxHeight = 0;
        int maxWidth = 0;

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                //先测量子View的宽高
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                //获取宽的最大值
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                //获取高的最大值
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
                if (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }
        ...
        //测量自己的宽高
	     setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
	}
}

其中measureChildWithMargins()方法在ViewGroup中:

java 复制代码
public abstract class ViewGroup extends View implements ViewParent, ViewManager {

    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {

        //获取子视图的LayoutParams
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        //通过父视图的测量规格以及子视图本身的LayoutParams来共同决定子视图的测量规格
        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);
        //执行子View的measure()方法
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
}

由以上代码可知,子View的宽高由父View的测量规格和自己的LayoutParams共同决定。

其中子View的childWidthMeasureSpec通过getChildMeasureSpec()方法拿到,代码如下:

java 复制代码
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    /**
     * Does the hard part of measureChildren: figuring out the MeasureSpec to
     * pass to a particular child. This method figures out the right MeasureSpec
     * for one dimension (height or width) of one child view.
     *
     * The goal is to combine information from our MeasureSpec with the
     * LayoutParams of the child to get the best possible results. For example,
     * if the this view knows its size (because its MeasureSpec has a mode of
     * EXACTLY), and the child has indicated in its LayoutParams that it wants
     * to be the same size as the parent, the parent should ask the child to
     * layout given an exact size.
     *
     * @param spec The requirements for this view
     * @param padding The padding of this view for the current dimension and
     *        margins, if applicable
     * @param childDimension How big the child wants to be in the current
     *        dimension
     * @return a MeasureSpec integer for the child
     */
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // 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 (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 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的measure()方法,如果子View为普通的View,则执行View的onMeasure()方法,代码如下:

java 复制代码
public class View{

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

    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        ...
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }

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

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }


    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;
        //MeasureSpec.AT_MOST和MeasureSpec.EXACTLY都返回specSize
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

    protected int getSuggestedMinimumHeight() {
        //背景为null, 则返回mMinHeight;否则返回mMinHeight和mBackground.getMinimumHeight()中较大的一个
        return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
    }


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


}

setMeasuredDimension()方法最终调用了setMeasuredDimensionRaw()对mMeasuredWidth和mMeasuredHeight赋值,一旦这两个变量被赋值意味着该View的测量工作结束。

上面的代码中我们看到View的mMeasuredWidth和mMeasuredHeight通过getDefaultSize()方法来获取,在SpecMode为MeasureSpec.AT_MOST和MeasureSpec.EXACTLY时都返回了specSize,这就是为什么在自定义View中如果直接继承View并配置为wrap_content时实际效果是该自定义View把剩下的空间都占满了的原因。这种情况下我们需要重写View的onMeasure方法,代码如下:

java 复制代码
public class MyView extends View {

    public MyView(Context context) {
        this(context, null);
    }

    public MyView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //你可以自己给mWidth和mHeight赋值为实际包裹内容的宽高
        int mWidth = 100;
        int mHeight = 100;
        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 == MeasureSpec.AT_MOST) {
            setMeasuredDimension(mWidth, heightSpecSize);
        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(widthSpecSize, mHeight);
        }
    }

}

整个View视图是一个树结构的视图: measure过程就是从DecorView开始自顶向下遍历递归调用view的measure()方法(measure()中又调用onMeasure()方法)并最终对mMeasuredWidth和mMeasuredHeight赋值的过程。

接下来分析performLayout()方法如下:

java 复制代码
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
	...
   final View host = mView;
	...
	host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
	...
}

ViewGroup的layout()方法为final类型,不可以重写:

java 复制代码
public abstract class ViewGroup extends View implements ViewParent, ViewManager {

    @Override
    public final void layout(int l, int t, int r, int b) {
        ...
        super.layout(l, t, r, b);
        ...
    }
}

接着调用View的layout()方法:

java 复制代码
public class View{

    public void layout(int l, int t, int r, int b) {
        ......
        //实质都是调用setFrame方法把参数分别赋值给mLeft、mTop、mRight和mBottom这几个变量
        //判断View的位置是否发生过变化,以确定有没有必要对当前的View进行重新layout
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
        //需要重新layout
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            //调用onLayout()方法
            onLayout(changed, l, t, r, b);
            ......
        }
        ......
    }

    protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;

        ...

        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            changed = true;
            ...
            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            ...
        }
        return changed;
    }
}

View的onLayout()方法是一个空方法,ViewGroup的onLayout()方法是一个抽象方法,意味着ViewGroup的子类都必须重写这个方法。DecorView是FrameLayout的子类,接下来调用FrameLayout的onLayout()方法:

java 复制代码
public class FrameLayout extends ViewGroup {

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
      //确定子View的4个点在父View中的位置
      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();

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            ...
            child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
    }
}

一般情况下layout过程会参考measure过程中计算得到的mMeasuredWidth和mMeasuredHeight来安排子View在父View中显示的位置,但这不是必须的,measure过程得到的结果可能完全没有实际用处,特别是对于一些自定义的ViewGroup,其子View的个数、位置和大小都是固定的,这时候我们可以忽略整个measure过程,只在layout过程中传入的4个参数来安排每个子View的具体位置。

到这里就不得不提一下getWidth()、getHeight()和getMeasuredWidth()、getMeasuredHeight()这两对方法之间的区别,在View源码中这些方法的实现如下:

java 复制代码
public class View{

    public final int getMeasuredWidth() {
        return mMeasuredWidth & MEASURED_SIZE_MASK;
    }

    public final int getMeasuredHeight() {
         return mMeasuredHeight & MEASURED_SIZE_MASK;
    }

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

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

    public final int getLeft() {
        return mLeft;
    }

    public final int getRight() {
        return mRight;
    }

    public final int getTop() {
        return mTop;
    }

    public final int getBottom() {
        return mBottom;
    }
}

从这些方法的实现中可以看出:getMeasuredWidth()、getMeasuredHeight()必须在onMeasure()执行之后才有效;getWidth()与getHeight()方法必须在layout(int l, int t, int r, int b)执行之后才有效。

从上面的分析中可以看出,layout过程是从顶层父View向子View的递归调用view的layout()方法并最终确定mLeft、mTop、mRight、mBottom四个顶点的过程。

接下来分析performDraw()方法:

java 复制代码
private void performDraw() {
   ...
	boolean canUseAsync = draw(fullRedrawNeeded);
   ...
}


private boolean draw(boolean fullRedrawNeeded) {

	...
	mAttachInfo.mThreadedRenderer.draw()  //硬件加速绘制
	...
	drawSoftware()  //软件绘制
	...
}

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

接下来调用View的draw()方法:

java 复制代码
public class View{

    public void draw(Canvas canvas) {
        /*
         * 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)
         *      7. If necessary, draw the default focus highlight
         */

        // Step 1, draw the background, if needed
        // 步骤1.绘制背景
        int saveCount;

        drawBackground(canvas);

        // skip step 2 & 5 if possible (common case)
        // 步骤2.如果需要的话,保存canvas的图层,为fading做准备
        // 步骤5.如果需要的话,绘制fading edges和恢复图层
        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.绘制View的内容
            onDraw(canvas);

            // Step 4, draw the children
            // 步骤4.绘制子View
            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.绘制View的装饰 (foreground, scrollbars)
            onDrawForeground(canvas);

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

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

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

        ...
    }
}

View的onDraw()方法是一个空方法,说明View默认不会绘制任何内容,真正的绘制都需要自己在子类中实现。在ViewGroup中对dispatchDraw()方法进行了重写,代码如下:

java 复制代码
public abstract class ViewGroup extends View implements ViewParent, ViewManager {

    protected void dispatchDraw(Canvas canvas) {
        ...
        final int childrenCount = mChildrenCount;
        final View[] children = mChildren;
        ...
        for (int i = 0; i < childrenCount; i++) {
            while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
                final View transientChild = mTransientViews.get(transientIndex);
                if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                        transientChild.getAnimation() != null) {
                    //调用drawChild()方法
                    more |= drawChild(canvas, transientChild, drawingTime);
                }
            }

        }
        ...
        // Draw any disappearing views that have animations
        if (mDisappearingChildren != null) {
            ...
            for (int i = disappearingCount; i >= 0; i--) {
                final View child = disappearingChildren.get(i);
                more |= drawChild(canvas, child, drawingTime);
            }
        }
        ...
    }

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

dispatchDraw()方法里面遍历每个子View,调用了子View的draw()方法。

相关推荐
理想不理想v7 分钟前
vue经典前端面试题
前端·javascript·vue.js
不收藏找不到我8 分钟前
浏览器交互事件汇总
前端·交互
YBN娜22 分钟前
Vue实现登录功能
前端·javascript·vue.js
阳光开朗大男孩 = ̄ω ̄=22 分钟前
CSS——选择器、PxCook软件、盒子模型
前端·javascript·css
minDuck27 分钟前
ruoyi-vue集成tianai-captcha验证码
java·前端·vue.js
小政爱学习!1 小时前
封装axios、环境变量、api解耦、解决跨域、全局组件注入
开发语言·前端·javascript
魏大帅。1 小时前
Axios 的 responseType 属性详解及 Blob 与 ArrayBuffer 解析
前端·javascript·ajax
花花鱼1 小时前
vue3 基于element-plus进行的一个可拖动改变导航与内容区域大小的简单方法
前端·javascript·elementui
k09331 小时前
sourceTree回滚版本到某次提交
开发语言·前端·javascript
EricWang13581 小时前
[OS] 项目三-2-proc.c: exit(int status)
服务器·c语言·前端