View的工作原理一览

1 ViewRoot和DecorView

每个Activity都有一个Window,每个Window都有一个DecorView,DecorView是一个FrameLayout,一般可能含有一个垂直布局的LinearLayout用来放置标题栏和ContentView(受Activity主题的影响可能没有标题栏,只有ContentView)。

2.MeasureSpec

2.1 什么是MeasureSpec

MeasureSpec是一个int值,但是包含了两部分内容,最高的2位记录了Mode值,剩下的30位记录了Size值。整个MeasureSpec用来表示一个View在onMeasure时应该遵循的规则,当然你也可以任性的选择不遵守。通常一个View的MeasureSpec是由自己所在的ViewGroup的MeasureSpec和自身的LayoutParams来决定的,但是最底层的DecorView则不是这样,它是由ViewRootImpl根据WindowSize和DecorView的LayoutParams来决定的。
Mode的值有三种可能:EXACTLY,AT_MOST和UNSPECIFIED。分别对应以下内容:

  1. EXACTLY: 父ViewGroup根据自己的MeasureSpec和你的LayoutParams进行分析后认为你应该把MeasureSpec里的Size作为自己的Size,当然你可以在自定义View中选择拒绝,但是系统实现的View应该都遵循了这个规则。
  2. AT_MOST: 父ViewGroup根据自己的MeasureSpec和你的LayoutParams进行分析后认为你应该把MeasureSpec里的Size作为自己Size的上限,只要不超过这个大小就随你折腾。
  3. UNSPECIFIED: 父ViewGroup根据自己的MeasureSpec和你的LayoutParams进行分析后认为你爱咋咋地,想要多大就多大。这种情况一般出现在AdapterView(比如ListView)的item的heightMode,或者ScrollView的childView的heightMode。

2.2 MeasureSpec和LayoutParams

上面说过MeasureSpec是由自己所在的ViewGroup的MeasureSpec和自身的LayoutParams来决定的,那么这个关系究竟是怎么样的呢,让我们一探究竟:

