View体系(十)从LinearLayout分析View的布局流程

208 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

之前的文章《View体系(六)View工作流程入口》提到View的工作流程包括了measurelayoutdraw的过程,上两篇文章《View体系(八)深入剖析View的onMeasure方法》《View体系(九)从LinearLayout分析ViewGroup的测量流程》分别对ViewViewGroupmeasure过程做了分析,今天我们就来看一下Viewlayout过程是怎样的。

(注:文中源码基于 Android 12

先看Viewlayout方法:

public void layout(int l, int t, int r, int b) {
    ...
    boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);//1
    ...
    onLayout(changed, l, t, r, b);//2

layout方法很长,这里只贴出关键代码。layout的4个参数l、t、r、b分别代表View从左上右下相对父容器的距离。注释1处根据不同情况会调用setOpticalFramesetFrame方法,而在setOpticalFrame方法内部也会调用到setFrame,所以我们直接看setFrame做了什么,进入setFrame方法:

/**
* Assign a size and position to this view.
*/
protected boolean setFrame(int left, int top, int right, int bottom) {
            ...
            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            ...
    }

从注释(Assign a size and position to this view.)可以看出,这个方法是为View分配了大小和位置。setFrame将传进来的4个参数分别初始化mLeft、mTop、mRight、mBottom这4个值,这样就确定了该View在父容器的位置。

接着看上段代码注释2处,可以看到layout内部调用了onLayout方法:

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }

onLayout是一个空方法,这样设计和ViewGroup中没有onMeasure方法类似,确定位置时不同的控件有不同的实现,所以onLayout需要由具体的控件自己来实现如何布局。所以在ViewViewGroup中都没有实现onLayout方法,我们还是以LinearLayout为例来分析onLayout方法,进入LinearLayoutonLayout方法:

    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mOrientation == VERTICAL) {
            layoutVertical(l, t, r, b);//1
        } else {
            layoutHorizontal(l, t, r, b);
        }
    }

和测量流程一样,这里根据mOrientation进行不同的布局流程,以纵向布局为例,会进入注释1处layoutVertical方法:

    void layoutVertical(int left, int top, int right, int bottom) {
        ...
        final int count = getVirtualChildCount();   //1
        for (int i = 0; i < count; i++) {   //2
            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);   //3
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);   //4

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

注释1处获取子View的数量,注释2处循环遍历子View 并调用注释3处的setChildFrame方法确定子View的位置,注释4处对childTop不断进行累加,这样子View才会依次按垂直方向一个接一个的排列下去,而不是堆叠在一起,接着看注释3处的setChildFrame方法:

    private void setChildFrame(View child, int left, int top, int width, int height) {
        child.layout(left, top, left + width, top + height);
    }

其内部调用了子Viewlayout方法来确定自己的位置,layout方法文章开头已经讲过,这里不再赘述。 至此,就完成了LinearLayoutlayout过程。

学习更多知识,请关注我的个人博客:droidYu