View与ViewGroup layout 过程
这个过程相比measure要简单一点,我们先从View 的layout()看起,ViewGroup的layout主要还是判断一些条件之后调用View 的
layout()
View.java
public void layout(int l, int t, int r, int b) {
if (DBG_SYSTRACE_LAYOUT) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout : " + getClass().getSimpleName());
}
// !=0 表示在measure 的时候设置这个标志,也就是measure的时候根据measure cache 测量,而不是直接调用onMeasure(),所以这里要重新onMeasure一次
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
/// M: Monitor onLayout time if longer than 3s print log.
if (DBG_LAYOUT || DBG_MEASURE_LAYOUT) {
Xlog.d(VIEW_LOG_TAG, "view onMeasure start (measure cache), this =" + this
+ ", widthMeasureSpec = " + MeasureSpec.toString(mOldWidthMeasureSpec)
+ ", heightMeasureSpec = " + MeasureSpec.toString(mOldHeightMeasureSpec));
}
long logTime = System.currentTimeMillis();
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
long nowTime = System.currentTimeMillis();
if (nowTime - logTime > DBG_TIMEOUT_VALUE) {
Xlog.d(VIEW_LOG_TAG, "[ANR Warning]onMeasure time too long, this =" + this
+ "time =" + (nowTime - logTime) + " ms");
}
if (DBG_LAYOUT || DBG_MEASURE_LAYOUT) {
Xlog.d(VIEW_LOG_TAG, "view onMeasure end (measure cache), this =" + this
+ ", mMeasuredWidth = " + mMeasuredWidth + ", mMeasuredHeight = "
+ mMeasuredHeight + ", time =" + (nowTime - logTime) + " ms");
}
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;//将标志位恢复
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//视觉/光学边界布局,一般是false
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (DBG_LAYOUT || DBG_MEASURE_LAYOUT) {
Xlog.d(VIEW_LOG_TAG, "view layout start, this = " + this + ", mLeft = " + mLeft
+ ", mTop = " + mTop + ", mRight = " + mRight + ", mBottom = " + mBottom
+ ", changed = " + changed);
}
//PFLAG_LAYOUT_REQUIRED这个标志也是在measure 的时候设置的.
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
/// M: Monitor onLayout time if longer than 3s print log.
long logTime = System.currentTimeMillis();
onLayout(changed, l, t, r, b);//这个方法view和ViewGroup都没有实现,具体是有子类去实现
long nowTime = System.currentTimeMillis();
if (nowTime - logTime > DBG_TIMEOUT_VALUE) {
Xlog.d(VIEW_LOG_TAG, "[ANR Warning]onLayout time too long, this =" + this
+ "time =" + (nowTime - logTime) + " ms");
}
if (DBG_LAYOUT || DBG_MEASURE_LAYOUT) {
Xlog.d(VIEW_LOG_TAG, "view layout end, this =" + this + ", mLeft = " + mLeft
+ ", mTop = " + mTop + ", mRight = " + mRight + ", mBottom = " + mBottom
+ ", time =" + (nowTime - logTime) + " ms");
}
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;//将标志位恢复
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);//接口回调
}
}
} else {
if (DBG_LAYOUT || DBG_MEASURE_LAYOUT) {
Xlog.d(VIEW_LOG_TAG, "view layout end 2 (use previous layout), this = " + this
+ ", mLeft = " + mLeft + ", mTop = " + mTop
+ ", mRight = " + mRight + ", mBottom = " + mBottom);
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
if (DBG_SYSTRACE_LAYOUT) {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
从上面的代码可以看到,layout 流程先会根据之前在measure流程中是否设置了
PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT
变量来判断是否需要重新measure一遍,然后调用
setFrame
来确定自己的4个点的位置,4个点分别是left,top.right,bottom.有了这4个点,后续才能准确的绘制.
接着根据在measure里面设置的
PFLAG_LAYOUT_REQUIRED
变量来执行
onLayout
流程,其实这个方法View和ViewGroup都没有去实现,真正的实现是在各个子类里面的,后面会分析.再这之后,如果这个视图设置过
OnLayoutChangeListener
接口,还会回调这些接口的
onLayoutChange()
方法.
这里暂时不分析
setFrame()流程,后续在draw()里面再看.
上面已经提到
View和ViewGroup没有自己去实现onLayout(),是需要子类是去实现.这里还是以LinearLayout为例分析一下实现过程.
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
这里我们只分析垂直布局
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);
}
}
}
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
从上面的代码可以看到,是先根据
Gravity
属性来确定第一个child 的top 位置,然后循环来获取childTop和childLeft,同时获取宽高来调用
setChildFrame
().而
setChildFrame
()只是调用child 的layout流程.就这样递归调用各个"child"的layout流程.