背景
前段时间我阅读了三大常用布局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)方法一文