Android View ViewGroup 的measure过程
来源:互联网 发布:淘宝网裙子 编辑:程序博客网 时间:2024/05/17 12:19
首先扯点别的:“光阴似箭,日月如梭”,这句话小学就知道了,随着年龄的增长,越来越觉得如此,人生如白驹过隙。毕业工作快一年了,但是感觉自己Android方面的基础知识还是不扎实,所以看看开发艺术探索,巩固提高自己。
View 的measure过程:measure 过程决定了View的宽和高。measure完成以后,就可以通过getMeasuredWidth和getMeasuredHeight来获取View测量后的宽高了,几乎所有的情况下它都等于View的最终宽高。
理解MeasureSpec
为了更好的理解View的measure过程,首先要了解MeasureSpec这个概念。MeasureSpec代表一个32位的int值,高两位代表SpecMode,低30位表示SpecSize。SpecMode是指测量模式,而SpecSize是指在某种测量模式下的规格大小。MeasureSpec参与了View的measure过程,在measure过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后再根据这个measureSpec来测量出View的宽高。
MeasureSpec类是View类中的一个静态内部类,下面看一下MeasureSpec中一些常量和方法。
private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; //三种测量模式 public static final int UNSPECIFIED = 0 << MODE_SHIFT; public static final int EXACTLY = 1 << MODE_SHIFT; public static final int AT_MOST = 2 << MODE_SHIFT; //生成的MeasureSpec public static int makeMeasureSpec(int size, int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { //返回生成的MeasureSpec,就是size和mode的按位与的结果。 return (size & ~MODE_MASK) | (mode & MODE_MASK); } }
三种测量模式说明
- UNSPECIFIED :父容器没有给当前View强加任何约束,View想多大就多大,此种情况一般用于系统内部。
- EXACTLY : 父容器已经决定了当前View的准确的大小。这个时候View的最终大小就是SpecSize所指定的值,它对应于LayoutParams中的match_paent和具体的数值。
- AT_MOST :父容器指定了一个SpecSize,View的大小不能大于这个值,它对应于LayoutParams中的wrap_content。
View的onMeasure方法
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //... }
view的measure过程是在onMeasure方法中完成的,这个时候我就想问了:方法中的的两个参数widthMeasureSpec和heightMeasureSpec是从哪里来的呢?答案是从父容器传递过来的。View的measure过程是由ViewGroupde measure过程传递而来,在ViewGroup的measure过程中会生成子View的MeasureSpec并调用子View的measure方法
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
然后子View的measure方法会调用onMeasure方法。然后就把widthMeasureSpec和heightMeasureSpec这两个参数传递过来了。
onMeasure(widthMeasureSpec, heightMeasureSpec);
ViewGroup的measure过程中是通过getChildMeasureSpec方法生成子View的MeasureSpec。方法不去细究,下表中给出结论。
public static int getChildMeasureSpec(int spec, int padding, int childDimension)
图片来自:http://www.jianshu.com/p/e3049dd24505
注意:表中parentSize是指父容器中目前可使用的大小。childSize是指子View指定的具体数值大小,比如140dp。
开始View的measure过程。
View的measure过程由其measure方法完成,measure方法是一个final类型的方法,子类无法重写。在measure方法中会调用View的onMeasure方法,因此只要看onMeasure方法的实现即可。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
setMeasuredDimension方法会储存测量的宽高。因此只需要看看getDefultSize方法。
public static int getDefaultSize(int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; //在这两种模式下都返回specSize。 case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; } return result; }
如果第二个参数measureSpec类型是AT_MOST或者EXACTLY,就返回specSize。这个specSize就是测量后的宽高。如果measureSpec类型是UNSPECIFIED类型,就直接返回第一个参数size,即宽高分别为getSuggestedMinimumWidth()和getSuggestedMinimumHeight()的返回值。
看一下getSuggestedMinimumWidth()方法的实现,getSuggestedMinimumHeight()的原理是一样的。
protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }
如果View没有设置背景(Drawable),宽度就是mMinWidth ,对应于android:minwidth这个属性指定的值。如果这个属性不指定,那么mMinWidth默认为0。如果View设置了背景,那么View的宽度就是就是mMinWidth和mBackground.getMinimumWidth()两者中间的最大值。看一下mBackground.getMinimumWidth()的实现。
public int getMinimumWidth() { final int intrinsicWidth = getIntrinsicWidth(); return intrinsicWidth > 0 ? intrinsicWidth : 0; }
如果Drawable的原始宽度不为0,就返回Drawable的原始宽度,否则返回0。
总结:从getDefaultSize方法看,View的宽高由specSize决定。直接继承View需要重写onMeasure方法,并设置wrap_content时的自身大小,因为在getDefultSize方法中,AT_MOST模式下返回的是specSize。根据上面的表格可以看出,在AT_MOST模式下,specSize就是parentSize,也就是父容器最大可用空间。这种效果跟使用match_parent效果一样。那如何解决呢?就是在重写onMeasure 方法的时候,如果View使用了wrap_content(测量模式是AT_MOST),那我们就提供一个默认的宽高。
//默认大小private static final int DEFAULT_SIZE = 200; @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec,heightMeasureSpec); int widthSpecModel = MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecModel = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); if (widthSpecModel == MeasureSpec.AT_MOST && heightSpecModel == MeasureSpec.AT_MOST) { setMeasuredDimension(DEFAULT_SIZE, DEFAULT_SIZE); } else if (widthSpecModel == MeasureSpec.AT_MOST) { setMeasuredDimension(DEFAULT_SIZE, heightSpecSize); } else if (heightSpecModel == MeasureSpec.AT_MOST) { setMeasuredDimension(widthSpecSize, DEFAULT_SIZE); } }
上面就是View的测量过程了。
ViewGroup的测量过程。
ViewGroup过程除了完成自己的measure过程以外,还要遍历调用所有子元素的measure方法,各个子View再递归执行这个过程。和View不同的是,ViewGruop是一个抽象类,没有重写onMeasure方法,但是它提供了一个叫measureChilden 的方法。
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren; //遍历所有的子View, for (int i = 0; i < size; ++i) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { //测量子View的大小。 measureChild(child, widthMeasureSpec, heightMeasureSpec); } } }
measureChildren方法中会通过调用measureChild对每一个子View进行测量。
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
measureChild方法会根据ViewGroup的测量规格和子View的LayoutParams生成子View的测量规格,然后传递给View的measure方法进行测量。
ViewGroup是一个抽象类,并没有实现onMeasure方法,需要子类(比如LinearLayout,RelativeLayout)去实现onMeasure方法。下面通过LinearLayout的onMeasure方法来分析ViewGroup的measure过程。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mOrientation == VERTICAL) { //竖直方向测量 measureVertical(widthMeasureSpec, heightMeasureSpec); } else { //水平方向测量 measureHorizontal(widthMeasureSpec, heightMeasureSpec); } }
ViewGroup的测量过程比较复杂,细节就不去细究了。查看竖直布局的LinearLayout测量过程measureVertical的大致逻辑:系统遍历子View执行测量过程,并用一个变量mTotalLength来储存LinearLayout在竖直方向上的初步高度,每测量一个子View,mTotalLength就会累加,增加的部分包括子元素的高度以及子元素在竖直方向上的margin等。当子元素测量完毕后,LinearLayout会测量自己的大小。
获取View的宽高
方法1:Activity/View#onWindowFocusChanged方法。
onWindowFocusChanged方法的意思是View已经初始化完毕了,宽高已经准备好了,这个时候获取宽高没问题。需要注意的是onWindowFocusChanged方法会被调用多次,当Activity得到焦点或者失去焦点的时候均会被调用一次。具体来说,当Activity继续执行和暂停执行时,onWindowFocusChanged都会被调用,如果频繁的进行onResume和onPause那么onWindowFocusChanged也会被频繁的调用。典型代码如下。
@Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus) { int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); } }
方法2:view.post(runnable)
通过post将一个runnable投递到消息队列的尾部,然后等待Looper调用次runnable的时候,view已经初始化好了。典型代码如下:
@Override protected void onStart() { super.onStart(); view.post(new Runnable() { @Override public void run() { int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); } }); }
方法3:ViewTreeObserver
使用ViewTreeObserver的众多回调可以获取view的宽高。比如使用OnGlobalLayoutListener接口,当View树的状态发生改变或者View树内部的Veiw的可见性发生改变的时候,OnGlobalLayoutListener的onGlobalLayout方法会被回调,因此这是获取View宽高的一个很好的时机。需要注意的是伴随着View树的状态改变等,onGlobalLayout方法会被调用多次。典型代码如下。
@Override protected void onStart() { super.onStart(); ViewTreeObserver observer = view.getViewTreeObserver(); observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); } }); }
结尾:摘抄自《Android开发艺术探索》。又是一个疲惫的周末,晚上好好休息,明天好好上班。
- Android View ViewGroup 的measure过程
- View和ViewGroup 的measure过程
- Android中View和ViewGroup的measure和layout过程分析
- Android View measure过程
- 浅析Android View的Measure过程
- Android应用程序窗口View的measure过程
- Android View的Measure过程(一)
- Android View的measure过程详解
- View的measure过程
- View 的 Measure 过程
- view的measure过程
- View的measure过程
- Android View的Measure
- 018.View的Measure过程
- View的Measure过程解析
- Android中View的MeasureSpec以及Measure的过程
- 源码解析Android中View的measure量算过程
- 源码解析Android中View的measure量算过程
- jQuery resize() 方法
- 线性回归之scikit-learn
- MFC傻瓜式教程
- PAT-A-1037. Magic Coupon (25)
- 一些开源项目
- Android View ViewGroup 的measure过程
- sql server2008连接代码(Java)
- 在centos和redhat上安装docker
- NumberFormat
- hive2.1.1 部署安装
- scikit-learn:CountVectorizer提取tf都做了什么
- Anroid异步消息处理(一)
- HDU1869
- bash变量赋与默认值