浅析Android View的Layout过程

Layout过程是View的三大过程之一,它负责确定一个View和它的子元素的最终位置。与measure过程类似,layout过程也分为layout()和onLayout()两个核心方法。layout负责确定view本身的位置,而layout主要负责确定所有子元素的位置。首先让我们来看看layout方法:

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);
            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;
    }
layout方法的核心思想就是通过setFrame来确定了view的上下左右四个顶点,并存入mTop,mBottom,mLeft,mLeft中。四个顶点一确定,view本身的大小位置就确定了,同时,layout方法还会调用onLayout来对子view进行布局。不同的是,View本身并没有定义layout方法,ViewGroup更是将layout方法定义为抽象,不同的布局需要自己定义onLayout方法,来确定子元素的摆放位置。

我们先来简单看来RelativeLayout的onLayout方法:

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //  The layout has actually already been performed and the positions
        //  cached.  Apply the cached values to the children.
        final int count = getChildCount();

        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                RelativeLayout.LayoutParams st =
                        (RelativeLayout.LayoutParams) child.getLayoutParams();
                child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
            }
        }
    }
代码不能再简单,RelativeLayout就是得到子view的LayoutParam,循环调用子View的layout方法,将他们堆叠在自己的左上角的位置(左上角位置为0,0)。

而LinearLayout的Layout方法就复杂些,它根据orientation的不同,分别对水平和竖直进行了单独处理:

 @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mOrientation == VERTICAL) {
            layoutVertical(l, t, r, b);
        } else {
            layoutHorizontal(l, t, r, b);
        }
    }
这里举竖直方向为例:可以看到,该方法也是循环遍历子元素并确定它们的位置,其中childTop会逐渐增大,这样一层一层的元素就被罗列在竖直方向上:

void layoutVertical(int left, int top, int right, int bottom) {
        final int paddingLeft = mPaddingLeft;

        int childTop;
        int childLeft;
        
        // Where right end of child should go
        final int width = right - left;
        int childRight = width - mPaddingRight;
        
        // Space available for child
        int childSpace = width - paddingLeft - mPaddingRight;
        
        final int count = getVirtualChildCount();

        final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
        final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;

        switch (majorGravity) {
           case Gravity.BOTTOM:
               // mTotalLength contains the padding already
               childTop = mPaddingTop + bottom - top - mTotalLength;
               break;

               // mTotalLength contains the padding already
           case Gravity.CENTER_VERTICAL:
               childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
               break;

           case Gravity.TOP:
           default:
               childTop = mPaddingTop;
               break;
        }

        for (int i = 0; i < count; i++) {
            final View child = getVirtualChildAt(i);
            if (child == null) {
                childTop += measureNullChild(i);
            } else if (child.getVisibility() != GONE) {
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();
                
                final LinearLayout.LayoutParams lp =
                        (LinearLayout.LayoutParams) child.getLayoutParams();
                
                int gravity = lp.gravity;
                if (gravity < 0) {
                    gravity = minorGravity;
                }
                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                                + lp.leftMargin - lp.rightMargin;
                        break;

                    case Gravity.RIGHT:
                        childLeft = childRight - childWidth - lp.rightMargin;
                        break;

                    case Gravity.LEFT:
                    default:
                        childLeft = paddingLeft + lp.leftMargin;
                        break;
                }

                if (hasDividerBeforeChildAt(i)) {
                    childTop += mDividerHeight;
                }

                childTop += lp.topMargin;
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

                i += getChildrenSkipCount(child, i);
            }
        }
    }

经过 《浅析Android View的Measure过程》和《浅析Android View的Layout过程》两篇博客的分析,我们可以得出 mMeasureWidth(Height) 和mWidth(Height)的不同:

mMeasureWidth和mMeasureHeight在measure过程中被实例化,而mHeight = mBottom - mTop, mWidth = mRight - mLeft, 它们都在layout过程中才被实例化。也就是说,当layout过程结束后,mMeasureWidth和mWidth,mMeasureHeight和mHeight是完全相同的。但当我们重写ViewGroup的onLayout方法时,想要获取子View的宽高来确定父View的宽高时,必须要使用mMeasureWIdth或mMeasureHeight了,因为此时还没有调用子view的layout过程。

这里有个小例子提供给大家:我自己定义了一个Viewgroup,它继承了HorizonScrollView,内部包含了一个LinearLayout,当在layout方法时获取本View的width、measuredWidth,子view的width,子view的measuredWidth时打印出如下结果:

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        Log.v("lzq","this view measure width" + getMeasuredWidth());
        Log.v("lzq","this view width" + getWidth());
        Log.v("lzq","child view width " + mLinearLayout.getWidth());
        Log.v("lzq","child view measured width " + mLinearLayout.getMeasuredWidth());
    }

V/lzq: this measure width1080

V/lzq: this width1080

V/lzq: this child width 0

V/lzq: this child measured width 2250


我们可以看到,子View的width为0,证明在layout方法中,没有完成子view的layout过程,子view的measureWidth被初始化了是因为子view已经完成了measure过程。父view的mesureWidth和width都有值是因为已经完成了父view的measure和layout过程(super.onLayout)。

总结:layout过程执行在measure过程之后,负责确定本View和子View的位置,并在其中实例化上下左右四个顶点(mTop,mBottom,mLeft,mRight),重写ViewGroup的onLayout方法可以自定义子View在该ViewGroup中的位置布局。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值