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()方法。

相关推荐
码事漫谈11 分钟前
解决 Anki 启动器下载错误的完整指南
前端
im_AMBER31 分钟前
Web 开发 27
前端·javascript·笔记·后端·学习·web
蓝胖子的多啦A梦1 小时前
低版本Chrome导致弹框无法滚动的解决方案
前端·css·html·chrome浏览器·版本不同造成问题·弹框页面无法滚动
玩代码1 小时前
vue项目安装chromedriver超时解决办法
前端·javascript·vue.js
訾博ZiBo1 小时前
React 状态管理中的循环更新陷阱与解决方案
前端
StarPrayers.1 小时前
旅行商问题(TSP)(2)(heuristics.py)(TSP 的两种贪心启发式算法实现)
前端·人工智能·python·算法·pycharm·启发式算法
一壶浊酒..2 小时前
ajax局部更新
前端·ajax·okhttp
DoraBigHead3 小时前
React 架构重生记:从递归地狱到时间切片
前端·javascript·react.js
彩旗工作室3 小时前
WordPress 本地开发环境完全指南:从零开始理解 Local by Flywhee
前端·wordpress·网站
iuuia3 小时前
02--CSS基础
前端·css