Android之自定义View的死亡三部曲之Measure

作者博客

http://www.cherylgood.cn

前言

我们在上一篇Android之View的诞生之谜分析了从Activity的创建到View开始执行测量、布局、绘制之前所经历的一些事情以及处理状态栏的一些小技巧等,如果你也想知道的话,不妨点击一下-Android之View的诞生之谜哦,或许你面有你想要的呢

你的三围是多少?

我们在上一章节Android之View的诞生之谜中分析了系统从启动actiivty到调用setContentView加载我们的xml布局文件,但是此时我们的View是不可见的,因为我们还没有对其进行如下操作:

  1. 测量:我还不知道你的三围呢(你要占多少屏幕),我怎么能轻易让你出场呢—-测量工作

  2. 布局:你把三围给我了,但是你还没告诉我你要站在那里,对位置的分布有什么要求—-行布局操作

  3. 绘制:好,现在我要给你花点妆,美美地出场—-绘制操作

OK,我们在上篇中分析道,系统加载好布局资源之后,会触发ViewRootImpl的performTraversals方法,在该方法内部会开始执行测量、布局、绘制的工作,也就是我们的死亡三部曲的开始。

我们来看ViewRootImpl的performTraversals方法的源码,为了简洁,我只留下关键的代码。

可以看到,里面按顺序调用了performMeasure、performLayout、performDraw三个方法,也就是对应的测量、布局、绘制,再继续深入之前,我们需要先补充点能量,对MeasureSpec已了解的同学可以跳过下面一段。

MeasureSpec

MeasureSpec 是个什么东西呢?其实MeasureSpec是View内部的一个静态类,在编写测量控件的代码中一定能见到其美丽的身影,他的诞生是那么的无私->为何辅助view的测量能够更好的进行。

我们可以先从官方文档中初步了解一下:

A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec represents a requirement for either the width or the height. A MeasureSpec is comprised of a size and a mode. There are three possible modes:

MeasureSpec对象中封装了从父对象传递给孩子的布局所需数数据(你要成为我的子控件,你要在我里面占位置,你先要知道我有多少空间吧?)。每一个MeasureSpec对象包含了对于宽度和高度的描述(也就是父控件告诉子控件,我有多大点地和我对于空间的使用策略等)。 MeasureSpec由大小和模式组成。有三种可能的模式:

  1. UNSPECIFIED 父控件还不知道子控件的大小,对子控件也没有任何约束,说你想占多少地方就占吧。(这个一般很少用到)

  2. EXACTLY 这种状态下的控件的大小是明确的。

  3. AT_MOST 父控件对子控件说,我还不知道你的大小,我给你自由,我的地方是这么大,你按你的意愿来,但最大也只能跟我一样大了,注意哦,可能需要二次测量,后面会讲到。

为了更好的理解三种模式,我们可以看一下实际测量的源码里是如何处理的

呃我想想,好吧,从ViewGroup.measureChild方法入手吧,这个是viewGroup测量下面的childView的方法,看源码,解释我就直接写源码里了,便于阅读:

上面的代码经过分析就很好理解了,我们继续看getChildMeasureSpec方法的源码,看里面是怎么测量出child的宽、高的MeasureSpec的呢?源码不多,一百多行,我们一起来看下

小结:从上面我们了解的MeasureSpec是用来辅助测量view的大小的一个辅助类,我们分析的MeasureSpec的mode和size是根据parent和child相互决定的。下面是我网上收集的一个MeasureSpec图片

我们继续回到开头的ViewRootImpl.performMeasure源码上分析,在1、2两步我们获得了DecorView的MeasureSpec,然后通过传入MeasureSpec开始了我们的测量之旅。那么我们继续看3里面是如何测量的。

补充:在Android Touch事件分发机制详解之由点击引发的战争我们分析过DecorView实际是集成自FrameLayout,那么我们看frameLayout,发现frameLayout并没有measure方法,但是它又继承自ViewGroup。所以肯定是ViewGroup了,然而,ViewGroup也没找到measure方法,那么继续查看其parent 类View,哈哈,在view中被我找到了吧,我们看代码。只保留了关键的一句,不要打我。

从上面我们看到,里面调用了onMeasure方法,这里要注意了:

  1. 我们的ViewGroup并没有重写View的onMeasure方法,而但是我们android开发中的四大布局 FrameLayout、LinearLayout、RelativeLayout、AbsoluteLayout都是通过继承ViewGroup来实现的,而且里面也重写onMeasure方法。

  2. 所以我们可以分两种情况来看待:1、布局类控件;2、一般展示类控件;

  3. 自定义控件过程中,一般情况下我们也需要通过重写onMeasure来做一些特殊处理。

接下来我们可以从两个方向去分析onMeasure方法:

  1. View.onMeasure

  2. 布局类的,例如. FrameLayout.onMeasure

那么我们先从View.onMeasure吧,毕竟他才是最原始的。

View.onMeasure源码如下,虽然就几句,但是做的事情可不少哦!

  1. 调用setMeasuredDimension设置view的大小

  2. 调用getDefaultSize获取View的大小,

  3. getSuggestedMinimumWidth获取一个建议最小值

调用顺序:

onMeasure-> 

setMeasuredDimension-> 

getDefaultSize-> 

getSuggestedMinimumWidth

我们逆过来分析一下,首先getSuggestedMinimumWidth这个是什么呢?我们点进源码看一下:

里面代码很少,判断是否有背景,没有的话返回mMinWidth,这个mMinWidth其实就是android:minWidth=""属性设置的值。也就是假设没设置有背景的情况下,就以设置minWidth值为准。

如果设置有背景,那么就去背景的实际宽度与minWidth中大的一个。

getMinimumWidth()可以理解成背景的bitmap形式下的实际宽度值。

然后我们看getDefaultSize这个方法,这是一个静态工具方法,他返回的是view的大小:

第3点很重要,你有没有发现,AT_MOST与EXACTLY模式下,返回的值居然是一样的,那岂不是wrap_content与match_parent是等效的?不要打我,我可没骗你哦

那么,我们实际开发中肯定要处理这个情况,所以我们在自定义直接继承View来实现的控件时,一定要自己处理这两种情况哦。否则wrap_content属性是等效于match_parent的哦

之后就到我们的setMeasuredDimension方法了,前面说了,setMeasuredDimension是设置view的大小的。我们进去看一下源码

我们继续看setMeasuredDimensionRaw方法

小结:

测量view的顺序为measure->onMeasure-> setMeasuredDimension-> setMeasuredDimensionRaw,由setMeasuredDimensionRaw最终保存测量的数据。

以上是测量一个view的过程,这样子我们的view的测量工作就结束了。

接下来我们来看下布局类frameLayout是如何测量的,我们同样看FrameLayout的onMeasure方法

至此,View的三围已经测出来了,本篇略长,测量在android的死亡三部曲中是第一部,也是里面最复杂、重要的一部,快看下你的三围是多少吧!

总结:

View的测量,重点是抓住MeasureSpec在其中体现的作用,MeasureSpec贯穿了View测量的整个过程,明白其的作用,也就明白了View测量的一半知识了。

View的Layout将在下一章进行分析

关注微信公众号「码个蛋」,每天更新优质文章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值