背景
在文章Android开发学习之线性布局测量流程源码阅读里,我以垂直方向为例记录了阅读线性布局的测量过程,其间主要是测量子view的高度,必要的话考虑权重分配,保存总高度和最大宽度做为当前布局的尺寸,最后强制把宽度为match_parent的子view的宽度设为当前view的宽度。
现在,我接着记录对线性布局的onLayout()方法的阅读过程
onLayout
代码如下
@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);
}
}
还是以垂直方向为例,进行阅读
layoutVertical
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; // 子view最大宽度
int childRight = width - mPaddingRight; // 子view最右边
// Space available for child
int childSpace = width - paddingLeft - mPaddingRight; // 子view最大可用空间
final int count = getVirtualChildCount();
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; // 垂直方向的绘制,以垂直方向的重心为主,不过并没有什么实际用处
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; // 横向重心为辅
// 从ViewRootImpl.performLayout()方法传进来的参数里,left和top都是0,bottom是measuredHeight,right是measuredWidth
// 所以下面三种情况,最后的结果都是childTop = mPaddingTop
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); // 0
} 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; // 子view的重心
if (gravity < 0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection(); // 当前布局的展示方向,从左往右还是从右往左
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); // 水平方向上的重心
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
// 根据子view水平方向上的重心,设置它的左端点
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)) { // 有分割线,top往下移
childTop += mDividerHeight;
}
childTop += lp.topMargin; // 算上外间距
setChildFrame(child, childLeft, childTop + getLocationOffset(child), // getLocationOffset()和getNextLocationOffset()方法都返回0
childWidth, childHeight); // 调用child.layout()方法
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
// 更新子view的高度
i += getChildrenSkipCount(child, i); // 0
}
}
}
也很简单,同时解释了为何垂直方向的LinearLayout里,设置子view的垂直重心不顶用
结语
最后,我会在安卓开发学习之线性布局的绘制过程中记录线性布局的绘制过程