理想是人生的太阳。 — 德莱塞
写在前面
在《View的绘制流程》一篇中介绍了View如何工作,最终会调用ViewRootImpl的performTraversals()遍历View树,分别执行measure,layout和draw流程。
本篇文章来看下layout流程,layout的作用是确定元素的位置,ViewGroup中layout方法用来确定子元素的位置,View中的layout方法用来确定自身的位置。
ViewGroup的layout流程
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
@Override
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b); // 1
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}
}
ViewGroup的layout方法中并没有做太多事情,而是调用super.layout(l, t, r, b),我们知道ViewGroup继承自View,所以super.layout(l, t, r, b)调用的就是其父类View的layout(l, t, r, b)函数,那么我们就直接看View的layout流程。
View的layout流程
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
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); // 1
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b); // 2
......
} else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) {
......
}
}
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);
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()函数中注释1处调用的setFrame()函数,setFrame()内部会先计算出之前的宽和高与现在的宽和高进行比较,判断宽和高是否有改变,无论宽还是高,只要有一个改变,sizeChanged就会置为true,说明View的位置已经有变化了。接下来会给mLeft,mTop,mRight,mBottom进行赋值,并将结果changed返回。setOpticalFrame(l, t, r, b)内部最终调用的也是setFrame(l, t, r, b)函数。现在回到layout()方法,如果setFrame()函数的返回值changed为true,则会调用onLayout()方法,onLayout()方法是一个空方法,和onMeasure()方法类似, 确定位置时需要根据不同的控件有不同的实现,所以View和ViewGroup均没有实现该方法。参数l, t, r, b分别表示View从左,上,右,下距离父容器的距离。
LinearLayout的layout流程
public class LinearLayout extends ViewGroup {
@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);
}
}
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); // 1
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
}
之前说过onLayout()是一个空方法,和onMeasure()类似,确定位置需要根据不同的控件右不同的实现,所以View和ViewGroup均没有实现该方法。
LinearLayout继承自ViewGroup,那么就来看下LinearLayout的onLayout()方法是如何实现的?
首先看onLayout()方法,会根据布局方向调用不同的函数,这里以垂直方向为例,会调用layoutVertical(l, t, r, b),在layoutVertical(l, t, r, b)函数中注释1处可以看到调用setChildFrame()函数,setChildFrame()函数内部调用了子元素的layout()方法,而子元素还可以再重写onLayout()方法确定位置,依此类推。因为布局方向是垂直的,所以childTop会一直增加,直到遍历完子元素,childTop就是当前的子元素到父控件顶边的距离。由此就可以推导出如果布局方向是水平的,childLeft会一直增加,直到遍历完子元素,childLeft就是当前的子元素到父控件左边的距离。
总结
View的layout()流程就这么多,是不是很简单呢?感兴趣的童鞋也可以深入了解下TextView和FrameLayout等layout()流程。
PS:本人才疏学浅,若有不足请赐教!!!
网友评论