switch (specMode) {
    // Parent has imposed an exact size on us
    case MeasureSpec.EXACTLY:
        if (childDimension >= 0) {  //childDimension根据LayoutParams里的layout_width和layout_height设置
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {   //MATCH_PARENT=-1
            // Child wants to be our size. So be it.
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {   //WRAP_CONTENT=-2
            // 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 = 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 be
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        }
        break;
    }
}

根据上面的源码,其实这个对应关系很简单:

  • parent的大小确定时,如果child有指定大小(即layout_width=”10dp”这种)或者为match_parent(就是说跟parent一样大,也相当于指明大小了),那child就是EXACTLY;如果是wrap_content,也就是最大为parent大小,为AT_MOST。
  • parent只有最大值时,如果child有指定大小,仍然遵循其意愿,并给他EXACTLY;如果是match_parent或者wrap_content的话,还是只能确定一个最大值,并不能确定准确的值,所以都是AT_MOST。
  • parent自己都不确定时,仍然还是如果child有指定大小,遵循其意愿,给EXACTLY。其他情况,连parent自己都不知道怎么办呢,也只能给个UNSPECIFIED了。。。
    所以看到这里也就很好理解说为什么在onMeasure时如果是EXACTLY就直接把MeasureSpec的Size当作实际的Size:显然是因为之前计算MeasureSpec时child自己指定size了,所以你就老实拿来用就好了嘛,毕竟是自己出的主意。

3 View的工作流程

一个View从构造函数到最后显示,毫无疑问有三个方法最为重要:measure,layout,draw。measure决定了这个View的宽高,layout决定了这个View在parent中的位置,draw决定了这个view好不好看。。。下面分别说说。

3.1 measure

measure本身是View中定义的final方法,无法被重写,其中定义了一系列measure的流程,但是在其中调用的onMeasure这个方法才是真正开始测量,所以我们重写这个方法就可以。View中的onMeasure已经实现了最基本的测量逻辑,EXACTLY和UNSPECIFIED都考虑到了,但是AT_MOST的处理和EXACTLY一样,这就意味着wrap_content的效果和match_parent一样,所以如果我们自定义View的话,务必要重写AT_MOST时的size。
ViewGroup的测量逻辑则要复杂一些。它需要先测量children的size,再由children的size根据一定的逻辑计算得出自己的size,自定义ViewGroup的话还需要考虑child的margin和自身的padding,否则最后得出的size一定不对。
在onMeasure结束后,我们需要把测量结果通过setMeasuredDimension方法进行设置,不然measure方法会报错的。

3.1.1 获取View宽高的方法

  1. 在onWindowFocusChanged中获取,关于onWindowFocusChanged的调用时机见问题2。
  2. View.post(Runnable)方法。Runnable会被加到MessageQueue的队尾,当执行到这里时,View也已经初始化好了。
  3. 利用ViewTreeObserver。给View的ViewTreeObserver添加OnGlobalLayoutListener。
  4. 手动调用View.measure()方法。此处有两个问题,见问题3和4。

3.2 layout

layout方法本身虽然可以被重写,但如果我们只关心布局位置的话,重写onLayout就可以了。而且对于view来说,这个方法不要管,只有当重写ViewGroup时,才需要管管自己children的布局。在ViewGroup中,通过调用child.layout(参数们…)来为child设定在ViewGroup中的位置,最终每个child得到一块矩形来绘制图案。

3.3 draw

draw方法本身遵循一套流程:

  1. 绘制背景 background.draw(canvas)
  2. 绘制自己 onDraw(canvas)
  3. 绘制children(dispatchDraw())
  4. 绘制Decoration(ScrollBars)

另外作者在文中提到了setWillNotDraw(boolean)这个方法,通过它能改变View内部的一个标志位。方法的意义在于如果View不具备绘制功能且标志位为true时,系统将进行相应优化。一般来说View都是需要绘制的,而ViewGroup一般不用,所以ViewGroup的标志位默认为true。但是如果你重写了ViewGroup的onDraw()方法,那么你可以通过setWillNotDraw(false)来关闭这个标志位。


问题

1.adjustViewBounds的用法
关于这一点网上很多帖子似乎写的都有点问题。我特意另外写了篇博客记录了一下,戳我查看


2.p190,onWindowFocusChanged的调用时机
从名字来看,onWindowFocusChanged在activity所在的window得到或失去焦点时会被触发。那么window什么时候会得到或失去焦点呢?

  1. 显示一个Activity时,新Activity的window会获得焦点,具体来说是Activity的onResume,View的measure,layout之后,draw之前调用。所以一般可以在onWindowFocusChanged方法里获取View的MeasuredSize。
  2. 显示一个Activity时,旧Activity的window会失去焦点,具体来说是onPause之后,onStop之前调用。
  3. 下拉系统的通知面板时,当前Activity的window会失去焦点。此时onPause并不会调用。
  4. 弹出dialog或者PopupWindow时,当前Activity的window会失去焦点。此时onPause并不会调用。

另外画面中Focus的转移不会对WindowFocus造成影响。


3.p192最下,作者说wrap_content的View可以手动调用measure方法得到size?
我认为如果一个View的layout是wrap_content的话,用measure方法应该是无法得到最终显示时的size的。作者在示例代码中用2^30-1作为View在measure时的最大值显然并不符合View在实际测量时的情况,一旦parent所剩空间较小,实际测量得到的size肯定会受到parent的限制,最终的值很有可能小于直接手动调用measure方法得到的值。当然为了证实我的想法,我也稍微写了个测试跑了一下,而结果也确实像我所说的。


4.p193,为什么说用unspecified去measure不能保证结果一定正确
当View在XML中直接指定了size时,用unspecified模式去measure这种View应该是没问题的,因为指定尺寸得到的MeasureMode一般都是Exactly,会直接用XML中指定的size作为最终的size。但是如果View的size是match_parent或者wrap_content,由于真正在测量的时候会受到父View尺寸的限制,或者被同级View挤占空间,所以真正测算出的Size应该是和直接用unspecified去测量不同。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值