View 的 layout 流程-再总结

看图说话

流程图

借用该链接:凶残的程序员-View 的工作流程 的一张图,来表示大致的工作流程。


时序图

来一张时序图:

ViewRootImpl DecorView ViewGroup View FrameLayout 0,performTraversals 1,performLayout 2,host.layout(..) 没有重写,直调父 3,super.layout 通知了观察者 4,super.layout setFrame 设置自身布局 invalidate(sizeChanged) 触发重绘 5,onLayout 6,super.onLayout(...) 7,layoutChildren( ... ) 8,child.layout(width, height) 子view 开始 layout loop [ 测量每个子 view ] other work ViewRootImpl DecorView ViewGroup View FrameLayout

表格

表格对比

layoutonLayoutlayoutonLayout
ViewGroupsuper.layout; 通知观察者nullView记录位置, 调onLayoutnull
ViewGroup子类null根据自身布局的 特点来确定布局, for循环 :调用 child.layoutView子类nullsuper.onLayout; 拓展自己的功能

简单源码梳理

第0步 performTraversals

  • 0,ViewRootImpl#performTraversals 源码
// 代码段0:
// ViewRootImpl 类

private void performTraversals() {
    // cache mView since it is used so much below...
    final View host = mView;

    WindowManager.LayoutParams lp = mWindowAttributes;
    Rect frame = mWinFrame;

    if (mWidth != frame.width() || mHeight != frame.height()) {
        mWidth = frame.width();
        mHeight = frame.height();
    }

	final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
	if (didLayout) {
        performLayout(lp, mWidth, mHeight);
        ...

第1步 performLayout

  • 1,ViewRootImpl#performLayout
// 源码段1:
// ViewRootImpl 类

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        mLayoutRequested = false;
        mScrollMayChange = true;
        mInLayout = true;

        final View host = mView;

        try {
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
            ...

比较简单的调用了 DecorView 的 layou 函数,流程继续。


第2步 DecorView#layou

  • 2,DecorView#layou

没有重写 layou 函数,直接使用父类 ViewGroup 的 layou 函数…


第3步 ViewGroup#layou

  • 3,ViewGroup#layou
// 源码段2:
// ViewGroup 类

    @Override
    public final void layout(int l, int t, int r, int b) {
        if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
            if (mTransition != null) {
                mTransition.layoutChange(this);
            }
            super.layout(l, t, r, b);
        } else {
            // record the fact that we noop'd it; request layout when transition finishes
            mLayoutCalledWhileSuppressed = true;
        }
    }

通知了观察者,然后调用 super.layout(l, t, r, b);即 View#layou 函数。


第4步 View#layou

  • 4,View#layou
// 源码段3:
// View类

    /**
     * Assign a size and position to a view and all of its
     * descendants
     *
     *
     * @param l Left position, relative to parent
     * @param t Top position, relative to parent
     * @param r Right position, relative to parent
     * @param b Bottom position, relative to parent
     */
    @SuppressWarnings({"unchecked"})
    public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);

	    // 。。。
	    
            // 通知观察者
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

    }

在 View#layou 函数的开头就非常直接的调用 setFrame 函数,设置布局的四个关键数值:mLeft,mTop,mBottom,mRight。setOpticalFrame 函数本质也是调用 setFrame 函数。

View#setFrame 源码

// 代码段3.1:
// View类

    /**
     * Assign a size and position to this view.
     *
     * This is called from layout.
     *
     * @param left Left position, relative to parent
     * @param top Top position, relative to parent
     * @param right Right position, relative to parent
     * @param bottom Bottom position, relative to parent
     * @return true if the new size and position are different than the
     *         previous ones
     * {@hide}
     */
    protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;

        if (DBG) {
            Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
                    + right + "," + bottom + ")");
        }

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

            // Remember our drawn bit
            int drawn = mPrivateFlags & PFLAG_DRAWN;

            int oldWidth = mRight - mLeft;
            int oldHeight = mBottom - mTop;
            int newWidth = right - left;
            int newHeight = bottom - top;
            boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

            // Invalidate our old position
            invalidate(sizeChanged); // 触发重绘

            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;

