鉴于是首篇讲解自定义view流程,之前也在网上搜了一些博主的博客看了看,都是大同小异,今天抽时间自己总结一下,分享一下自己的感悟,也算是一篇笔记。
(本篇为开头篇,稍微讲述一下有关的东西)
View的绘制流程:
a: Measure测量一个View的大小(onMeasure) (本篇讲解Measure 测量)
b: Layout 摆放一个View的位置(onLayout)
c: Draw 绘制 View的内容 (onDraw)
举个例子:假如你想盖一所房子,首先你需要确定你盖的房子占地是多少平米,高几米(Measure)
确定好尺寸后,你需要定位,找个合适的地方盖这所房子,是想盖天安门旁边,还是盖到老家后院。(Layout)
当你确定好房子的宽高大小,占地尺寸后(Measure后),也想好房子在哪里盖(老家后院 Layout后),那就该
搭建房子,开始盖房子了(Draw)。
附上两张截图,看一下Log打印 了解一下执行顺序:
重写三个方法
控制台打印
应该会有人有两个疑问:
1:为什么在view.class类中,绘制方法用的是onMeasure 而不是 Measure?
2:为什么控制台打印中 有两个 onMeasure?为什么会走两次
回答:
1:
继承的View中的measure方法
由图可见,因为measure方法是final类型的 所以无法被继承,而layout 和 draw方法是void类型。那为什么是重新onMeasure方法呢?是因为官方在measure方法上方的注解中提到的:看下图
measure方法上方注解
注解的大概意思是:这方法(measure() )是一个查找view多大,一个父类提供了约束信息在宽高方面(也就是说子类是由父类提供约束信息的)它实际测量的工作 是在 onMeasure()中完成的.
所以第一个问题解答完毕!
问题2:答案(为什么打印有两个onMeasure() ),的确,onMeasure() 方法执行了两遍,其实layout 与 draw方法也有可能会走多遍,至于走不走多遍,是根据视图的复杂性决定的,视图越复杂,测量,定位,绘制三个方法走的次数就会增多。
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
进入正题: Measure (onMeasure (测量View大小)
测量View大小方法
Measure方法中调用了OnMeasure方法,实际测量是在OnMeasure方法中完成的,所以我们直接看OnMeasure方法
可以看到OnMeasure方法有两个参数 widthMeasureSpec 和 heightMeasureSpec。
widthMeasureSpec :父容器对子类的宽度的期望
heightMeasureSpec:父容器对子类的高度的期望
onMeasure中调用的setMeasuredDimension方法
进入onMeasure方法中 看到在onMeasure方法中 调用了 setMeasuredDimension方法。
然后我们继续进入到setMeasuredDimension方法中 去看一下里面有什么操作。
setMeasuredDimensionRaw方法中的内容
可以看到在setMeasuredDimension方法中 仅仅只是把测量出来的宽高,然后在setMeasuredDimensionRaw()方法中进行了保存。
所以 测量的流程就是 Measure --> onMeasure -- > setMeasuredDimension -- > setMeasuredDimensionRaw.
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
测量:至于测量view大小的measure中 有一个必不可少的系统自带的工具类 MeasureSpec类
MeasureSpec类:(获取view的测量模式以及view想要绘制的大小)
1:measureSpec描述了父View 对 子View大小的期望,里面包含测量模式和大小
2:MeasureSpec类 将测量模式与测量大小组合到一个32位的int类型的数值中,高两位表示测量模式,低30位代表测量大小,在计算中使用位运算的原因是提高并优化效率
3:可以从MeasureSpec类中提取测量模式与大小,内部是采用了位运算的方式,也可以通过MeasureSpec的静态方法把大小和模式合成,该方法内部只是简单的相加。
measure方法中打印宽度
上图举了个例子,打印了一下宽度的值,得到的这个数据 就是测量模式与大小,拼在一起的数据。应着了上面的第二点,将测量模式和测量大小组合到一个32位的int类型的数值中。
测量模式:(分为三种)
1:EXACTLY(精准值模式),当控件的layout_width或layout_height属性定位具体数值时,或指定为match_parent属性时,系统使用的是EXACTLY模式
2:AT_MOST(最大值模式),当空间的layout_width或layut_height属性指定为warp_content时,控件大小一般随着控件的子控件或者内容的变化而变化,这时控件的尺寸只要不超过父控件允许的最大尺寸即可。
3:UNSPECIFIED(未定义的模式),它不指定其大小的测量模式,view想多大就多大,通常会在自定义view时使用。
注:View的onMeasuer方法只支持EXACTLY模式,所有如果在自定义控件的时候不重写onMeasure()的话,就只能使用EXACTLY模式,且控件只能响应你指定的具体宽高值或者match_parent属性,如果要让自定义的View支持wrap_content属性,那就必须重新onMeasure方法来指定wrap_content时的大小。
measureSpec类的作用是获取测量模式以及大小,所以这个类中有两个方法,getMode()和getSize()
在xml中使用时,宽的大小已指定
打印了一下宽的size大小和宽的mode模式
问题:
1:为什么我代码中打印log 要 mode>>30? 为什么打印结果是1 ?
2:为什么size是600? 在xml中宽是200dp呀
答:
1:因为刚刚说到measure()方法中的两个宽高值,是int类型 32位的 由2位的测量模式和30位的大小组合而成,所以需要将值往前移30位,拿到前两位也就是测量模式。所以打印的是结果为 1 这个1 可以看上图 红框框住的三个测量模式,源码中可以看到,精准模式EXACTLY 为1 ,AT_MOST 最大值模式为2,UNSPECIFIED未定义模式为0,所以我们打印的测量模式为 1,相对应的就是精准模式,在看我们在xml中设置的宽的大小为200dp,是指定的大小,所以就是精准模式。也就是打印的值为 1。
2:xml中为200dp 为什么size是600? 因为这个600是px 也就是像素,遵循一个公式,
px(像素) = dp * dpi(屏幕像素密度) / 160 (固定为160,不会改变) ,现在已知px(像素) ,我的模拟器dpi为480,求dp数 不难吧?
模拟器dpi