前言

一个View从创建到被绘制到屏幕上,需要完成measure(测量)、layout(布置)、draw(绘制)三个步骤,分别对应View中的measure()、layout()、draw()三个方法。网上关于这三个方法的源码解析文章有很多,而且一般情况下也不会去重写它们(measure()方法还无法覆盖),因此本文不打算将其作为重点。本文以及接下来的几篇文章会详细介绍和编程人员关系更大的onMeasure()、onLayout()与onDraw()的具体实现方法,以及过程中会涉及到的一些知识。

MeasureSpec

View中的onMeasure()方法是这样的:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {...}

可以看到,它的参数是widthMeasureSpec与heightMeasureSpec两个int值。这两个参数实质上是由View的静态内部类MeasureSpec管理的两个特殊的“对象”(并非真正的对象),包含了父view关于子view应当如何测量自身给出的“指示”。为了提升效率,Android系统采用位运算的方式,将模式SpecMode(2位)与尺寸SpecSize(30位)拼接成了一个int值,并传递这个int值作为测量时使用的参数。
MeasureSpec类的实现基本都是依靠位运算,没什么实质性内容。直接上一些结论:
(1)SpecMode分三种:UNSPECIFIED、EXACTLY、AT_MOST。UNSPECIFIED表示不指定具体测量模式,EXACTLY表示父View希望子view的尺寸取精确值(即等于SpecSize),AT_MOST表示父View希望子view的尺寸不超过SpecSize。
(2)使用MeasureSpec.getMode(int measureSpec)与MeasureSpec.getSize(int measureSpec)获取SpecMode与SpecSize。
(3)使用MeasureSpec.makeMeasureSpec(int size,int mode)生成一个measureSpec值。

onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法

下面回到onMeasure()方法。顾名思义,这个方法是在该view需要测量自身时调用的。具体来说,当这个view的父view对其调用measure()方法时,onMeasure()方法会在过程中被调用。下面分别看看View与ViewGroup分别应当怎么实现这个方法。

View的onMeasure()实现

view只需要根据自身情况,计算出自己的尺寸就可以了。步骤如下:
(1)使用MeasureSpec.getMode()与MeasureSpec.getSize()获取父view要求的SpecMode与SpecSize。
(2)根据上面的参数确定自己的实际尺寸(width与height)。一般来说,如果SpecMode是EXACTLY,那么直接取尺寸值=SpecSize即可。如果SpecMode是AT_MOST,那么就需要根据自身特点计算出一个尺寸值,并保证最终尺寸值不超过SpecSize。当然了,你也可以完全无视父view的要求,自顾自地进行测量,不过这种方式显然是不推荐的。
(3)使用setMeasuredDimension(int measuredWidth, int measuredHeight)设置最终测量尺寸。这个方法被调用之后,view的getMeasuredWidth()方法与getMeasuredHeight()方法才能生效(在之前调用会返回0)。
实际上,对于不那么复杂的自定义view,View类提供的默认实现已经可以满足大部分需求了。下面看看它是怎么做的:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

getSuggestedMinimumWidth()与getSuggestedMinimumHeight()是根据view是否设置了BackgroundDrawable确定一个最小尺寸值。重点看一下getDefaultSize()方法:

public static int getDefaultSize(int size, int measureSpec) {
//size是view根据自身需求提供的一个尺寸值,measureSpec来自父view。//最终尺寸值int result = size;//获取父布局要求的测量模式与测量尺寸int specMode = MeasureSpec.getMode(measureSpec);int specSize = MeasureSpec.getSize(measureSpec);switch (specMode) {//如果模式为UNSPECIFIED则不作限制case MeasureSpec.UNSPECIFIED:result = size;break;//AT_MOST直接当做EXACTLY处理case MeasureSpec.AT_MOST://如果模式为EXACTLY,则根据父布局要求确定最终尺寸值case MeasureSpec.EXACTLY:result = specSize;break;}return result;
}

解释见代码注释。这里需要记住的是,如果使用这个方法计算尺寸值的话,AT_MOST模式不会生效。AT_MOST一般是在view的layout_width与layout_height为WRAP_CONTENT时使用的。因此,如果想要自定义view支持WRAP_CONTENT属性,就必须自己对AT_MOST的情况作出处理。

ViewGroup的onMeasure()实现

