之前的文章《View体系(六)View工作流程入口》 提到View
的工作流程包括了measure
、layout
和draw
的过程,上两篇文章《View体系(八)深入剖析View的onMeasure方法》 和《View体系(九)从LinearLayout分析ViewGroup的测量流程》 分别对View
和ViewGroup
的measure
过程做了分析,今天我们就来看一下View
的layout
过程是怎样的。
(注:文中源码基于 Android 12
)
先看View
的layout
方法:
1 2 3 4 5 6 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); ... onLayout(changed, l, t, r, b);
layout
方法很长,这里只贴出关键代码。layout
的4个参数l、t、r、b
分别代表View
从左上右下相对父容器的距离。注释1处根据不同情况会调用setOpticalFrame
或setFrame
方法,而在setOpticalFrame
方法内部也会调用到setFrame
,所以我们直接看setFrame
做了什么,进入setFrame
方法:
1 2 3 4 5 6 7 8 9 10 11 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
方法:
1 2 protected void onLayout (boolean changed, int left, int top, int right, int bottom) {}
onLayout
是一个空方法,这样设计和ViewGroup
中没有onMeasure
方法类似,确定位置时不同的控件有不同的实现,所以onLayout
需要由具体的控件自己来实现如何布局。所以在View
和ViewGroup
中都没有实现onLayout
方法,我们还是以LinearLayout
为例来分析onLayout
方法,进入LinearLayout
的onLayout
方法:
1 2 3 4 5 6 7 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); } }
和测量流程一样,这里根据mOrientation
进行不同的布局流程,以纵向布局为例,会进入注释1处layoutVertical
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 void layoutVertical (int left, int top, int right, int bottom) { ... final int count = getVirtualChildCount(); 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); } } }
注释1处获取子View
的数量,注释2处循环遍历子View
并调用注释3处的setChildFrame
方法确定子View
的位置,注释4处对childTop
不断进行累加,这样子View
才会依次按垂直方向一个接一个的排列下去,而不是堆叠在一起,接着看注释3处的setChildFrame
方法:
1 2 3 private void setChildFrame (View child, int left, int top, int width, int height) { child.layout(left, top, left + width, top + height); }
其内部调用了子View
的layout
方法来确定自己的位置,layout
方法文章开头已经讲过,这里不再赘述。 至此,就完成了LinearLayout
的layout
过程。
关注我