欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

三个维度,不太一样的View事件分发机制(理论概念)

程序员文章站 2022-03-30 18:46:31
我想说一说不太一样的View事件分发机制,这篇是事件分发机制的理论概念篇。...

个人博客原文,这里的排版阅读效果更好哦~


每一个Android开发者,都绕不开View的事件分发,理解好View的事件分发机制,有利于我们解决各种与设备触摸交互的问题,同时也利于我们实现更复杂、炫酷的自定义View效果。

关于Android View的事件分发机制,笔者打算分为三篇文章来描述,分别为:

(1)Android View事件分发机制之概念理论篇

(2)Android View事件分发机制之源码解读篇

(3)Android View事件分发机制之滑动冲突实战解决篇

本文是第一篇:Android View事件分发机制之概念理论篇


为何称为事件分发


且看一段伪代码:

@Override
public boolean onTouch(View view, MotionEvent event) {
    // 处理我们的手势触摸交互
   
    return true;
}

这个方法相信大家都不陌生,我们在处理触摸屏幕手势交互时,就会重写onTouch方法,onTouch方法中,第二个参数叫MotionEvent,其义为手势行为事件。而通过onTouch的回调调用栈及源码可发现,onTouch被回调前,经过了多重“关卡”的判断、验证、处理,MotionEvent对象也被层层传递与处理,最终才执行到了当前View的onTouch方法。即:

我们与设备产生的交互,即触摸(点击、长按、拖动等)手势,再到应用界面的响应,整个过程都是一个MotionEvent对象的传递以及对其处理的过程,MotionEvent即手势行为事件,故称为:事件分发。


事件分发机制理论


首先,我们先了解几个概念:

注:尽管ViewGroup也是一种View,但在下面的描述中,我们把ViewGroup和View区分开,ViewGroup可以包含其他的View和ViewGroup,而View则不可以包含,View是某一个特定的View控件。


MotionEvent的四种基本状态

  • ACTION_DOWN : 手指接触屏幕的瞬间

  • ACTION_MOVE :手指在屏幕上移动

  • ACTION_UP : 手指离开屏幕的瞬间

  • ACTION_CANCEL : 取消手势


其中,ACTION_CANCEL事件需要在某种特定情况下才会产生,比如出现滑动冲突时,子View就会收到一个ACTION_CANCEL事件,但本文先不做讲述,会在滑动冲突分析解决篇做一下介绍。


事件序列的生命周期


手指从按下接触屏幕到,抬起离开屏幕,这个过程包含了一个ACTION_DOWN事件,一个ACTION_UP事件,以及多个ACTION_MOVE事件,这一串事件,我们成为一个事件序列,这个事件的生命周期由ACTION_DOWN开始,并由ACTION_UP(或者ACTION_CANCEL)结束。


View的事件分发机制,可分为三部分来理解:事件分发、事件拦截、事件响应,分别对应着三个方法:dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent,下面详细描述。


事件分发


dispatchTouchEvent()

true:消耗(处理)当前事件,并终止传递

false:不消耗(不处理)当前事件,事件向子元素下传

手势交互发生在某一个指定View上,产生的事件序列并进行分发,必然会有一个起点和一个终点,Activity为起点,我们触摸的那个View为终点,起点到终点的过程,还经过了多层传递,传递顺序如下:

Activity -> Window -> ViewGroup -> ViewGroup -> … (n个ViewGroup ) -> View


事件拦截


onInterceptTouchEvent()

true:拦截当前事件,并终止传递

false:不拦截当前事件,事件向子元素下传

既然我们的手势是与这个指定的View进行交互,那么我们的预期应该是整个事件序列的所有事件,都应该被传递到指定的View,并由View做出响应。但是在这个事件序列进行分发传递的过程中,ViewGroup ,即指定View的父亲可以对某个或者某几个事件拦截下来,并终止传递给下一级。因此,事件在进行分发过程中,就有可能会出现事件被拦截的情况,也因此可能会出现不符合我们预期的手势交互响应情况。而有些时候,ViewGroup拦截事件,也可能是人为故意拦截,用以实现某些功能逻辑和效果。

从事件分发传递的顺序就不难得出,View不存在onInterceptTouchEvent()方法,即View不会去拦截任何事件,直接消耗处理事件,或者不处理(通知上层父元素处理)。因为View根本不存在子元素了,拦截没有任何意义。而这点也可以通过SDK源码得以证明。


