浅析Android View的Layout过程
2015-11-12 22:05
531 查看
Layout过程是View的三大过程之一,它负责确定一个View和它的子元素的最终位置。与measure过程类似,layout过程也分为layout()和onLayout()两个核心方法。layout负责确定view本身的位置,而layout主要负责确定所有子元素的位置。首先让我们来看看layout方法:
我们先来简单看来RelativeLayout的onLayout方法:
而LinearLayout的Layout方法就复杂些,它根据orientation的不同,分别对水平和竖直进行了单独处理:
经过《浅析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时打印出如下结果:
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中的位置布局。
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中的位置布局。
相关文章推荐
- android view构造函数研究
- Android 动态添加view
- Android获取TextView的长度
- android传递数据bundle封装传递map对象
- ListView的多选模式
- android高级控件ListView
- Android卡片新闻页优化实践
- android基础控件
- 35.Android .gitignore 模板
- 获取文字宽度
- Android自定义RatingBar(评分控件)
- Android中Handler引起的内存泄露
- Android中常见的资源获取方式
- android Activity和Fragment之间的数据传输的实现
- android换肤你所应该知道的一切------换肤精华
- 类对象,类名.class与类名.this
- android 很多牛群叉界面库github地址
- Android高级之xUtils框架(三)BitmapUtils
- Android中使用SurfaceView+MediaPlayer+自定义的MediaController实现自定义的视屏播放器
- Android Studio如何引用外部Library工程