这四个入参,表示子控件对于父控件四个边的距离,在 View 类中用四个变量保存:mLeft,mTop,mBottom,mRight。

后续的流程,比如 onDraw 函数中,可以通过 getLeft()、getTop()、getBottom()、getRight() 这四个函数获取;

    public final int getLeft() {
        return mLeft;
    }
    
    public final int getRight() {
        return mRight;
    }
    
    ...
    
    public final int getWidth() {
        return mRight - mLeft;
    }
    
    public final int getHeight() {
        return mBottom - mTop;
    }

再回到 View#layou 函数,执行完 setFrame 函数后,表示布局已经确定,接着调用 onLayout 函数,流程继续。


第5步 DecorView#onLayout

  • 5,DecorView#onLayout
// 代码段4
// DecorView 类

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        // 即调用 FrameLayout 的 onLayout 函数
        super.onLayout(changed, left, top, right, bottom); 
        
        // 其他一些处理逻辑
        getOutsets(mOutsets);
        if (mOutsets.left > 0) {
            offsetLeftAndRight(-mOutsets.left);
        }
        if (mOutsets.top > 0) {
            offsetTopAndBottom(-mOutsets.top);
        }
        if (mApplyFloatingVerticalInsets) {
            offsetTopAndBottom(mFloatingInsets.top);
        }
        if (mApplyFloatingHorizontalInsets) {
            offsetLeftAndRight(mFloatingInsets.left);
        }

        // If the application changed its SystemUI metrics, we might also have to adapt
        // our shadow elevation.
        updateElevation();
        mAllowUpdateElevation = true;

        if (changed && mResizeMode == RESIZE_MODE_DOCKED_DIVIDER) {
            getViewRootImpl().requestInvalidateRootRenderNode();
        }
    }

开头处即调用求父类 FrameLayout 的 onLayout 函数…


第6步 FrameLayout#onLayout

  • 6,FrameLayout#onLayout
代码段5:
//  FrameLayout 类

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }

第7步 FrameLayout#layoutChildren

  • 7,FrameLayout#layoutChildren
// 代码段6:
//  FrameLayout 类

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

                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();

                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;
                }

                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;
                }

                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }

layoutChildren 函数中,逐个计算好 子View 的布局,在调用 child.layout 触发 子 View 的布局流程。


第8步 child.layout

  • 8,child.layout

从这一步开始,后续布局流程的 普通 ViewGroup的子类 和 普通 View 的子类 的 layout 和 onLayout 流程的大致相当的,都是调用 View#layout – setFrame 设定自己的布局,ViewGroup的子类在 onLayout 函数中计算好 子View 的布局,并触发子 View 的布局流程。在这过程,ViewGroup的子类可能会调用到父类的一些函数。


总结

从 layout 这个流程看下来,发现它是一个先测自己,再测 子view 的流程;

从第8步开始的 child.layout,child 也就是 DecorView 的第一个 子View,即系统布局 R.layout.screen_title 或 R.layout.screen_simple 或 其他类似的,统一放在源码工程的 frameworks/base/core/res/res/layout/ 路径下;

R.layout.screen_title 源码R.layout.screen_simple 源码

因为 R.layout.screen_title 这类的布局文件是 ViewGroup 类型的,所以第8步 child 就是它们的 根view:LinearLayout

其实,这整个流程看下来,只是对 layout 流程有一个大概的了解,具体的View 子类和 ViewGroup 子类的 onLayout 函数都有自己的实现,需要去具体分析…


链接汇总

R.layout.screen_title 源码

R.layout.screen_simple 源码

ViewRootImpl 源码

DecorView 源码

FrameLayout 源码

View 源码

ViewGroup 源码

ViewStub extends View

LinearLayout 源码

TextView 源码

查源码的链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值