事件响应


onTouchEvent()

true:消耗(处理)当前事件,并反馈给上层父元素,告知已消耗处理

false:不消耗(处理)当前事件,同时反馈给上层父元素,让父元素消耗处理

真正消耗处理事件,则在onTouchEvent方法中处理,在这个方法中,会判断传进来的MotionEvent状态,并对指定的状态进行处理,如点击、长按、双击、触摸移动。一般地,我们在处理MotionEvent对象后,都会返回true,表示此次的MotionEvent已经被消耗并处理,这样的话,这个事件所属的事件序列的后续事件才会继续传递到当前处理的View。如果这里不想消耗处理事件,则回调父元素的相关方法,让父元素来消耗处理,如果一直都不消耗,最终会传回到Activity,如果Activity也不消耗,那么这个事件就消失了。


读到这里,你可能会有一个疑问:dispatchTouchEvent返回结果的含义,似乎与onInterceptTouchEvent、onTouchEvent存在冲突的地方,其实不然,它们三者之间的关系可用下面的伪代码来表示:

public boolean dispatchTouchEvent(MotionEvent event) {
    boolean comsune = false;
    if (onInterceptTouchEvent(event)) {
        comsune = onTouchEvent(event);
    } else {
        comsune = child.dispatchTouchEvent(event);
    }
    
    return comsune;
}

这段话伪代码很好理解,也把三者之间的关系表现的很直白。我们简单描述一下:

dispatchTouchEvent的返回结果,表示当前View是否消耗事件,而它是否消耗,则由自身的onTouchEvent和子元素的dispatchTouchEvent来决定。而自身的onTouchEvent是否被得到执行,则取决于自身是否对事件进行拦截,即onInterceptTouchEvent是否返回true,如果拦截,才由自身的onTouchEvent处理,否则由子元素的dispatchTouchEvent处理;当事件传到了子元素的dispatchTouchEvent,处理过程又是一样的,直到事件被传到终点,或者在某一层被拦截,才终止传递分发,然后做出响应。


这里需要弄明白的整个事件分发和响应的设计思路:

从上述伪代码和描述中得知,如果当前ViewGroup不消耗,但是它的子元素消耗了,那么对于当前ViewGroup的父元素来说,就是由当前ViewGroup消耗了。如果有点绕,不妨假设有个事件传递链:

A -> B -> C

被箭头指向的,作为子元素。一个事件从A分发到B,但是B不处理,B继续分发给C,C接收到后进行消耗处理了,并把消耗结果告诉B,B接收到这个结果后也要告诉A。但由于A是直接跟B打交道的,A只知道在B那一层事件被消耗了,并不知道其他的具体细节,所以,对于A来说,就是由B进行消耗处理了。


我们再举一个更直白的例子:


假设你上头有一个经理,自己又有一个手下,经理交给你一个任务,如果你自己能完成并且想去完成,则自己直接处理,并报告上级经理;或者你自己不处理,交给你的手下处理,如果你的手下完成了你交给他的任务,也相当于你完成你经理交给你的任务;如果你手下没有能力去完成,那么就会告诉你他没法处理,然后让你处理,如果你自己也不处理或者没法处理,你就只能继续反馈给你的经理说没法处理了,后续的情况,就是你经理去负责了。


因此,我们可以得知:

  • 事件分发 - 由外到内
  • 事件响应 - 由内到外

事件分发机制流程图


文章读到这里,View的事件分发机制理论就了解得差不多了,我们可以用一个流程图来描述:

三个维度,不太一样的View事件分发机制(理论概念)

本文到这里就差不多结束啦,相信你对View的事件分发机制已有了大致的了解和掌握,下一篇文章中,将会从SDK源码角度去分析和解读View的事件分发机制,以更深层次的了解其工作原理!

这里不妨先引出几个问题:

(1)如果一个View同时监听onClickListener和onTouchListener ,哪个会先被执行呢?

(2)onTouch()和onTouchEvent()有什么区别?如何去区分呢?

(3)子View能否阻止父对事件的拦截?如果能,该如何处理?


我们下一篇文章解读~


THE END

本文地址:https://blog.csdn.net/h_xiao_x/article/details/108188606