继上篇内容,本文介绍 ViewTreeObserver 的使用,以及体会其所涉及的观察者模式,期间会附带回顾一些基础知识。最后,我们简单聊一下 Android 的消息传递,附高清示意图,轻松捋清整个传递过程!

在开始下篇之前,有必要回顾一下上篇《解析 ViewTreeObserver 源码,体会观察者模式、Android消息传递(上)》提及的 ViewTreeObserver 的概念:

ViewTreeObserver 是被用来注册监听视图树的观察者,在视图树发生全局改变时将收到通知。这种全局事件包括但不限于:整个视图树的布局发生改变、在视图开始绘制之前、视图触摸模式改变时…

还没有看上篇,或者对上篇已经没印象的,建议先去看一下。

本篇内容较多,为节省篇幅,直接接着上篇继续讲。

#1. 一览 ViewTreeObserver 的大纲

先通过这部分来对类的构成进行粗略的认知,这样才能自如的应对后面的内容。本部分建议大家参考源码去看,这样会更直观、更容易理解,我参考的源码是 Android 6.0 的 SDK(api 23)。

查看类的大纲发现,该类看着挺复杂,但概括起来看就很简单了,下面我们按类别来一个个拿下。(windows 下 AS 查看类大纲的默认快捷键是 Ctrl + F12,大纲模式下还支持搜索以快速定位)
1.1 类的接口

ViewTreeObserver 通过接口回调的方式实现观察者模式,当接收到通知后,通过接口的回调方法告知程序相应的事件发生了。在 ViewTreeObserver 中,包含了 11 个接口,对应着11中观察事件,如下图:


这里写图片描述
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 方法:

如图所示,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 接口:

注释很精确的概括了其作用:当全局布局状态,或者视图树的子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

解析 ViewTreeObserver 源码(下)的更多相关文章

  1. 解析 ViewTreeObserver 源码(上)

    主要内容:ViewTreeObserver 是被用来注册监听视图树的观察者,在视图树发生全局改变时将收到通知.本文从 ViewTreeObserver 源码出发,带你剖析 ViewTreeObserv ...

  2. Python解析器源码加密系列之(二):一次使用标准c的FILE*访问内存块的尝试

    摘要:由于近期打算修改Python解释器以实现pyc文件的加密/解密,出于保密的要求,解密之后的数据只能放在内存中,不能写入到文件中.但是后续的解析pyc文件的代码又只能接受FILE*作为入参,所以就 ...

  3. HtmlAgilityPack --解析Html源码

    最近项目需要从网络上抓取一下数据解析Html源码,奈何正则表达式难写,于是网上搜索找到了“ HtmlAgilityPack”类库,敏捷开发,果然效率非同寻常. 在此做笔记,写下心得,顺便给自己总结一下 ...

  4. Jsoup解析网页源码时常用的Element(s)类

    Jsoup解析网页源码时常用的Element(s)类 一.简介 该类是Node的直接子类,同样实现了可克隆接口.类声明:public class Element extends Node 它表示由一个 ...

  5. 用Beautiful Soup解析html源码

    #xiaodeng #python3 #用Beautiful Soup解析html源码 html_doc = """ <html> <head> ...

  6. 二十三、并发编程之深入解析Condition源码

    二十三.并发编程之深入解析Condition源码   一.Condition简介 1.Object的wait和notify/notifyAll方法与Condition区别 任何一个java对象都继承于 ...

  7. Django(49)drf解析模块源码分析

    前言 上一篇分析了请求模块的源码,如下: def initialize_request(self, request, *args, **kwargs): """ Retu ...

  8. 【转】Android 源码下利用jni编译自己的项目(参考系统development/samples/SimpleJNI)

    原文网址:http://blog.csdn.net/qiuxiaolong007/article/details/7860481 记于正文前:环境是ubuntu10.10,android 源码是2.0 ...

  9. mvc5 解析route源码实现自己的route系统

    Asp.net mvc5 解析route源码实现自己的route系统   url route 路由系统的责任是找到匹配的路由,创建路由数据,并将请求分配给一个处理程序. 选择动作是 MVC 的处理程序 ...

随机推荐

  1. mysql数据库授权

    mysql数据库授权所有人 grant all privileges on *.* to 'root'@'%' identified by 'password'; flush privileges; ...

  2. html5的audio实现高仿微信语音播放效果

    效果图 前台大体呈现效果图如下: 点击就可以播放mp3格式的录音.点击另外一个录音,当前录音停止! 思路 关于播放动画,这个很简单,我们可以用css3的逐帧动画来实现.关于逐帧动画,我之前的文章也写过 ...

  3. ThinkPHP页面跳转success与error方法

    首先是控制器中,可以使用下代码: config配置如下: 'TMPL_ACTION_ERROR' => 'Public:error', // 默认错误跳转对应的模板文件 'TMPL_ACTION ...

  4. windows快速进入安装目录

    ctrl+R 输入%LOCALAPPDATA%\+应用名字 %LOCALAPPDATA%\composer

  5. 000 Security的计划

    可重用的,企业级的,认证和授权模块 1.主要涉及的模块 2.最终的目标 3.开发的项目模块结构

  6. 2017-2018-1 20179202《Linux内核原理与分析》第三周作业

    一.mykernel 实验 : 1.深度理解函数调用堆栈: 上周已经一步步地分析过含有变量的函数调用时堆栈的变化,现在对堆栈框架进行一些补充,以以下程序为例: int main() { ... g(x ...

  7. linux下安装node踩坑总结

    1.在node官网下载linux二进制文件(确定文件的类型)本文以二进制文件为例 2.放入linux的对应目录下之后: tar -xvf node-v10.15.3-linux-x64.tar.xz ...

  8. Vue项目History模式404问题解决

    本文主要解决Vue项目使用History模式发布到服务器Nginx上刷新页面404问题.(由于每个项目的情况都不尽相同,本方案已经完美解决本在所使用项目,具体情况可能还需要修改.) 1.项目背景分析 ...

  9. XamarinAndroid组件教程设置自定义子元素动画(二)

    XamarinAndroid组件教程设置自定义子元素动画(二) (9)打开MainActivity.cs文件,为RecylerView的子元素设置添加和删除时的透明动画效果.代码如下: …… usin ...

  10. class关键字

    class的数据类型为function,可以看做构造函数的另一种写法.事实上,类的所有方法都定义在类的prototype属性上面.一.声明class class Animal { constructo ...