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;//视觉/光学边界布局,一般是falseboolean 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 gofinal int width = right - left;int childRight = width - mPaddingRight;// Space available for childint 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 alreadychildTop = mPaddingTop + bottom - top - mTotalLength;break;// mTotalLength contains the padding alreadycase 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流程.
437

被折叠的 条评论
为什么被折叠?



