【view的绘制流程】布局

四、布局

上上篇文章中的第二节,绘制入口那里得知,View视图绘制流程中的【布局】是由ViewRootImpl中的performLayout方法开始的。我们看看这个方法的源码:

ViewRootImpl类:
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        ..................
        //标记当前开始布局
        mInLayout = true;
        //mView就是DecorView
        final View host = mView;
        ..................
        //DecorView请求布局
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        //标记布局结束
        mInLayout = false;
        ..................
}

host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());这行代码,把DecorView的四个位置左=0,顶=0,右=屏幕宽,底=屏幕高传了进来,说明DecorView布局的位置是充满整个屏幕的。

我们进入layout方法看看:

View类:
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;
        }
        //保存上一次View的四个位置
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
        //设置当前视图View的左,顶,右,底的位置,并且判断布局是否有改变
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
        //如果布局有改变,则视图View重新布局
            if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            //调用onLayout,将具体布局逻辑留给子类实现
            onLayout(changed, l, t, r, b);
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

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

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}

方法中保存了上次view布局的四个位置,用来和新的布局进行对比,如果布局有变化,那就对视图view重新测量。

setFrame方法是用来设置当前View的布局位置的,也就是说,当调用了setFrame(l, t, r, b)方法之后,当前View布局基本完成。下面我们看看setFrame方法:

View类:
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;
            int drawn = mPrivateFlags & PFLAG_DRAWN;
            //得到本次和上次的宽和高
            int oldWidth = mRight - mLeft;
            int oldHeight = mBottom - mTop;
            int newWidth = right - left;
            int newHeight = bottom - top;
            //判断本次View的宽高和上次View的宽高是否相等
            boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

            //清楚上次布局的位置
            invalidate(sizeChanged);
            //保存当前View的最新位置
            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);

            mPrivateFlags |= PFLAG_HAS_BOUNDS;

            //如果当前View的尺寸有所变化
            if (sizeChanged) {
                sizeChange(newWidth, newHeight, oldWidth, oldHeight);
            }

            ...............
        return changed;
}

如果当前View视图的最新位置和上一次不一样时,则View会重新布局。保存当前View的最新位置,到此当前View的布局基本结束。从这里我们可以看到,四个全局变量 mLeft,mTop,mRight,mBottom在此刻赋值,联想我们平时使用的View.getWidth()方法获得View的宽高,可以发现,其实View.getWidth()方法的实现如下:

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

也就是说,以上两个方法是获得View布局时候的宽高,因此,我们只有在View 布局完之后调用getWidth才能真正获取到大于0的值。

在View.layout方法中,还调用了onLayout方法,这是一个空方法,既然是空方法,那么该方法的实现应该在子类中。前面分析过,DecorView是继承自FrameLayout的,那么进入FarmeLayout类中看看 onLayout方法的实现吧:

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

void layoutChildren(int left, int top, int right, int bottom,boolean forceLeftGravity) {
		//获取子view的数量
        final int count = getChildCount();

        final int parentLeft = getPaddingLeftWithForeground();
        final int parentRight = right - left - getPaddingRightWithForeground();
        final int parentTop = getPaddingTopWithForeground();
        final int parentBottom = bottom - top - getPaddingBottomWithForeground();

        mForegroundBoundsChanged = true;
        //遍历当前FrameLayout下的子View
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            //当子视图View可见度设置为GONE时,不进行当前子视图View的布局,这就是为什么当你布局中使用Visibility=GONE时,该view是不占据空间的。
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                //获得子视图View的测量的宽高
                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;
                //获得子视图View的四个位置,用于子视图View布局。
                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);
            }
        }
}

在这个方法中,通过传进来的父view的布局位置以及每个子view的测量宽高,计算出每个子view的布局位置。在onLayout方法调用了layoutChildren方法,在这个方法,会遍历viewGroup中的子view,

当子视图View可见度设置为GONE时,不进行当前子视图View的布局,这就是为什么当你布局中使用Visibility=GONE时,该view是不占据空间的。

如果不为GONE,那就获取每个子view的测量的宽和高,然后在计算得到子View的四个位置,用于子View布局。每个子view会再调用view.layout方法

总结如下:

布局的开始是在performTraversals方法的performLayout方法开始的,

performLayout方法中,执行host.layout方法,同时将屏幕的上下左右四个顶点作为入参,host则是代表DecorView,在layout方法中,会先执行setFrame(l,t,r,b)方法确定自己的布局位置,

同时,mRight/mLeft/mBottom/mTop也会在setFrame方法中被赋值,也就意味着getWidth()/getHeight()方法此后调用将不会为0。

执行了setFrame之后,就会继续执行onLayout方法,onLayout是ViewGroup的抽象方法,需要ViewGroup的子类来实现,

所以这里仍以FrameLayout为例,FrameLayout.onLayout方法又会调用layoutChildren方法,通过传进来的父view的布局位置以及每个子view的测量宽高,计算出每个子view的布局位置。最后再递归调用子View的view.layout方法,当布局完最底层的子View,就会回溯最外层View的onLayout方法,此时就代表所有View的布局已经结束了。

我们的布局流程可以用如下流程表示:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一场雪ycx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值