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

通过前面几篇的深入分析,相信大家对View的理解已经很深了,我们说了setContentView背后做了什么,说了View从xml加载到通过WindowManager添加View后的一系列操作,说了Android的事件由来,Canvas的由来等等,这一篇我们将来分析View绘制的三大方法,即measure、layout、draw的工作过程以及一些相关参数的产生规则
本篇你会学到什么
  • 什么时候需要重新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值
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值