继上篇内容,本文介绍 ViewTreeObserver 的使用,以及体会其所涉及的观察者模式,期间会附带回顾一些基础知识。最后,咱们简单聊一下 Android 的消息传递,附高清示意图,轻松捋清整个传递过程!
在开始下篇以前,有必要回顾一下上篇《解析 ViewTreeObserver 源码,体会观察者模式、Android消息传递(上)》说起的 ViewTreeObserver 的概念:
ViewTreeObserver 是被用来注册监听视图树的观察者,在视图树发生全局改变时将收到通知。这种全局事件包括但不限于:整个视图树的布局发生改变、在视图开始绘制以前、视图触摸模式改变时…
尚未看上篇,或者对上篇已经没印象的,建议先去看一下。
本篇内容较多,为节省篇幅,直接接着上篇继续讲。
#1. 一览 ViewTreeObserver 的大纲
先经过这部分来对类的构成进行粗略的认知,这样才能自如的应对后面的内容。本部分建议你们参考源码去看,这样会更直观、更容易理解,我参考的源码是 Android 6.0 的 SDK(api 23)。
查看类的大纲发现,该类看着挺复杂,但归纳起来看就很简单了,下面咱们按类别来一个个拿下。(windows 下 AS 查看类大纲的默认快捷键是 Ctrl + F12,大纲模式下还支持搜索以快速定位)
1.1 类的接口
ViewTreeObserver 经过接口回调的方式实现观察者模式,当接收到通知后,经过接口的回调方法告知程序相应的事件发生了。在 ViewTreeObserver 中,包含了 11 个接口,对应着11中观察事件,以下图:java
这里写图片描述
1.2 类的方法
介绍完接口,下面总结一下 ViewTreeObserver 类的方法,大概分为如下四种类型。
添加监听:addOnXxxListener(OnXxxListener)
移除监听:removeOnXxxListener(OnXxxListener)
分发事件:dispatchOnXxx()
其余方法:checkIsAlive()、isAlive()方法等
“其余方法”在上篇差很少提过了,如今咱们着重看前三类方法,下面简称 add、remove 和 dispatch 方法。
查看类可知,对于前面那张图所展现的每个接口,都有与其对应的 add、remove、dispatch 方法。举个例子吧,以 OnGlobalLayoutListener(全局布局监听) 为例,那么与其对应的三类方法就是:
addOnGlobalLayoutListener(OnGlobalLayoutListener listener);
removeOnGlobalLayoutListener(OnGlobalLayoutListener victim);
dispatchOnGlobalLayout();
这么说,一共有11个接口,那么与之对应的 add、remove、dispatch 方法也就分别有11个,没错,咱们经过大纲查看时就是这样。这个你们自行去类中查看,或者根据上面举的例子类推一下,我就再也不贴代码了。
下面补充一点与方法的使用相关的内容:
虽然说 ViewTreeObserver 包含这么多方法,可是系统并无对咱们开放全部的API。咱们能够验证一下,在程序代码中先经过 getViewTreeObserver() 获取 View 的 ViewTreeObserver 对象,而后使用该对象分别调用这几类方法,分别模糊匹配 add、remove 和 dispatch,而后查看IDE的智能提示。
先看看调用 add 和 remove 方法:android
如图所示,add 和 remove 方法只分别只有8个,并无11个。其中remove中最后一个方法removeGloableOnLayoutListener已通过时了,在 API 16 取代它的方法是removeOnGloableLayoutListener。查看removeGloableOnLayoutListener方法可知,其直接调用了removeOnGloableLayoutListener方法,功能上没区别。区别在于名字,确定是初期方法命名不合理,后来想改,但又不能直接修改或删除。因此,在一开始就设计好一些规范,并在开发过程当中按照代码规范开发,是有多重要…
既然都是8个,那各自少掉的3个呢?进 ViewTreeObserver类一看,发现不让外部调用的是与OnWindowShownListener、OnComputeInternalInsetsListener、OnEnterAnimationCompleteListener接口对应的add、remove方法,这几个方法之因此在程序中没法访问,是由于被添加了 @hide标签,这是什么?
@hide 意味着被其标记的方法、类或者变量,在自动生成文档时,将不会出如今API文档中对开发者开放,可是系统能够调用,这就解释了为何咱们只能访问其中8个方法了。其中有些要求对版本有要求,例如添加或移除 OnWindowAttachListener,须要 API 18 以上,而咱们一版在开发时会选择最低适配 Android 4.0,也便是 API 为 14,这样一来就没法使用。
其实,能够经过反射访问被 @hide 标记的域。可是不建议这么作,由于 Google 在添加该标记时解释道:
We are not yet ready to commit to this API and support it,so @hide。
既然没有准备好提交这个API并支持他,也就意味着 Google 可能会随时修改这些方法(虽然可能性很小),因此出于保险仍是不要经过反射使用的好(我的观点)。
再来看看 dispatch 方法可用的有哪些:
喔,竟然只有3个!查看 ViewTreeObserver 类,发现其他8个不可访问的方法没有声明修饰符,那就是默认的 default 类型。咱们知道,default 修饰的方法只能在同一包内可见,ViewTreeObserver.java 在 android.view 包下,咱们在程序中显然没法访问。
#2. 接口和方法的做用
为了保持内容的连贯和思路的清晰,在上一节只是介绍了 ViewTreeObserver 类的构成,并无解释具体的做用。下面趁热打铁,看一下各自的做用。此处仍以 OnGlobalLayoutListener(全局布局监听) 接口对应的三个方法为例,其余接口的原理都同样,再也不赘述。
2.1 OnGlobalLayoutListener 接口:windows
注释很精确的归纳了其做用:当全局布局状态,或者视图树的子view可见性发生改变时,将调用该回调接口。
该接口包含了一个回调方法 onGlobalLayout(),咱们在程序中就是经过覆写该方法,实现本身的逻辑,具体使用将在实战部分介绍。
##2.2 addOnGlobalLayoutListener 和 removeOnGlobalLayoutListener 方法
仍是将这俩好基友放在一块介绍,我直接简称 add 和 remove 了。
在程序中,经过 add 方法添加一个对 view 布局发生改变的监听,传入 OnLayoutGlobalListener 接口对象,覆写接口的 onGlobalLayout() 方法,系统会将咱们传入的 OnLayoutGlobalListener 存在集合中。
当经过 add 监听以后,咱们须要在适当的时候经过 remove 方法移除该监听,防止屡次调用。一般在覆写的 onGlobalLayout() 时方法中调用 remove 方法移除监听。
##2.3 dispatchOnGlobalLayout 方法
dispatch 方法通常都由系统调用,咱们不须要去关心。在 dispatchOnGlobalLayout 方法中,会遍历存放 OnLayoutGlobalListener 对象的集合,而后调用 OnLayoutGlobalListener 对象的 onGlobalLayout() 方法,通知程序该事件发生了。
[注:上述代码中存放 OnGlobalLayoutListener 的集合 CopyOnWriteArray,值得了解一下,会让你受益不浅。本打算讲的,但限于篇幅只好做罢,感兴趣的能够上网了解一下]
3.使用姿式(实战)
到目前为止,咱们对 ViewTreeObserver 的认识仍停留在概念级别,终于等到了实战环节,验收本身学习成果的时刻到了。
##3.1 使用流程
咱们仍是先以 OnGlobalLayoutListener 为例介绍一下标准使用流程,这里须要结合上篇所学内容。
经过 View 对象的 getViewTreeObserver() 获取 ViewTreeObserver 对象。
检测 observer 是否可用。不可用的话再获取一次
定义 OnGlobalLayoutListener 接口对象 listener,并覆写 onGlobalLayout() 回调方法。若是只监听一次,记得在方法最后调用 observer.removeOnGlobalLayoutListener() 移除监听,避免重复调用。
observer.addOnGlobalLayoutListener(listener) ,至此完成对该 View 对象的全局布局监听。
附上一张不完整的流程图,使用在线工具 ProcessOn 画的,挺好用的,推荐给你们:
##3.2 实际使用
上面只是标准使用流程,实际开发中咱们不会这么多约束,下面看两个实际的例子。值得注意的是,咱们一直所说的 View,实际上指的是 View 及其子类,好比 LinearLayout、ImageView、TextView等。
① 在 onCreate() 中获取 View 的高度
在开发中,咱们有时须要在 onCreate() 方法中拿到一个view(任何View的子类)的宽高,这时候咱们直接经过 getWidth() 和 getHeight() 方法获取的值均为 0,由于真正的值要在 view 完成 onLayout() 以后才能够返回。这时,咱们就能够借助 OnGlobalLayoutListener 监听 view 的布局改变,当 view 布局发生改变且完成 onLayout() 后,就会调用 dispatchOnGlobal() 通知咱们,接下来就会走到回调方法 onGlobalLayout() 中去。
view.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
//1. do sth you want
width = view.getWidth();
height = view.getHeight;
Log.d("OnGlobalLayoutListener", "width:" + width + ",height:" + height);
//2. remove listener
// api 小于 16
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN){
//使用过期方法
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
// api >= 16
else {
//使用替换方法
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
}
});
代码已经写得很清楚了,下面再补充两点:
由于每次都是经过 getViewTreeObserver() 直接获取 View 当前的observer,因此就没再使用 isAlive() 判断。
在介绍 remove 方法时,提到 removeGlobalOnLayoutListener() 方法已通过时,取而代之的是 removeOnGlobalLayoutListener() 方法。后者是在 JELLY_BEAN 版本才引入的,对应的 api 是 16。因为我当前程序的 minSdkVersion 为 14,因此须要根据实际版本号分开处理。其实,在本例中,是不须要分开处理的,咱们直接调用已过期的 removeGlobalOnLayoutListener() 方法便可,由于在前面分析过,两者仅仅是名字上的差异。但我之因此这么作,就是为了演示如何判断版本号并据此选择对应的方案。毕竟有些方法系统只提供了高版本的实现,以前的版本就没有对应的方法,此时咱们就必须本身实如今低版本上的功能了。
除了 OnGlobalLayoutListener,咱们还能够借助 OnPreDrawListener 实现上述功能。同时,OnPreDrawListener 还能够帮助咱们实现 View 初始化加载时的动画效果。下面再举个例子,供你们参考以熟悉api,实际的开发中须要灵活运用。
② View 的初始化加载动画
直接上代码,在 onCreate() 方法中:
添加属性动画:
最终效果:
---------------------
https://blog.csdn.net/my_truelove/article/details/52653072
api