Android - View的绘制流程二(layout)

Android - View的绘制流程一(measure)一文中提到,view绘制的核心逻辑都在ViewRoot的performTraversals()方法中,主要分为三个阶段: 第一个阶段是measure,第二个阶段是layout,第三个阶段是draw
ViewRoot类的performTraversals方法中layout方法的调用,代码如下:
private void performTraversals() 
measure过程结束后,从这里开始layout,同样host是一个DecorView对象。host.mMeasuredWidth 和 host.mMeasuredHeight 则是 measure过程结束之后得到的host的宽和高。
在较低的版本中,定义在view中的layout方法是被final修饰的,不能被复写,本博文参考的是android-19代码,layout没有被final修饰,不过不影响大致流程的理解:
public void 
int oldL = mLeft;
    int oldT = mTop;
     int oldB = mBottom;
    int oldR = mRight;
    boolean changed = isLayoutModeOptical(mParent) ?setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
protected boolean setFrame(int left, int top, int right, int bottom) {
    boolean changed = false;
    if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
        changed = true;
        mLeft = left;
        mTop = top;
        mRight = right;
        mBottom = bottom;
    }
    return changed;
}
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
    mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
}
}
layout方法的参数由父类提供,参数指定了该子视图在父视图中的左、上、右、下的位置,主要逻辑如下:
1、调用setFrame方法,先比对这些参数是否和原来的相同,如果相同,则什么都不做,只要四个参数中有一个发生改变,则将这些参数赋值给view的成员变量(mLeft、mTop、mRight和mBottom)。
2、调用onLayout方法。
// View 中的onLayout方法的定义:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {  }
// ViewGroup 中的onLayout方法的定义:
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
View 中的onLayout方法默认什么都不做,ViewGroup中的onLayout方法则是一个抽象方法,所以,ViewGroup类型的视图都会在自己类中对子视图进行位置计算。
Android - View的绘制流程一(measure)一样,也从MyCustomLinearLayoutA为例,从onLayout()方法开始分析一个ViewGroup类型的视图遍历—layout子视图的详细过程。
LinearLayout类中的onLayout()方法:
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);
        }
}
根据LinearLayout的布局方向调用layoutVerticallayoutHorizontal,下面以Android - View的绘制流程一(measure)demo中MyCustomLinearLayoutA为例分析竖直方向的layoutVertical
LinearLayout的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
// LinearLayout可用的宽度
        final int width = right - left;
// child最多能够到达的右边位置
        int childRight = width - mPaddingRight;
        // Space available for child
// 计算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;
// 首先根据gravity的值计算出child的top值
        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;
        }
// 然后进行遍历,根据child的水平布局方式(水平居中、靠左或靠右)计算出child的left值
        for (int i = 0; i < count; i++) {
            final View child = getVirtualChildAt(i);
            if (child == null) {
                childTop += measureNullChild(i);
            } else if (child.getVisibility() != GONE) {
// child的宽度和高度已经在measure步骤中计算过了,现在直接获取
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();
                // 获取child的布局参数
                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;
// 计算完child的top和left值,获取到measure步骤中计算的宽和长,再调用setChildFrame方法设置child的位置
                setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight);
private void setChildFrame(View child, int left, int top, int width, int height) {   
//  setChildFrame方法中调用的就是layout方法
        child.layout(left, top, left + width, top + height);
}
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
                i += getChildrenSkipCount(child, i);
            }
        }
}
至此,view的layout过程就分析完成了。
layout过程的核心就是根据xml文件中的gravity属性计算出top和left的值,再利用第一步 — measure所得到的宽和高的值计算出view对象的坐标(left、top、right和bottom)。
大小和位置都确定了,接下来是第三步 — draw。

你可能感兴趣的:(android,布局)