Android自定义View基础之onMeasure详解
2016-11-01 11:31
381 查看
上一篇文章,介绍了MeasureSpec类的基础知识,包括三种模式及子view的measureSpec的生成过程,搞懂了这个,那么我们下面就可以进入到onMeasure流程中了,同时,也会在上一篇的基础上做一下关于自定义view wrap_content和match_parent的补充。
通过源码可以看出,onMeasure流程为:
通过setMeasureDimension设置measure阶段view的宽和高
通过getDefaultSize方法获取默认的宽度和高度
通过getSuggestedMinimumHeight/width方法获取建议的最小宽度或高度值
下面,一次进入对应的方法,看一下内部实现。
其中mBackground为view的背景,如果有背景的话,可以获取到其最小高 宽度值
mMinHeight 此值可以在xml中通过minHeight设置,也可以通过view的setMinimumHeight()方法设置
该方法用于获取view的宽度或高度值,通过switch,可以看出有两种返回,分别如下:
measureSpec的mode为unspecified时,返回的就是view的最小宽度或高度值,这种情况一般很少见
measureSpec的mode为exactly或at_most时,返回的是view measureSpec中的specSize
由此,也得知,在measure阶段中,view的大小宽高,由其measureSpec中的specSize决定的。
由图可以看到,当子view的宽高为warp_content时,不管父容器的specMode为exactly还是at_most,其占据的空间都为parentLeftSize,显然,这不是我们所期望的,那么,我们就应该对wrap_content的情况在onMeasure阶段进行特殊处理。
如果在xml中,宽高有一个被设置为wrap_content,那么就将该值设置为默认值,另一个采用系统测量的specSize即可,代码示例如下:
好了,现在就解决了自定义view宽高为wrap_content的问题了,不过。。。。
不过呢,仔细推理一下,就会发现,原来。。。搜噶。
问:什么情况下,父容器的specMode为at_most呢
答:两种情况:
a. 当父容器的layoutParams为wrap_content,系统给父容器的specMode为at_most
b. 父容器的layoutParams为match_parent,系统给父容器的specMode为at_most
下面,来分别分析一下这两种情况。
A. 父容器为wrap_content,子view为match_parent,子view为包裹内容,想和父容器一样大,而父容器又不知道自己知道多大,那么两者就陷入死循环了,谁也决定不了谁,这种case理论上可能出现,但是实际中一般是不可取的。
B. 既然父容器是match_parent,那么爷(父容器的父容器)应该为什么呢?下面,排除法,列举一下
爷容器为wrap_content,此case同A,不可取,不正确的
精确的值,比如200dp。试想一下,如果爷容器为精确的值,爷mode为exactly,父为match_parent,那么父的mode就不可能为at_most,而应该是exactly,所以这种也是不可能的
爷容器为match_parent,爷容器的mode可能为exactly或at_most,那么,分别分析一下
爷容器mode为exactly,这种情况下,父容器的mode应该为exactly,而不是at_most,所以这种case是不可能的。
爷容器mode为at_most,大小为match_parent,那么父容器的mode为at_most,这是唯一可能存在的情况。仔细分析一下,这样就会陷入一个死循环,子view大小是match_parent,mode为at_most,父容器大小是match_parent,mode为at_most,爷容器大小也是match_parent,mode为at_most,如此下去,直到rootview,如果根view的大小为match_parent,那么其对应的mode应该为exactly,所以这种case也是不可能的。
由上面推测发现,如果子view的大小为match_parent,并且父容器的mode为at_most,那么此时子view的mode也为at_most其大小为parentLeftSize,这种情况是不合理的,不可取的。
执行步骤:measureChildren()—>measureChild()—>child.measure()
ViewGroup是一个抽象类,没有实现onMeasure()方法,那么其子类就应该根据自身的特点去测量子view的,测量好了子view的大小,那么其自身的大小也就明确了。比如linearLayout根据布局方向完成测量,relativeLayout根据子view的相对位置完成测量,等等。
onMeasure源码分析
onMeasure
/** * <p> * Measure the view and its content to determine the measured width and the measured height. This method is invoked by {@link #measure(int, int)} and should be overriden by subclasses to provide accurate and efficient measurement of their contents. * </p> * @param widthMeasureSpec horizontal space requirements as imposed by the parent.The requirements are encoded with {@link android.view.View.MeasureSpec}. * @param heightMeasureSpec vertical space requirements as imposed by the parent. The requirements are encoded with {@link android.view.View.MeasureSpec}. * */ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
通过源码可以看出,onMeasure流程为:
通过setMeasureDimension设置measure阶段view的宽和高
通过getDefaultSize方法获取默认的宽度和高度
通过getSuggestedMinimumHeight/width方法获取建议的最小宽度或高度值
下面,一次进入对应的方法,看一下内部实现。
getSuggestedMinimumWidth(height)
/** * Returns the suggested minimum height that the view should use. This * returns the maximum of the view's minimum height * and the background's minimum height * ({@link android.graphics.drawable.Drawable#getMinimumHeight()}). * <p> * When being used in {@link #onMeasure(int, int)}, the caller should still * ensure the returned height is within the requirements of the parent. * * @return The suggested minimum height of the view. */ protected int getSuggestedMinimumHeight() { return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); }
其中mBackground为view的背景,如果有背景的话,可以获取到其最小高 宽度值
mMinHeight 此值可以在xml中通过minHeight设置,也可以通过view的setMinimumHeight()方法设置
getDefaultSize
/** * Utility to return a default size. Uses the supplied size if the * MeasureSpec imposed no constraints. Will get larger if allowed * by the MeasureSpec. * * @param size Default size for this view * @param measureSpec Constraints imposed by the parent * @return The size this view should be. */ 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; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; } return result; }
该方法用于获取view的宽度或高度值,通过switch,可以看出有两种返回,分别如下:
measureSpec的mode为unspecified时,返回的就是view的最小宽度或高度值,这种情况一般很少见
measureSpec的mode为exactly或at_most时,返回的是view measureSpec中的specSize
由此,也得知,在measure阶段中,view的大小宽高,由其measureSpec中的specSize决定的。
解决自定义view之wrap_content问题
问题及MeasureSpec规则回顾
首先回顾一下上篇文章中,绘制的measureSpec形成图,如下由图可以看到,当子view的宽高为warp_content时,不管父容器的specMode为exactly还是at_most,其占据的空间都为parentLeftSize,显然,这不是我们所期望的,那么,我们就应该对wrap_content的情况在onMeasure阶段进行特殊处理。
解决方案
如果在xml中,宽高均为wrap_content,需要设置view的宽高为mWidth mHeight如果在xml中,宽高有一个被设置为wrap_content,那么就将该值设置为默认值,另一个采用系统测量的specSize即可,代码示例如下:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //获取宽高的size mode int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSise = MeasureSpec.getSize(heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); //宽高都为wrap_content if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mWidth, mHeight); } else if (widthMode == MeasureSpec.AT_MOST) { //宽度为wrap_content setMeasuredDimension(mWidth, heightSise); } else if (heightMode == MeasureSpec.AT_MOST) { //高度为wrap_content setMeasuredDimension(widthSize, mHeight); } }
好了,现在就解决了自定义view宽高为wrap_content的问题了,不过。。。。
引发的另一个“血案”
上面成功解决了wrap_content问题,不过,不知道注意到没有,在子view的layoutParams为match_parent,父容器的specMode为at_most时,子view的specMode也为at_most,其size也为parentLeftSize,那这种情况下,子view match_parent的case也会走到刚才解决wrap_content的代码中,那么本来宽高想要填充父容器,现在却被设置了默认值,这样也不合理的,有木有,有木有!!!不过呢,仔细推理一下,就会发现,原来。。。搜噶。
问:什么情况下,父容器的specMode为at_most呢
答:两种情况:
a. 当父容器的layoutParams为wrap_content,系统给父容器的specMode为at_most
b. 父容器的layoutParams为match_parent,系统给父容器的specMode为at_most
下面,来分别分析一下这两种情况。
A. 父容器为wrap_content,子view为match_parent,子view为包裹内容,想和父容器一样大,而父容器又不知道自己知道多大,那么两者就陷入死循环了,谁也决定不了谁,这种case理论上可能出现,但是实际中一般是不可取的。
B. 既然父容器是match_parent,那么爷(父容器的父容器)应该为什么呢?下面,排除法,列举一下
爷容器为wrap_content,此case同A,不可取,不正确的
精确的值,比如200dp。试想一下,如果爷容器为精确的值,爷mode为exactly,父为match_parent,那么父的mode就不可能为at_most,而应该是exactly,所以这种也是不可能的
爷容器为match_parent,爷容器的mode可能为exactly或at_most,那么,分别分析一下
爷容器mode为exactly,这种情况下,父容器的mode应该为exactly,而不是at_most,所以这种case是不可能的。
爷容器mode为at_most,大小为match_parent,那么父容器的mode为at_most,这是唯一可能存在的情况。仔细分析一下,这样就会陷入一个死循环,子view大小是match_parent,mode为at_most,父容器大小是match_parent,mode为at_most,爷容器大小也是match_parent,mode为at_most,如此下去,直到rootview,如果根view的大小为match_parent,那么其对应的mode应该为exactly,所以这种case也是不可能的。
由上面推测发现,如果子view的大小为match_parent,并且父容器的mode为at_most,那么此时子view的mode也为at_most其大小为parentLeftSize,这种情况是不合理的,不可取的。
ViewGroup的measure
viewgroup是一个抽象类,他提供了测量child的measureChildren方法,在该方法里,又会调用measureChild方法,在measureChild方法里,会执行child.measure()方法,就回到我们前面分析的流程中了。执行步骤:measureChildren()—>measureChild()—>child.measure()
ViewGroup是一个抽象类,没有实现onMeasure()方法,那么其子类就应该根据自身的特点去测量子view的,测量好了子view的大小,那么其自身的大小也就明确了。比如linearLayout根据布局方向完成测量,relativeLayout根据子view的相对位置完成测量,等等。
结束语
至此,关于自定义view宽高为wrap_content的情况解决了,onMeasure()的调用过程也简单的分析了一下,欢迎指出不足和问题,不定时再更新自己的理解和补充。相关文章推荐
- Android自定义ViewGroup的OnMeasure和onLayout详解
- Android 自定义View基础 onMeasure & onLayout
- 安卓自定义View基础06-View的onMeasure(),onDraw()方法详解以及Padding的处理
- Android中View的绘制过程 onMeasure方法简述 附有自定义View例子
- android 自定义view中onMeasure()理解
- 自定义View之onMeasure()详解
- Android 自定义View onMeasure理解
- android 自定义View onMeasure
- Android中View的绘制过程 onMeasure方法简述 附有自定义View例子
- Android 自定义视图 onMeasure,MeasureSpec 思路详解
- Android中View的绘制过程 onMeasure方法简述 附有自定义View例子
- Android中View的绘制过程 onMeasure方法简述 附有自定义View例子
- android 自定义view中onMeasure()理解
- Android学习自定义View(五)——自定义ViewGroup及其onMeasure()的理解
- Android 自定义view 和 onMeasure方法介绍
- 【转】Android中View的绘制过程 onMeasure方法简述 附有自定义View例子
- Android之自定义View,你需要了解和掌握的onMeasure测量规则
- Android中View的绘制过程 onMeasure方法简述 附有自定义View例子
- Android 自定义View 中的OnMeasure的用法
- Android View中的onMeasure()方法详解