开篇:
上一篇已经了解了 View 的工作原理之 Measure 过程,了解到 Measure 过程是从 ViewRootImpl#performTraversals 开始的,最后会执行到 onMeasure 方法,也对自定义 View 时 margin、padding 以及 wrap_content 如何处理已经简单说明,下面开始 Layout 过程。
还是从 ViewRootImpl#performTraversals 方法开始,不过这次我们要看的是 performLayout(lp, mWidth, mHeight) 方法了
ViewRootImpl#performTraversals 方法开始
private void performTraversals() {
...
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
if (didLayout) {
performLayout(lp, mWidth, mHeight);
...
if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
performDraw();
...
直接看 performLayout 方法
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
final View host = mView;
if (host == null) {
return;
}
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
最终调用了 View 的 Layout 方法,并传递当前 View 的四个顶点位置
layout 方法
public void layout(int l, int t, int r, int b) {
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;
//如果 isLayoutModeOptical 方法返回 true,那么就会去执行 setOpticalFrame 方法,否则就会去执行 setFrame 方法,并且 setOpticalFrame 内部其实还是调用的 setFrame 方法,所以无论如何都会执行 setFrame 方法,而 setFrame 方法会将传递的 left、top、right、bottom 存储在 View 的成员变量中,并且返回一个布尔值,如果返回true,表示 View 的位置或尺寸发生了变化,否则表示未发生变化,后面会单独介绍
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
//如果 View 的位置发生了变化那么就会去调用 View 的 onLayout 方法,View 的 onLayout 方法默认是一个空实现
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
看下 View 的 onLayout 方法,是一个空方法,具体实现是在子类中
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
来看一下 setFrame 方法,完事了开始介绍 ViewGroup 的 Layout
setFrame 方法
setFrame 方法具体是用来指定 View 的大小以及位置的,将传递过来的 left、top、right、bottom 都存储在 View 的成员变量中,并返回一个布尔值来标识 View 的位置或尺寸是否发生了变化
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 + ")");
}
//将新旧 left、top、right、bottom 进行比较,如果不完全相同则表示 View 的位置发生了改变,使用变量 changed 记录
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
// Remember our drawn bit
int drawn = mPrivateFlags & PFLAG_DRAWN;
//获取新与旧的宽度和高度
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
//比较新旧的 width 和 height 是否相同,不同则表示 View 的尺寸发生了变化
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// Invalidate our old position
invalidate(sizeChanged);
//将新的 left、top、right、bottom 进行存储
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
mPrivateFlags |= PFLAG_HAS_BOUNDS;
//如果尺寸发生了变化,就调用 sizeChange 方法,该方法又会调用 onSizeChanged 方法,传递 View 的新旧尺寸
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();
}
// Reset drawn bit to original value (invalidate turns it off)
mPrivateFlags |= drawn;
mBackgroundSizeChanged = true;
mDefaultFocusHighlightSizeChanged = true;
if (mForegroundInfo != null) {
mForegroundInfo.mBoundsChanged = true;
}
notifySubtreeAccessibilityStateChangedIfNeeded();
}
return changed;
}
ViewGroup的 Layout 过程
前面已经说过,Layout 过程的具体实现其实是在 onLayout 方法中,而 ViewGroup 继承自 View ,那它肯定也实现了 onLayout 方法,我们来看一下:
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
居然是一个抽象方法,为什么是抽象方法其实在介绍 Measure 过程时就已经说过了,因为不同的 ViewGroup 肯定是有各自的实现,不同的实现就让具体的 View 的实现不就行了,所以我们还是要看 LinearLayout 的 onLayout 方法
LinearLayout # 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 方法
LinearLayout # layoutVertical
layoutVertical 方法比起 measureVertical 代码量上就少了好多了,才 100 行不到,我们一步一步来分析吧
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
//子 View 布局时使用的 top 值
int childTop;
//子 View 布局时使用的 left 值
int childLeft;
// Where right end of child should go
// 当前 view 的最大宽度
final int width = right - left;
//减去 paddingRight 后剩余的 width
int childRight = width - mPaddingRight;
// Space available for child
//除去leftPadding以及rightPadding后剩余的宽度
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;
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;
}
//遍历子 View
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} 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;
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:
//计算当前子 View 的 left 位置
childLeft = paddingLeft + lp.leftMargin;
break;
}
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
//当前子 View 的 top 值
childTop += lp.topMargin;
//调用子 view 的 layout 方法
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
setChildFrame 方法
public void setOrientation(@OrientationMode int orientation) {
if (mOrientation != orientation) {
mOrientation = orientation;
requestLayout();
}
}
完事,Layout 过程是这三个过程中最简单的一个,没什么需要多说的....
网友评论