新闻资讯
View工作流程梳理——Layout流程
Layout流程
layout流程同样从rootview开始,在rootview完成测量之后,会调用到performLayout方法来发起layout
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView; if (host == null) { return;
} if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
Log.v(mTag, "Laying out " + host + " to (" +
host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
//根view layout流程开始,传入已经测量好的高宽参数
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
看下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;
boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
...
}
...
}
这个方法里首先会调用到setFrame方法来设置自己的left、top、bottom、right,方法如下:
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false; if (DBG) {
Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + "," + right + "," + bottom + ")");
}
//判断新传入的位置和之前的位置是否有改变 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;
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; 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;
}
然后回到layout方法,会回调自己的onLayout方法,如果是ViewGroup,会在onLayout方法里对子view进行layout,确定子view的位置。这里需要注意,onLayout方法view和ViewGroup都没有自己实现,因为子view的layout是根据具体的布局来的,不一样的布局实现不同,所以onLayout方法交给了子类去实现。这里就看下LiearLayout的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);
}
}
就看一个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
final int width = right - left;
int childRight = width - mPaddingRight;
// Space available for child
int childSpace = width - paddingLeft - mPaddingRight;
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;
} 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:
childLeft = paddingLeft + lp.leftMargin; break;
} if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin; setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
这个方法是排列竖直方向的子view。我们可以看到循环遍历了含有的子view,分别取出子view的布局参数和测量好的高宽,来计算子view的left、top、right、bottom的值,来确定子view摆放的位置。然后在竖直方向还会累加子view的top值到childTop,每计算一个子view都会把这个childTop计算在内,意思就是按顺序摆放竖直方向的子view,后面的子view只能摆放在前面的子view的下方。计算好子view的left、top、right、bottom四个值之后,会调用到setChildFrame方法:
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
这里就可以看到调用到了子view的layout方法,把计算好的四个值传过去,把layout流程交给了子view。就这样一层一层的传递下去,直到所有view树的view都完成layout过程。
原文链接:https://juejin.im/post/5e53e031f265da573c0c778f
回复列表