View绘制详解(四),谝一谝layout过程
上篇博客我们介绍了View的测量过程,这只是View显示过程的第一步,第二步就是layout了,这个我们一般译作布局,其实就是在View测量完成之后根据View的大小,将其一个一个摆放在ViewGroup中的过程。OK,那我们今天就来聊聊这个过程。在本文之前我已经有过三篇博客来介绍View的绘制过程,那三篇文章有助于你理解本文:
2.View绘制详解(二),从setContentView谈起
OK,废话不多说,来看看今天的东东。本文主要包含如下几方面内容:
1.View中的layout
2.在ViewGroup中对View进行排列
3.以LinearLayout为例来看看layout过程
4.根布局的layout
1.View中的layout
要说layout过程,首先我们得先来看看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);
-
-
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;
-
}
2.在ViewGroup中对View进行排列
我们都知道,ViewGroup继承自View,而ViewGroup也重写了View中的layout和onLayout方法,而且这里还有一点点变化,我们来看看ViewGroup中的这两个方法:
-
@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);
-
} else {
-
// record the fact that we noop'd it; request layout when transition finishes
-
mLayoutCalledWhileSuppressed = true;
-
}
-
}
-
-
@Override
-
protected abstract void onLayout(boolean changed,
-
int l, int t, int r, int b);
-
@Override
-
protected void onLayout(boolean changed, int l, int t, int r, int b) {
-
//获取容器中的控件(假设只有一个)
-
View view = getChildAt(0);
-
//设置该View上下左右四个点的坐标,对View进行摆放
-
view.layout(0, 0, 100, 100);
-
}
3.以LinearLayout为例来看看layout过程
这里我们以LinearLayout为例,来看看View到底是如何摆放在一个容器中的。因为我们说过,凡是继承自ViewGroup的类都是不能重写layout方法的,但是同时又必须重写onLayout方法,所以这里我们就先来看看LinearLayout的onLayout方法:
-
@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();
-
//获取父容器的Gravity
-
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
-
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
-
//根据底部对齐、垂直居中、顶部对齐分别来计算控件顶部的起始位置
-
//注意mTotalLength参数是我们在上一篇博客中提到的LinearLayout测量时子View的总高度
-
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) {
-
//获取子View的测量宽高
-
final int childWidth = child.getMeasuredWidth();
-
final int childHeight = child.getMeasuredHeight();
-
-
final LinearLayout.LayoutParams lp =
-
(LinearLayout.LayoutParams) child.getLayoutParams();
-
//从子控件的LayoutParams总获取gravity属性,但是这个gravity是指子View的android:layout_gravity属性而不是android:gravity属性
-
int gravity = lp.gravity;
-
if (gravity < 0) {
-
gravity = minorGravity;
-
}
-
final int layoutDirection = getLayoutDirection();
-
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
-
//根据子控件的layout_gravity属性来计算子控件显示时的childLeft的值
-
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;
-
//该方法中实际上调用了layout方法进行控件的摆放
-
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
-
childWidth, childHeight);
-
//对于下一个控件而言,它的起始高度是已经摆放好的View的高度之和
-
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);
-
}
说到这里,小伙伴们应该明白了,其实每一个控件包括容器都是在它的父容器中进行摆放的,那么这个时候小伙伴们可能会有另外一个疑问,那么我们的控件总有一个是没有父容器的,就是那个DecorView,那么DecorView又是在哪里进行摆放的呢?请看下文。
4.根布局的layout
OK,控件摆放还剩一个小问题,就是DecorView是在哪里摆放?其实和我们之前说的DecorView是在哪里进行测量是同一个问题,对于这个问题,我们还是得回到ViewRootImpl中去寻找答案。我们都知道View绘制过程的启动是从performTraversals方法开始的,在这个方法中系统首先进行了View的测量,然后调用了performLayout方法进行View的摆放,performLayout中又调用了layout方法来进行控件的摆放,整个流程基本就是这样。这里的方法略长,我就不贴出来了,有兴趣的小伙伴们可以自行查看。
OK,这就是layout的一个简单的摆放过程。
以上。
文章来源: wangsong.blog.csdn.net,作者:_江南一点雨,版权归原作者所有,如需转载,请联系作者。
原文链接:wangsong.blog.csdn.net/article/details/52734970
- 点赞
- 收藏
- 关注作者
评论(0)