从源码角度分析view的layout过程
2015-08-19 16:28
555 查看
上两篇文章主要讲述了view的measure过程,主要分析了xml文件中控件的height和width设置成不同值的时候,经过测量之后,如何计算出控件的真实高度。所以也就验证了我们经常所说的measure过程就是把match_parent等值转化成在具体设备上的具体的值。
本文主要分析一下layout的过程,同样我们以LinearLayout的layout过程为例。
在ViewRoot的performTraversals方法中首先是measure过程,然后接着是layout,layout开始也是从host.layout方法开始的。
host是一个viewGroup,ViewGroup继承自view,然而view中的layout是final的,所以这里调用的仍然是view的layout方法,然后调用了onLayout方法,所以我们从linearLayout的onLayout方法中开始分析
linearLayout的layout过程如下:
首先根据mRight - mLeft获得width,根据width - paddingLeft - mPaddingRight获得childSpace,也就是实际上我们可以用的宽度。然后根据LinearLayotu的gravity的值计算childTop的位置:
为Gravity.BOTTOM:
childTop = mBottom - mTop + mPaddingTop - mTotalLength;
在这里当测量出来的mTotalLength总高度足够大的时候会出现childTop是负数的情况,也就是child的上面一部分会显示不全的情况,对应的图就像是下面的情况,图中上面的蓝色框内的一部分是不会显示的。
为Gravity.CENTER_VERTICAL:
childTop += ((mBottom - mTop) - mTotalLength) / 2;
对应的图片如下所示:
接着循环遍历子view,如果子view为可见的,则计算出子view在水平方向上的childLeft,这里我们讨论的是垂直方向的布局,这里为什么会出现还要计算view在水平方向上的left呢,因为就算是垂直布局,每个子view他们也有marginleft和paddingLeft值。最后调用setChildFrame设置子view的布局位置,我们可以进入此方法看一下:
给子View布局最终调用的是setFrame方法,四个参数分别代表view在其父视图中的位置。首先判断如果这四个值和之前已经有的值是否相等,如果有一个不相等,就代表要重新布局,此时如果子view现在的高度和宽度和之前的宽高是不相同的,那么就必须要调用onSizeChanged方法,通知程序view的大小发生了变化,最后如果view是VISIBLE的,那么就要执行invalidate操作。
本文主要分析一下layout的过程,同样我们以LinearLayout的layout过程为例。
在ViewRoot的performTraversals方法中首先是measure过程,然后接着是layout,layout开始也是从host.layout方法开始的。
host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);
host是一个viewGroup,ViewGroup继承自view,然而view中的layout是final的,所以这里调用的仍然是view的layout方法,然后调用了onLayout方法,所以我们从linearLayout的onLayout方法中开始分析
public final void layout(int l, int t, int r, int b) { boolean changed = setFrame(l, t, r, b); if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) { if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT); } onLayout(changed, l, t, r, b); mPrivateFlags &= ~LAYOUT_REQUIRED; } mPrivateFlags &= ~FORCE_LAYOUT; }
linearLayout的layout过程如下:
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (mOrientation == VERTICAL) { layoutVertical(); } else { layoutHorizontal(); } } void layoutVertical() { final int paddingLeft = mPaddingLeft; int childTop = mPaddingTop; int childLeft; final int width = mRight - mLeft; int childRight = width - mPaddingRight; // Space available for child 实际上可用的宽度空间 int childSpace = width - paddingLeft - mPaddingRight; final int count = getVirtualChildCount(); final int majorGravity = mGravity & Gravity.VERTICAL_GR***ITY_MASK; final int minorGravity = mGravity & Gravity.HORIZONTAL_GR***ITY_MASK; //根据LinearLayotu的gravity的值计算childTop的位置 if (majorGravity != Gravity.TOP) { switch (majorGravity) { case Gravity.BOTTOM: childTop = mBottom - mTop + mPaddingTop - mTotalLength; break; case Gravity.CENTER_VERTICAL: childTop += ((mBottom - mTop) - mTotalLength) / 2; 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(); //拿到子View的LayoutParams final LinearLayout.LayoutParams lp =(LinearLayout.LayoutParams) child.getLayoutParams(); int gravity = lp.gravity; if (gravity < 0) { gravity = minorGravity; } //计算子View在水平方向的childLeft switch (gravity & Gravity.HORIZONTAL_GR***ITY_MASK) { case Gravity.LEFT: childLeft = paddingLeft + lp.leftMargin; break; case Gravity.CENTER_HORIZONTAL: childLeft = paddingLeft + ((childSpace - childWidth) / 2) + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: childLeft = childRight - childWidth - lp.rightMargin; break; default: childLeft = paddingLeft; break; } childTop += lp.topMargin; // 调用child.layout方法设置child的布局位置 setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight); childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child); i += getChildrenSkipCount(child, i); } } }
首先根据mRight - mLeft获得width,根据width - paddingLeft - mPaddingRight获得childSpace,也就是实际上我们可以用的宽度。然后根据LinearLayotu的gravity的值计算childTop的位置:
为Gravity.BOTTOM:
childTop = mBottom - mTop + mPaddingTop - mTotalLength;
在这里当测量出来的mTotalLength总高度足够大的时候会出现childTop是负数的情况,也就是child的上面一部分会显示不全的情况,对应的图就像是下面的情况,图中上面的蓝色框内的一部分是不会显示的。
为Gravity.CENTER_VERTICAL:
childTop += ((mBottom - mTop) - mTotalLength) / 2;
对应的图片如下所示:
接着循环遍历子view,如果子view为可见的,则计算出子view在水平方向上的childLeft,这里我们讨论的是垂直方向的布局,这里为什么会出现还要计算view在水平方向上的left呢,因为就算是垂直布局,每个子view他们也有marginleft和paddingLeft值。最后调用setChildFrame设置子view的布局位置,我们可以进入此方法看一下:
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
public final void layout(int l, int t, int r, int b) { boolean changed = setFrame(l, t, r, b); if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) { if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT); } onLayout(changed, l, t, r, b); mPrivateFlags &= ~LAYOUT_REQUIRED; } mPrivateFlags &= ~FORCE_LAYOUT; }
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
int drawn = mPrivateFlags & DRAWN;
invalidate();
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mPrivateFlags |= HAS_BOUNDS;
int newWidth = right - left;
int newHeight = bottom - top;
if (newWidth != oldWidth || newHeight != oldHeight) {
onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
}
if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) {
mPrivateFlags |= DRAWN;
invalidate();
}
// Reset drawn bit to original value (invalidate turns it off)
mPrivateFlags |= drawn;
mBackgroundSizeChanged = true;
}
return changed;
}
给子View布局最终调用的是setFrame方法,四个参数分别代表view在其父视图中的位置。首先判断如果这四个值和之前已经有的值是否相等,如果有一个不相等,就代表要重新布局,此时如果子view现在的高度和宽度和之前的宽高是不相同的,那么就必须要调用onSizeChanged方法,通知程序view的大小发生了变化,最后如果view是VISIBLE的,那么就要执行invalidate操作。
相关文章推荐
- 4、Cocos2dx 3.0三,找一个小游戏开发Hello World 分析
- char[]数组与char *指针的区别
- Bootstrap--组件之按钮组
- VC++ MFC中如何将应用程序的配置信息保存到注册表中(一)
- [LeetCode] Ugly Number
- 剑指offer:3-二维数组中的查找
- 桌面支持--操作ppt会弹出对话框
- Objective-C中不同方式实现锁(二)
- [置顶] JAVA的六大经典算法,代码案例简化分析
- IE6兼容透明背景图片及解决方案
- 欢迎使用CSDN-markdown编辑器
- HDU 2689 Sort it(树状数组求逆序数)
- Android SDK Manager不能显示所有包的解决办法
- The Himalayas (zoj 3809)
- 允许主机远程管理mysql数据库
- iGrimace IG1.44和V3.0.0 版本区别
- 桌面支持--PPT2010母版中的背景如何修改与设置
- wampserver localhost 403
- boost bind 用法
- 工作总结 8.9-18