您的位置:首页 > 移动开发 > Android开发

浅析Android View的Layout过程

2015-11-12 22:05 531 查看
Layout过程是View的三大过程之一,它负责确定一个View和它的子元素的最终位置。与measure过程类似,layout过程也分为layout()和onLayout()两个核心方法。layout负责确定view本身的位置,而layout主要负责确定所有子元素的位置。首先让我们来看看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);
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;
}
layout方法的核心思想就是通过setFrame来确定了view的上下左右四个顶点,并存入mTop,mBottom,mLeft,mLeft中。四个顶点一确定,view本身的大小位置就确定了,同时,layout方法还会调用onLayout来对子view进行布局。不同的是,View本身并没有定义layout方法,ViewGroup更是将layout方法定义为抽象,不同的布局需要自己定义onLayout方法,来确定子元素的摆放位置。

我们先来简单看来RelativeLayout的onLayout方法:

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//  The layout has actually already been performed and the positions
//  cached.  Apply the cached values to the children.
final int count = getChildCount();

for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
RelativeLayout.LayoutParams st =
(RelativeLayout.LayoutParams) child.getLayoutParams();
child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
}
}
}
代码不能再简单,RelativeLayout就是得到子view的LayoutParam,循环调用子View的layout方法,将他们堆叠在自己的左上角的位置(左上角位置为0,0)。

而LinearLayout的Layout方法就复杂些,它根据orientation的不同,分别对水平和竖直进行了单独处理:

@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);
}
}
这里举竖直方向为例:可以看到,该方法也是循环遍历子元素并确定它们的位置,其中childTop会逐渐增大,这样一层一层的元素就被罗列在竖直方向上:

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


经过《浅析Android View的Measure过程》和《浅析Android View的Layout过程》两篇博客的分析,我们可以得出mMeasureWidth(Height) 和mWidth(Height)的不同:

mMeasureWidth和mMeasureHeight在measure过程中被实例化,而mHeight = mBottom - mTop, mWidth = mRight - mLeft, 它们都在layout过程中才被实例化。也就是说,当layout过程结束后,mMeasureWidth和mWidth,mMeasureHeight和mHeight是完全相同的。但当我们重写ViewGroup的onLayout方法时,想要获取子View的宽高来确定父View的宽高时,必须要使用mMeasureWIdth或mMeasureHeight了,因为此时还没有调用子view的layout过程。

这里有个小例子提供给大家:我自己定义了一个Viewgroup,它继承了HorizonScrollView,内部包含了一个LinearLayout,当在layout方法时获取本View的width、measuredWidth,子view的width,子view的measuredWidth时打印出如下结果:

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
Log.v("lzq","this view measure width" + getMeasuredWidth());
Log.v("lzq","this view width" + getWidth());
Log.v("lzq","child view width " + mLinearLayout.getWidth());
Log.v("lzq","child view measured width " + mLinearLayout.getMeasuredWidth());
}


V/lzq: this measure width1080

V/lzq: this width1080

V/lzq: this child width 0

V/lzq: this child measured width 2250

我们可以看到,子View的width为0,证明在layout方法中,没有完成子view的layout过程,子view的measureWidth被初始化了是因为子view已经完成了measure过程。父view的mesureWidth和width都有值是因为已经完成了父view的measure和layout过程(super.onLayout)。

总结:layout过程执行在measure过程之后,负责确定本View和子View的位置,并在其中实例化上下左右四个顶点(mTop,mBottom,mLeft,mRight),重写ViewGroup的onLayout方法可以自定义子View在该ViewGroup中的位置布局。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: