分析理解View的源码——Layout

Measure的过程分析完了,来分析一下layout。

measure方法执行完之后,系统确定了view的测量大小,接下来会执行到view的layout代码。

该过程会确定视图的显示位置,即子view在父控件中的位置。

既然涉及到摆放的位置,如果是自定义view,一般是用不到重写Layout,因为只有一个控件,还要怎么去摆放呢?

如果自定义viewGroup,这个时候需要重写,但是也不是重写layout方法,而是重写onLayout方法。为什么呢?看代码

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);
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);

ViewGroup重写的layout是final修饰的,不能重写,onLayout是abstract修饰的,必须要重写,让子类自己实现自己需要的布局效果,重写onLayout在哪一步被调用呢?我们来看看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);    //判断布局view有没有变化

if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);                    //有变化就会调用onLayout

if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}

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);
}
}
}

mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
在判断change时,此处调用setFrame()方法,该方法会把之前的left,top,right,bottom和传入的判断,如果有改变,说明view的位置发生了改变,返回true,接下来会调用onLayout方法,重新布局。
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 + ")");
}

if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {      
changed = true;                                  //如果有变化,返回的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;                                    //在这一步给mLeft等赋值的
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;
if (mForegroundInfo != null) {
mForegroundInfo.mBoundsChanged = true;
}

notifySubtreeAccessibilityStateChangedIfNeeded();
}
return changed;
}
上诉代码可以看到,什么时候会调用onLayout(),也发现getMeasureWidth(),和getWidth()的区别;
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}

public final int getWidth() {
return mRight - mLeft;
}
熟悉了执行的流程,从两个方法中就可以看出来区别: 
mMeasuredWidth方法在onMeasure时就会给每一个view获取到MeasureSpec的属性。所以在Measure流程执行过后,就可以得到测量的宽高。
getWidth方法需要属性mRight - mLeft,才能得到具体的值,但是这两个值在setFrame方法执行的时候才回去赋值,所以在layout执行完之后才能得到值。layout之后完后得到的值才是View的实际大小。

下面看下LinearLayout的代码,看看它是如何实现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);
}
}
根据不同方向,来做不同的布局。
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;      //计算child可使用的空间大小。用宽度 - padding值

final int count = getVirtualChildCount();            //垂直方法子view的个数
      
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;      //判断LinearLayout的Gravity属性是什么,只是top从顶部,中间,还是底部开始
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++) {                  //遍历每一个子view
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {          
final int childWidth = child.getMeasuredWidth();            //测量子view的宽高,确定子view的宽和高
final int childHeight = child.getMeasuredHeight();

final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();

int gravity = lp.gravity;                        //确定子view的位置。
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),        //得到子view的位置,让子view去摆放自己
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);                      //setChildFrame实际上就是调用child的layout方法
}
看到这里我们就清楚了,ViewGroup首先调用了layout方法确定自己本身在父View的位置(ViewRoot),然后调用onLayout确定每个子view的位置,每个子View会调用自己的layout,确定自己在ViewGroup的位置。
简单的说,就是View的layout方法用于view确定自己本身在父View的位置,ViewGroup的onLayout方法用来确定子View的位置。









posted @ 2017-11-08 16:25  wlwqnj  阅读(308)  评论(0编辑  收藏  举报