不同于View,ViewGroup需要负责子view的测量。具体来讲,就是为子view提供合适的MeasureSpec,并调用子view的measure()(注意,不是onMeasure())方法。至于自身的尺寸,则需要结合更高一层的父view的指示以及子view的情况来确定。
为了给子view提供合适的MeasureSpec,ViewGroup中提供了一个getChildMeasureSpec()方法,下面看看它的实现:

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 uscase 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 uscase MeasureSpec.AT_MOST:if (childDimension >= 0) {// Child wants a specific size... so be itresultSize = 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 becase MeasureSpec.UNSPECIFIED:if (childDimension >= 0) {// Child wants a specific size... let him have itresultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// Child wants to be our size... find out how big it should beresultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;resultMode = MeasureSpec.UNSPECIFIED;} else if (childDimension == LayoutParams.WRAP_CONTENT) {// Child wants to determine its own size.... find out how big it should beresultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;resultMode = MeasureSpec.UNSPECIFIED;}break;}//noinspection ResourceTypereturn MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

英文注释是自带的,已经很详细了。参数spec是高层view提供的MeasureSpec,padding为需要扣除的padding部分尺寸,childDimension为子view需求的尺寸(一般直接传MarginLayoutParams.width)。实质上,这个方法就是综合考虑了高层view的指示以及低层view的需求,分9种情况构建了一个合适的MeasureSpec。下面的图来自Android View系统解析(下) ,任玉刚:

总结

关于onMeasure()方法的实现差不多就这些内容了。可以看出,MeasureSpec是父view与子view沟通的桥梁。实现onMeasure()方法的关键点就在于如何响应父view的MeasureSpec,以及如何为子view构建合适的MeasureSpec。

自定义View之onMeasure()方法相关推荐

  1. Android中自定义view的onMeasure()方法详谈

    背景 理解MeasureSpec MeasureSpec 情况分析 结合图例分析 总结 A little bit of progress every day!Come on! 背景 首先关于自定义vi ...

  2. Android横竖屏切换View设置不同尺寸或等比例缩放的自定义View的onMeasure解决方案(2)...

    Android横竖屏切换View设置不同尺寸或等比例缩放的自定义View的onMeasure解决方案(2) 附录文章1以xml布局文件方式实现了一个view在横竖屏切换时候的大小尺寸缩放,实现这种需求 ...

  3. Andoid自定义View的OnMeasure详解和自定义属性

    一,OnMeasure详解 Android开发中偶尔会用到自定义View,一般情况下,自定义View都需要继承View类的onMeasure方法,那么,为什么要继承onMeasure()函数呢?什么情 ...

  4. android中对View的onMeasure()方法的理解

    在android开发中,很多人对自定义View是望而生畏,我也一样,但这又是向高级进阶的必经之路,主要是对View里面的很多方法不知道怎么理解,其中一个就是onMeasure()方法,网上有很多这样解 ...

  5. 【Android】自定义view之onMeasure

    1 onMeasure什么时候会被调用 onMeasure方法的作用是测量控件的大小,当我们创建一个View(执行构造方法)的时候不需要测量控件的大小,只有将这个view放入一个容器(父控件)中的时候 ...

  6. Android自定义View的实现方法,带你一步步深入了解View(四)

    不知不觉中,带你一步步深入了解View系列的文章已经写到第四篇了,回顾一下,我们一共学习了LayoutInflater的原理分析.视图的绘制流程.视图的状态及重绘等知识,算是把View中很多重要的知识 ...

  7. 自定义View之onMeasure()

    1.自定义View之onMeasure() 2.onMeasure实例分析

  8. android自定义view案例,Android自定义View的实现方法实例详解

    一.自绘控件 下面我们准备来自定义一个计数器View,这个View可以响应用户的点击事件,并自动记录一共点击了多少次.新建一个CounterView继承自View,代码如下所示: 可以看到,首先我们在 ...

  9. 对View的onMeasure方法理解

    我们知道View在屏幕上显示出来要先经过measure和layout. 在调用onMeasure(int widthSpec, int heightSpec)方法时,要涉及到MeasureSpec的使 ...

最新文章

  1. Windows Azure Mobile Services增加了对 Android的支持并扩展其适用范围至东亚地区
  2. st 串口烧写工具 芯片_STM32芯片的几种烧写方式简介
  3. php vimrc配置文件,vim技巧:我的 .vimrc 配置文件,详解每一个配置项的作用
  4. python单链表实现具体例子_Python实现数据结构线性链表(单链表)算法示例
  5. linux 内存泄露 工具,Linux Kernel模块内存泄露分析
  6. SQL.H 通过此文件寻找sqlAPI编程的一种捷径
  7. 4月份SCCM/ConfigMgr的5大看点
  8. ZTree相关使用的例子
  9. 2017-05-24
  10. SpringBoot加载自定义yml文件
  11. BZOJ1862: [Zjoi2006]GameZ游戏排名系统
  12. 计算机实验导论,《计算机科学导论》实验
  13. 南华大学计算机全国排名多少位,华南理工大学全国排名第几位
  14. 直流电源输入防反接保护电路总结
  15. python34 pandas_python 3 科学计算之pandas入门(一)
  16. 把notepad++添加到右键打开方式(解决 打开方式 没有notepad++编辑器情况)
  17. EasyX 图片透明设置
  18. 【java之汉字转拼音】
  19. 如何设置两个元件靠近后不变绿不报警
  20. 【SQLite预习课3】SQLite 的常用语法

热门文章

  1. 微信小程序多图片上传全栈实战
  2. 计算机应用怎么样多项选择,【2017年整理】12春学期《计算机应用基础》在线作业第三次多项选择.doc...
  3. MACD改良抓牛神器 通达言指标公式 副图 源码 无加密 无未来
  4. 命令行运行jar包的常见方式
  5. 快速学习-Sleuth--链路追踪
  6. 计算机视觉的数学基础
  7. php编程实训,php编程实训模板
  8. Nginx 启动配置文件
  9. 如何理解先验概率和后验概率
  10. 拼接php图片路径,利用ThinkPHP5中的获取器,实现图片URL拼接