深入理解View知识系列四-View的测量规则以及三大方法流程

Posted 刘镓旗

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解View知识系列四-View的测量规则以及三大方法流程相关的知识,希望对你有一定的参考价值。

通过前面几篇的深入分析,相信大家对View的理解已经很深了,我们说了setContentView背后做了什么,说了View从xml加载到通过WindowManager添加View后的一系列操作,说了android的事件由来,Canvas的由来等等,这一篇我们将来分析View绘制的三大方法,即measure、layout、draw的工作过程以及一些相关参数的产生规则

深入理解View知识系列一- setContentView和LayoutInflater源码原理分析

深入理解View知识系列二- View底层工作原理以及View的绘制流程

深入理解View知识系列三-Window机制、Canvas的由来、Android事件的由来

深入理解View知识系列四-View的测量规则以及三大方法流程


本篇你会学到什么

  • 什么时候需要重新onMeasure
  • View的测量规则
  • 三大方法的流程
  • 一些相关参数是如何产生的

上一篇回顾

  • 上一篇我们只要说了一堆WindowXXX都是干什么的,之前有什么区别和联系,因为涉及了太多,所以详细的请看上一篇

  • 还说了Window的类型和级别,简单说Window包括三种类型,分别是:应用Window、子Window和系统Window,应用Window的级别对应1-99,子Window的级别对应1000-1999,系统Window对应2000-2999,级别越高的会覆盖在级别底Window上方,在WMS添加Window时对各种Window进行了检查,如果是系统Window则需要申请权限;如果是子Window,要求父Window必须存在;在7.1以后还专门对Toast类型的Window做了限制

  • 我们还说了Surface是什么,它在ViewRootImpl中被创建,但是这只是一个空壳而已,是在绘制之前通过relayoutWindow方法去native层真正的创建Surface并填充到ViewRootImpl中的Surface里

  • 还说了Canvas是怎么产生的,它是通过Surface被创建的,也就是说其实真正的画布应该是Surface

  • 还有Android的事件是在哪里开发分发的,在ViewRootImpl中的setView方法中,创建了一个事件通道,并在添加Window的时候将这个通道传递到WMS中,WMS中也会创建一个通道并建立联系,然后WMS会通过InputManagerService将通道注册到native层,接着ViewRootImpl创建了一个用来接收事件的监听类,这个类叫WindowInputEventReceiver,所有的事件都是从这个类中开始分发,在分发的过程中会判断是按键事件还是触摸事件。

本编源码基于 7.1.1

测量规则

其实在View的三大方法中,最复杂的就是measure的过程,因为在这个过程中设计了多种模式的测量规则,下面进入正文,通过之前我们分析,我们知道View的真正绘制起始点是在ViewRootImpl中的performTraversals方法中,在这里会相应的调用performMeasure、performLayout、performDraw来分别对应View中的三大方法,在这三个方法中分别会调用View的measure、layout、draw方法,以measure为例,measure中会调用onMeasure方法,在onMeasure中会会所有的子元素进行measure过程,而所有的子元素又会重复这一操作,直到整个View树遍历完成,layout和draw的过程和measure是一样的,只不过他们中间执行的是onLayout和onDraw

理解MeasureSpec

这个类应该不会太陌生,它是View的静态内部类,从字面翻译是测量规格,不过确实也是这样。在重写一个View的onMeasure的时候,会给我们提供两个int参数,他们便是MeasureSpec,一个代表宽度的测量规格,一个代表高度的测量规格,这里说的MeasureSpec是通过MeasureSpec类生成的int值并不是MeasureSpec这个类。这个值是由父View传递到子View中的,MeasureSpec这个类通过计算后得出的int值是包装了View测量模式和尺寸的,这个得出的值一个32位的整型数,高两位代表了测量模式,后30位代表了测量尺寸,View的测量的模式分为3种,分别对应了UNSPECIFIED、EXACTLY、AT_MOST,我们可以通过这个int值获取到当前View的测量模式和尺寸。下面看一下这个类

MeasureSpec值的产生有两种情况,一种是顶层View,对于顶层的DecorView计算MeasureSpec值是通过屏幕的宽高和自身的LayoutParams来决定的,另一种普通View是通过父View的MeasureSpec值和View本身的LayoutParams来决定的

