可以看到,View的layout函数其实并没有执行多少实际的布局操作,而是负责一些状态的更新以及本view的坐标设置,使用setFrame()函数将父容器传来的坐标应用到了自己的视图。然后调用了onLayout(changed, l, t, r, b),接下来就可以去看FrameLayout的onLayout()函数了。需要注意的是,l、t、r、b都是相对于父view的位置,而不是在屏幕中的绝对位置,这点在注释里也说明了。
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { layoutChildren(left, top, right, bottom, false /* no force left gravity */); }很简单,直接调用了layoutChildren,并且比原来的参数多了个布尔值。其中changed这个参数需要说明一下,这是布局根据自己原来的位置和之后layout函数中传来的新值做比较来发现自己的大小和位置是否改变,如果改变则changed为真,否则为假。这也可以用来决定什么时候对子view进行layout操作,避免频繁进行不必要的layout浪费资源。而验证是否changed这项工作是有View类的layout函数中进行的,因此不必我们担心。
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) { final int count = getChildCount(); /** * 获取父layout的上下左右位置,这里的父layout是指FrameLayout本身 * */ final int parentLeft = getPaddingLeftWithForeground(); final int parentRight = right - left - getPaddingRightWithForeground(); final int parentTop = getPaddingTopWithForeground(); final int parentBottom = bottom - top - getPaddingBottomWithForeground(); mForegroundBoundsChanged = true; /** * 依次测量每个子view,并且考虑子view的gravity * */ for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) child.getLayoutParams(); 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(); /** * 根据gravity和布局方向来确定横向和纵向的绝对gravity,以此来决定子view的左边和上边的位置。如果熟悉开发者选项, * 会发现其中有一个选项是“强制从右到左的布局”,并且FrameLayout也有android:layoutDirection属性,可以设置为继承(inherit)、 * 本地(local)、从左到右(ltr)、从右到左(rtl)四个选项,但是没有设置上下方向的,毕竟没有哪个文化的阅读习惯是上下颠倒的。因此 * 左右的gravity需要结合布局方向,但是上下布局只需要解析view自己的gravity设置即可,而不需要direction。另外absoluteGravity * 是按照从左到右的,无论开发者选项里怎么设置。 * */ final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; /** * 计算子view的left位置 * */ switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: /** * 如果绝对gravity为横向居中,下面的计算显而易见是为了让子view横向居中的,并且考虑了margin值 * */ childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: /** * 如果不是强制gravity为左,那么还要计算gravity为右的情况 * */ if (!forceLeftGravity) { childLeft = parentRight - width - lp.rightMargin; break; } case Gravity.LEFT: default: childLeft = parentLeft + lp.leftMargin; } /** * 计算子view的top位置 * */ 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; } /** * 调用子view的layout函数,传入计算好的子view的left、top、right、bottom值 * */ child.layout(childLeft, childTop, childLeft + width, childTop + height); } } }