我们在布局中经常使用Linearlayout,Relativelayout和FrameLayout这三个容器。在使用中会发现这三个容器在对子组件排版时会有所不同。今天带领大家从源码层面简要分析下为什么会不同。
首先,大家要掌握一个基础知识,即ViewGroup的绘制流程
首先:要度量子组件的大小(onMeasure)
其次,要将位置确定好(onLayout)
最后,在屏幕上进行绘制(draw);
而我们要分析的这三个组件的不同之处就在于里面确定位置这个方法的不同,即在onLayout方法里面的不同。
首先看Linearlayout:
源码如下

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

Linearlayout中有横向和纵向布局;纵向布局代码如下:

 void layoutVertical(int left, int top, int right, int bottom) {
        final int paddingLeft = mPaddingLeft;
        //布局开始顶部
        int childTop;
        //布局开始的左边
        int childLeft;
        //整体布局的宽度
        final int width = right - left;
        //子组件的右边终点
        int childRight = width - mPaddingRight;
        //子组件的真正可用的宽度
        int childSpace = width - paddingLeft - mPaddingRight;
        //子组件的数量
        final int count = getVirtualChildCount();
        //本人感觉对应的是 android:layout_gravity属性,即自己在父容器的位置
        final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
        //本人感觉对应的是android:gravity,即子组件的默认位置
        final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
        //首先确认自己的位置
        switch (majorGravity) {
        //在父容器的底部
        case Gravity.BOTTOM:
        // 我的布局开始的顶部=我距离顶部的距离+父容器的高度-我自身的高度
               childTop = mPaddingTop + bottom - top - mTotalLength;
               break;
           // 在父容器的中间
           case Gravity.CENTER_VERTICAL:
           //顶部起点=我距离顶部的距离+(父容器高度-我的高度)除以二
               childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
               break;
           //默认或者设置在父容器的顶部
           case Gravity.TOP:
           default:
               //顶部起点=我设置的距离顶部的距离
               childTop = mPaddingTop;
               break;
        }
        //上面的case说明,纵向行排版时,自己的位置属性在顶部,底部,还有垂直居中下才有效Lin
        for (int i = 0; i < count; i++) {
            //分别对每一个子组件进行排版
            final View child = getVirtualChildAt(i);
            if (child == null) {
                childTop += measureNullChild(i);
            } else //当子组件属性不是View.Gone时
            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;
                }
                //根据上面三个case分析,在Linearlayout纵向排版的时候,子组件的位置属性只有水平居中,居左,居右三个有效。
                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);
            }
        }
    }

在看看横向排版的时候:

 void layoutHorizontal(int left, int top, int right, int bottom) {
        final boolean isLayoutRtl = isLayoutRtl();
        final int paddingTop = mPaddingTop;

        int childTop;
        int childLeft;

        // Where bottom of child should go
        final int height = bottom - top;
        int childBottom = height - mPaddingBottom; 

        // Space available for child
        int childSpace = height - paddingTop - mPaddingBottom;

        final int count = getVirtualChildCount();

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

        final boolean baselineAligned = mBaselineAligned;

        final int[] maxAscent = mMaxAscent;
        final int[] maxDescent = mMaxDescent;

        final int layoutDirection = getLayoutDirection();
        switch (Gravity.getAbsoluteGravity(majorGravity, layoutDirection)) {
            case Gravity.RIGHT:
                // mTotalLength contains the padding already
                childLeft = mPaddingLeft + right - left - mTotalLength;
                break;

            case Gravity.CENTER_HORIZONTAL:
                // mTotalLength contains the padding already
                childLeft = mPaddingLeft + (right - left - mTotalLength) / 2;
                break;

            case Gravity.LEFT:
            default:
                childLeft = mPaddingLeft;
                break;
        }
        //上面的case说明,横行排版时,自己的位置属性在左,右,还有水平居中下才有效
        int start = 0;
        int dir = 1;
        //In case of RTL, start drawing from the last child.
        if (isLayoutRtl) {
            start = count - 1;
            dir = -1;
        }

        for (int i = 0; i < count; i++) {
            int childIndex = start + dir * i;
            final View child = getVirtualChildAt(childIndex);

            if (child == null) {
                childLeft += measureNullChild(childIndex);
            } else if (child.getVisibility() != GONE) {
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();
                int childBaseline = -1;

                final LinearLayout.LayoutParams lp =
                        (LinearLayout.LayoutParams) child.getLayoutParams();

                if (baselineAligned && lp.height != LayoutParams.MATCH_PARENT) {
                    childBaseline = child.getBaseline();
                }

                int gravity = lp.gravity;
                if (gravity < 0) {
                    gravity = minorGravity;
                }

                switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
                    case Gravity.TOP:
                        childTop = paddingTop + lp.topMargin;
                        if (childBaseline != -1) {
                            childTop += maxAscent[INDEX_TOP] - childBaseline;
                        }
                        break;

                    case Gravity.CENTER_VERTICAL:
                                               childTop = paddingTop + ((childSpace - childHeight) / 2)
                                + lp.topMargin - lp.bottomMargin;
                        break;

                    case Gravity.BOTTOM:
                        childTop = childBottom - childHeight - lp.bottomMargin;
                        if (childBaseline != -1) {
                            int descent = child.getMeasuredHeight() - childBaseline;
                            childTop -= (maxDescent[INDEX_BOTTOM] - descent);
                        }
                        break;
                    default:
                        childTop = paddingTop;
                        break;
                }
                //以上case说明,在横向时,只有纵向居中,居顶,居底有效
                if (hasDividerBeforeChildAt(childIndex)) {
                    childLeft += mDividerWidth;
                }
                //横向排版是对宽度和左边起点进行累计
                childLeft += lp.leftMargin;
                setChildFrame(child, childLeft + getLocationOffset(child), childTop,
                        childWidth, childHeight);
                childLeft += childWidth + lp.rightMargin +
                        getNextLocationOffset(child);

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

从上面的分析可知Linearlayout的排版方式是怎么实现的了,并且可以获取到以下信息:
一:layout_gravity指的是自身在父容器的位置;gravity指的是子组件默认在父容器的位置。当自身的layout_gravity和父容器对自己默认的gravity冲突时,优先选择自身的layout_gravity;
二:
当android:orientation=”horizontal”时
自身的:layout_gravity属性有效取值为:
right,,left,center_horizontal有效;
子组件的layout_gravity有效取值为:
top,bottom,center_vertical
当android:orientation=”vertical”时
自身的layout_gravity属性有效取值为:
top,buttom,center_vertical
子组件的layout_gravity有效取值为:
right,,left,center_horizontal

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