View绘制2-onMeasure
来源:互联网 发布:js confirm样式修改 编辑:程序博客网 时间:2024/06/05 00:10
在自定义View的绘制过程中,重写onMeasure,onLayout,onDraw三个函数实现了View的外观形象,加上onTouchEvent等等函数实现的重载视图行为,构建出一个完整的自定义View体系。
在Android体系中,以on来头的onXXX函数,多以在Activity,Service,View中出现,一般都是使用了设计模式里面的模板设计模式。定义好一套模板流程,然后通过重写模板方法实现自定义效果。
作用
- onMeasure指定绘制View的大小
- onLayout 指定绘制View的位置
- onDraw 实现绘制过程
从系统源码看起
View的onMeasure实现过程
onMeasure( ) - 封装外部调用
|
setMeasuredDimension( ) -实现把测绘到的占用大小设置给View
|
getDefaultSize( )-比较Min大小和测绘大小做出抉择
|
getSuggestMinimumWidth( )-得到Min大小
分别的实现代码如下
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //根据测绘大小的widthMeasureSpec和heightMeasureSpeac来确定子控件的大小 setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
具体实现方法封装在setMeasuredDimension()
中
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { //一系列程序健壮性判断 代码省略.... mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; }
public static int getDefaultSize(int size, int measureSpec) { //size默认大小 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; }
getDefaultSize()方法返回MeasureSpec中的specSize,这个specSize就是View的测量大小,因为View的最终大小是在layout()
中确定的,但是specSize的大小几乎所有时候都是和layout()
中确定的最终大小相等
protected int getSuggestedMinimumWidth() { //mMinWidth可以通过xml布局设置android:minSize指定,也可以通过View.SetMinSize指定 return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }
getSuggestedMinimumWidth()方法,android:minWidth
如果有设置的话就设置宽度为这个值,但是还存在一种情况就是设置了Background的情况,这种情况下需要比较Background和minWidth的大小。
上面就是通过widthMeasureSpec 和 heightMeasureSpec设置占用空间大小的过程,追本溯源widthMeasureSpec和heightMeasureSpec有是从何而来呢?
MeasureSpec是什么
测量规格,MeasureSpec有一个32位的int数表示,作用
- 包含父布局对子布局View的测量要求
- 包含测量模式和测量数据
- 可以表示宽、高
1和3都好理解,不好理解的是第2点,查看MeasureSpec的源码
public static class 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; /** * 获取测量模式 */ public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); } /** * 获取测量数据 */ public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); } /** * 生成器 */ public static int makeMeasureSpec(int size, int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } static int adjust(int measureSpec, int delta) { return makeMeasureSpec(getSize(measureSpec + delta), getMode(measureSpec)); } }
这里源码做了一些便于理解的删减。定义了一个标记为MODE_MASK=3<<30;获取测量模式
public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); }
和获取测量数据
public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); }
可知这个int类型的数据32位,前2位表示测量模式,后30位表示测量数据
其中
- 00表示
MeasureSpec.UNSPECIFIED
表示父布局对子布局不做任何限制,子控件想要多大就多大 这种模式一般不深究,一般系统用来对ListView和ScrollView这些控件使用。 - 01表示
MeasureSpec.EXACTLY
表示精确控制 View的大小就是getSize()返回的值 - 10表示
MeasureSpec.AT_MOST
表示由子布局自己指配但是最大不能超过getSize( )的参考值
从ViewGroup得到MeasureSpec的过程
从measureChildWithMargins
方法看起
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
包含5个参数:
子View,父WidthMeasureSpec
、父HeightMeasureSpec
、已经使用的宽度、已经使用的高度
执行过程:
1. 首先拿到LayoutParams
2. 获取View的WidthMeasureSpec
3. 获取View的HeightMeasureSpec
4. 测绘child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; switch (specMode) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
执行过程
1. 获取父specMode 和 specSize
2. 获取水平(垂直)方向最大可用空间 size
3. 通过specMode 和 childDimension(view的空间大小)来确定子View的MeasureSpec
这里我们知道,子View的控件的占用大小是由子View和他的ViewGroup共同决定的,具体关系可以参考下表:
通过表可以清除的发现,只要子View是具体的值那么不管父ViewGrounp的测量模式他都是EXACTILY + 子View具体的值
自定义View我们还需要做什么
根据源码得到的表中加粗加斜体这两项,逻辑上存在问题,比如一个控件如果指定他的高度为android:height=wrap_content
那么就应该由他自己来设置高度的大小,而不是去匹配他父ViewGroup的大小。设置为 AT_MOST& parentLeftSize
wrap_content和match_parent没有区别,实际上在系统自定义控件如TextView ImageView
的onMeasure
方法也是改写过的,wrap_content
模式下让子View自己去指配自己的大小。重写onMeasure()
实现代码:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //遵循模板方法,其他逻辑不变 super.onMeasure(widthMeasureSpec , heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpceSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec); int heightSpceSize=MeasureSpec.getSize(heightMeasureSpec); //判断如果是At_Most情况下做相应的处理 if(widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){ setMeasuredDimension(mWidth, mHeight); }else if(widthSpecMode==MeasureSpec.AT_MOST){ setMeasuredDimension(mWidth, heightSpceSize); }else if(heightSpecMode==MeasureSpec.AT_MOST){ setMeasuredDimension(widthSpceSize, mHeight); } }
在上面的代码中,只需在wrap_content的时候给mWidth mHeight
设置一个默认的高度即可,至于具体的值需要具体分析。
逻辑上还存在不对的地方
分析如果子View是math_parent
父ViewGroup为AT_MOST的情况是否存在。
- ViewGroup是一个确定的值,那么根据第一横排的信息他一定是EXACTILY类型,错误
- ViewGroup是wrap_content,那么子View是match_parent,子View的大小取决于父ViewGroup的大小,但是父ViewGroup的大小又是根据他包括的内容确定,互相持有对方的依赖,相当于操作系统的死锁,所以这种情况不复存在。
- ViewGroup是match_content,那么ViewGroup的父布局也一定是match_content的(如果是wrap_content和确定的值可以参考上面1,2分析),由此类推ViewGroup的父布局的父布局也一定是match_content,一次类推….,但是到最终肯定会出现一个根布局不是match_content类型,因为手机屏幕是客观存在且有大小的一个事物,所以ViewGroup是match_content这种类型也不存在。
总之,没有任何一种情况可以让View是match_parent类型而且View的父布局是AT_MOST的。
最后
measure过程是View三大流程中最复杂的一个,在measure完成后通过getWidthMeasure() getHeightMeasure()
方法可以获取到正确的宽高。但是在一些特殊情况下,系统需要多次measure才能确定最终的宽高,就种情况在onMeasure方法中拿到的测量宽高可能不准确。一个好的习惯是在onLayout()
中获取View的测量宽高和最终宽高。
如果有一个需求是在Activity一启动就去获取一个View的宽高。可能会想到在生命周期方法onCreate()/onStart()/onResume()
中,但是measure和生命周期的方法并不同步,如果在特定的生命周期方法中获取而measure还没执行完拿到的值很可能是0.
通过onWindowFocusChanged()
在View绘制完成后,焦点肯定会改变,同时如果频繁进行onResume和onPause的话onWindowFocusChanged()
也会执行
代码:
@Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus) { int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); } }
在view绘制线程post一个消息在尾部,view绘制完成后会执行这个runnable
@Override protected void onStart() { super.onStart(); view.post(new Runnable() { @Override public void run() { int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); } }); }
参考:《Android开发艺术探讨》-任玉刚
- View绘制2-onMeasure
- View绘制之---onMeasure()
- View绘制---onMeasure()重写
- 自定义View-2-重写onMeasure
- Android的View绘制过程,onMeasure()方法介绍
- Android中View的绘制过程 onMeasure方法
- Android中View的绘制过程 onMeasure方法简述
- Android中View的绘制过程 onMeasure和onLayout()方法
- Android中View的绘制过程 onMeasure方法简述
- View.onMeasure()
- View.onMeasure()
- Android中View的绘制过程 onMeasure方法简述 附有自定义View例
- Android中View的绘制过程 onMeasure方法简述 附有自定义View例子
- Android中View的绘制过程 onMeasure方法简述 附有自定义View例子
- Android中View的绘制过程 onMeasure方法简述 附有自定义View例子
- Android中View的绘制过程 onMeasure方法简述 附有自定义View例子
- Android中View的绘制过程 onMeasure方法简述 附有自定义View例子
- Android中View的绘制过程 onMeasure方法简述 附有自定义View例子
- javascript实现中国地图
- 第 8 章 界定问题
- leetcode---Search Insert Position
- bzoj2958: 序列染色
- windows的服务中的登录身份本地系统账户、本地服务账户和网络服务账户修改
- View绘制2-onMeasure
- MyEclipse+Git使用详解
- PHP函数之十进制、二进制、八进制和十六进制转换函数
- ${pageContext.request.contextPath} :JSP取得绝对路径方法
- Linux Shell系列教程之(五)Shell字符串
- 【Spring学习笔记】Spring框架的IoC容器学习笔记
- iptables导致nginx反向代理不可用的解决方案
- 未解之谜
- 详解DHCP Snooping