上一篇文章我们了解了View的onMeasure,那么今天我们继续来学习Android View绘制三部曲的第二步,onLayout,布局。

ViewRootImpl#performLayout

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) {
    mLayoutRequested = false;
    mScrollMayChange = true;
    mInLayout = true;

 final View host = mView;
 if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
 Log.v(mTag, "Laying out " + host + " to (" +
 host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
 }

 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
 try {
 host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

 mInLayout = false;
 //此处省略的代码是在layout的过程中,重复的requestLayout,需要做的处理。
 //具体的处理方案是重新measure,layout。
 ...
 } finally {
 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
 }
 mInLayout = false;
}

这个方法主要的作用就是调用了host.layout,并把已经测绘好的宽高传计算成上下左右递过去,host就是decorView。

View#layout

public void layout(int l, int t, int r, int b) {
    //根据mPrivateFlags3标记位状态判断,如果需要,则重新measure。
    if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
        onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
        mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
 }

 int oldL = mLeft;
 int oldT = mTop;
 int oldB = mBottom;
 int oldR = mRight;
 
 //检查是位置有变化,并setFrame
 //setFrame分析见下文
 boolean changed = isLayoutModeOptical(mParent) ?
 setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
 
 //如果位置有变化或者PFLAG_LAYOUT_REQUIRED标记位为on,则进行onLayout
 if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
 onLayout(changed, l, t, r, b);
 //把PFLAG_LAYOUT_REQUIRED标记位置为off
 mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
 
 //进行onLayoutChange回调
 ListenerInfo li = mListenerInfo;
 if (li != null && li.mOnLayoutChangeListeners != null) {
 ArrayList<OnLayoutChangeListener> listenersCopy =
 (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
 int numListeners = listenersCopy.size();
 for (int i = 0; i < numListeners; ++i) {
 listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
 }
 }
 }
 
 //将PFLAG_FORCE_LAYOUT标记置为off,将PFLAG3_IS_LAID_OUT置为on
 mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
 mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}

View#setFrame

protected boolean setFrame(int left, int top, int right, int bottom) {
    boolean changed = false;

    if (DBG) {
        Log.d("View", this + " View.setFrame(" + left + "," + top + ","
 + right + "," + bottom + ")");
 }
 
 //如果上下左右任意一项有改动,则继续往下进行,否则直接返回false
 if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
 changed = true;

 //记录PFLAG_DRAWN位状态,最后复原的时候需要
 int drawn = mPrivateFlags & PFLAG_DRAWN;

 int oldWidth = mRight - mLeft;
 int oldHeight = mBottom - mTop;
 int newWidth = right - left;
 int newHeight = bottom - top;
 boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

 //刷新原有布局,invalidate方法将在另一篇文章中详细展开。
 invalidate(sizeChanged);

 //设置该View的上下左右,也是setFrame的核心功能
 mLeft = left;
 mTop = top;
 mRight = right;
 mBottom = bottom;
 mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
 //PFLAG_HAS_BOUNDS位置为on
 mPrivateFlags |= PFLAG_HAS_BOUNDS;

 //如果尺寸有改变,调用onSizeChange并且调用rebuildOutline
 if (sizeChanged) {
 sizeChange(newWidth, newHeight, oldWidth, oldHeight);
 }

 if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
 // If we are visible, force the DRAWN bit to on so that
 // this invalidate will go through (at least to our parent).
 // This is because someone may have invalidated this view
 // before this call to setFrame came in, thereby clearing
 // the DRAWN bit.
 mPrivateFlags |= PFLAG_DRAWN;
 invalidate(sizeChanged);
 // parent display list may need to be recreated based on a change in the bounds
 // of any child
 invalidateParentCaches();
 }

 // 把PFLAG_DRAWN设置为原有数值。(invalidate过程中会将其设为off)
 mPrivateFlags |= drawn;

 mBackgroundSizeChanged = true;
 if (mForegroundInfo != null) {
 mForegroundInfo.mBoundsChanged = true;
 }
 
 //Android无障碍辅助通知
 notifySubtreeAccessibilityStateChangedIfNeeded();
 }
 return changed;
}

FrameLayout#onLayout

如果是View的话,执行完layout方法,那么他已经布局完成,不过如果是ViewGroup,那么它需要对它的子View进行处理。onLayout主要的作用就是调用layoutChildren,对子View进行布局,所以这里着重介绍layoutChildren。

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}

void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
 final int count = getChildCount();

 //计算parent的上下左右
 final int parentLeft = getPaddingLeftWithForeground();
 final int parentRight = right - left - getPaddingRightWithForeground();

 final int parentTop = getPaddingTopWithForeground();
 final int parentBottom = bottom - top - getPaddingBottomWithForeground();

 for (int i = 0; i < count; i++) {
 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;
 }

 //获取layout默认方向,通常是从左到右,在某些特定语言的情况下是从右到左
 final int layoutDirection = getLayoutDirection();
 //通过刚才的方向值,计算出绝对的横向位置属性
 final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
 //计算竖向位置属性
 final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

 //通过位置属性,计算子View的left和right
 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) {
 childLeft = parentRight - width - lp.rightMargin;
 break;
 }
 case Gravity.LEFT:
 default:
 childLeft = parentLeft + lp.leftMargin;
 }

 //通过位置属性,计算子View的top和bottom
 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方法
 child.layout(childLeft, childTop, childLeft + width, childTop + height);
 }
 }
}

时序图

图为View layout 时序图

小结

到这里就介绍完了View绘制的layout方法。比起measure,layout可是简单多了。不过这里还预留了一些坑,没有交代清楚,比如invalidate,还有RenderNode硬件加速等,以后会写一些笔记专门针对这些知识点做梳理。