背景
前段时间我阅读了三大常用布局FrameLayout、RelativeLayout、LinearLayout的测量过程,众所周知,view的绘制过程有三大步:测量、布局、绘制,所以做事有始亦有终,我开始了阅读三大布局的layout过程,主要是阅读onLayout()方法
从框架布局开始,它的测量过程在文章安卓开发学习之FrameLayout测量流程源码阅读中。
onLayout
代码如下
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
直接调用了layoutChildren()方法
layoutChildren
方法不长,也就60多行,我就不分步骤了
代码如下
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
final int parentLeft = getPaddingLeftWithForeground(); // 子view各端点的极限
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
for (int i = 0; i < count; i++) {
// 遍历子view
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (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();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); // 横向重心
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; // 纵向重心
// 根据重心设置子view左端和上端
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if (!forceLeftGravity) { // forceLeftGravity默认是false
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
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;
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
思路很简单,就是根据重心设置子view的左端和右端,而后根据子view的measuredWidth和measuredHeight确定右端和上端,就调用子view的layout()方法,子view的layout()方法、measure()、draw()方法的阅读记录,我会放在三大布局的onMeasure()、onLayout()、onDraw()方法结束后,写出来。
Gravity#getAbsoluteGravity
可以看到在layoutChildren()方法中,为了得到子view的横向重心,当前布局调用了Gravity.getAbsoluteGravity()方法,这其实就是位运算,我就不深入了
public static int getAbsoluteGravity(int gravity, int layoutDirection) {
int result = gravity;
// If layout is script specific and gravity is horizontal relative (START or END)
if ((result & RELATIVE_LAYOUT_DIRECTION) > 0) { // 重心有效性
if ((result & Gravity.START) == Gravity.START) { // 和父view的起点对齐
// Remove the START bit
result &= ~START;
if (layoutDirection == View.LAYOUT_DIRECTION_RTL) { // 父view方向从右往左
// Set the RIGHT bit
result |= RIGHT;
} else { // 默认起点是左边
// Set the LEFT bit
result |= LEFT;
}
} else if ((result & Gravity.END) == Gravity.END) { // 和父view的终点对齐
// Remove the END bit
result &= ~END;
if (layoutDirection == View.LAYOUT_DIRECTION_RTL) { // 父view方向从左往右
// Set the LEFT bit
result |= LEFT;
} else {
// Set the RIGHT bit
result |= RIGHT;
}
}
// Don't need the script specific bit any more, so remove it as we are converting to
// absolute values (LEFT or RIGHT)
result &= ~RELATIVE_LAYOUT_DIRECTION;
}
return result;
}
结语
可以看到,框架布局的onLayout()它的onMeasure()还要简单,其实不只是框架布局,所有view的绘制过程最复杂的一步就是测量过程,跨过那道坎,后面的路会相对轻松一些
另外由于框架布局和它的父类ViewGroup都没有覆写onDraw()方法,所以它的绘制流程,是在View中实现的,但View不是调用的onDraw()方法,因为这是个空实现,要交给子类实现,真正调用的,是View.draw(canvas)方法,详情参见
参加安卓开发学习之View的draw(canvas)方法一文