public static class MeasureSpec 
//用来标记需要位计算需要位数
private static final int MODE_SHIFT = 30;
//可以理解为用来计算的key
//0x3是16进制,对应的二进制是11,左移30位变成了
//1100 0000 0000 0000 0000 0000 0000 0000,最左端的数字代表符号位,0代表正数,1代表1负数
// 那么最终结果计算的10进制结果是 -1073741824
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
//无约束模式:计算的最终值是0
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
//精确模式:
//1左移30位后的值 0100 0000 0000 0000 0000 0000 0000
//转换成10进制是 1073741824
public static final int EXACTLY = 1 << MODE_SHIFT;
//最大模式:
//2左移30位后 1000 0000 0000 0000 0000 0000 0000
//转换成10进制是 -2147483648
public static final int AT_MOST = 2 << MODE_SHIFT;
//用来计算包装尺寸和测量模式的方法
//我们可以看到这里对传入的size做了限制,只能是0 - (1 << MeasureSpec.MODE_SHIFT) - 1)的值
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode)

if (sUseBrokenMakeMeasureSpec)
return size + mode;
else
return (size & ~MODE_MASK) | (mode & MODE_MASK);


public static int makeSafeMeasureSpec(int size, int mode)
if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED)
return 0;

//调用了上面的makeMeasureSpec
return makeMeasureSpec(size, mode);

//从measurespec值中获取测量模式
public static int getMode(int measureSpec)
//noinspection ResourceType
return (measureSpec & MODE_MASK);

//从measurespec值中获取尺寸
public static int getSize(int measureSpec)
return (measureSpec & ~MODE_MASK);

....

MeasureSpec这个类算上注释一共130多行代码,最主要的就上面几个常量和方法,可以看到MeasureSpec声明的几个常量,有一个用来计算的Key,还有三个测量模式,在注释中也给出了计算后的值,而且可以看到用来计算makeMeasureSpec的方法限制了传入的尺寸大小,只能是0 - (1 << MeasureSpec.MODE_SHIFT) - 1)的值,那么也就是说这个View的最大尺寸也就只能是它,因为需要将高两位让出来作为测量模式,那么后30位最大也就是全是1,也就是0011 1111 1111 1111 1111 1111 1111 1111,这个数换算后的值为1073741823。

三种测量模式:

UNSPECIFIED : 无约束模式:该模式下,父View不会施加任何的约束,也就是说想要多大给多大,一般情况下我们很少用到

EXACTLY : 精确模式:该模式下,一般对应于View的精确尺寸 或者 MATCH_PARENT,为什么说一般呢?这是因为还需要看父View是什么测量模式

AT_MOST : 最大模式:该模式下,子View可获得最大尺寸是父View的可用大小,一般对应于 WRAP_CONTENT

DecorView的测量规则

MeasureSpec值是通过父View传递过来的,而我们在之前的分析中说过,所有的View全部都存在ViewParent,哪怕是DecorView,它的Parent是ViewRootImpl,那么下面我们就先来看看ViewRootImpl对DecorView的测量规则,在ViewRootImpl中调用performMeasure之前会先计算出宽高MeasureSpec值。

源码位置:/frameworks/base/core/java/android/view/ViewRootImpl.java

private void performTraversals() 
....
//计算MeasureSpec值
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
//测量宽高并传入刚刚计算的MeasureSpec值
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
....

我们可以看到宽高的MeasureSpec值都是调用了getRootMeasureSpec方法,而传入的两个值一个是屏幕的尺寸,另一个是Window的LayoutParams中的宽高尺寸,其实也就是DecorView自己的LayoutParams,进去看一下这个方法

源码位置:/frameworks/base/core/java/android/view/ViewRootImpl.java

    private static int getRootMeasureSpec(int windowSize, int rootDimension) 
int measureSpec;
//判断宽高
switch (rootDimension)
case ViewGroup.LayoutParams.MATCH_PARENT:
//如果是MATCH_PARENT的情况
//创建measureSpec值,高度指定为屏幕宽高,模式为精确模式
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
//如果是WRAP_CONTENT
//创建measureSpec值,宽度为屏幕宽高,模式为最大模式
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
//如果是其他的情况,宽高为DecorView的LayoutParams的宽高,模式为精确模式
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;

//返回measureSpec值
return measureSpec;

深入理解View知识系列二- View底层工作原理以及View的绘制流程

深入理解View知识系列二- View底层工作原理以及View的绘制流程

深入理解View知识系列一- setContentView和LayoutInflater源码原理分析

深入理解View知识系列一- setContentView和LayoutInflater源码原理分析

深入理解View知识系列三-Window机制Canvas的由来Android事件的由来

深入理解View知识系列三-Window机制Canvas的由来Android事件的由来