深入浅出解析Android事件传递机制

电脑杂谈  发布时间:2020-01-26 15:02:38  来源:网络整理

深入浅出android_深入理解android内核设计思想_邓凡平深入理解android(卷2)^^^深入理解andro

深入浅出解析Android事件传递机制

关于Android中的事件释放,在Android平台源代码层级的谋求上相当复杂,而针对应用程序的研发而言,我们不必深究太多细节,只应该把握事件释放模式所带给的一些结论即可。本文通过父控件→子控件的事件模型来看看关于事件释放模式的一些知识点。

在起初分析之前,大家可以先记几个结论,这样便于理解。

结论1:事件一定是先到达父控件上。

结论2:事件简单来说可以分为三种:Down事件、Move事件、Up事件,结合结论1可以受到,Down事件、Move事件Up事件均是最先到达父控件上。

结论3:父控件和父类不是一回事,这两个概念初学者很容易混淆。

事件模型:父控件→子控件

关于这些事件模型主要涵盖至3个概念:事件的分发、事件的拦截、事件的响应。

我们先解读下基础知识,事件分发是dispatchTouchEvent,事件拦截是onInterceptTouchEvent,事件的响应是onTouchEvent,对于ViewGroup类型的控件来说,它拥有这三种方式,而针对单个View控件来说,它只有dispatchTouchEvent和onTouchEvent,因为View不能包含其它的View,所以不需要判断是否应拦截事件。

一个事件即使到达某一个View以及ViewGroup,那么必定会最先调用到这个控件的dispatchTouchEvent。dispatchTouchEvent这个方式的意思就是把一个事件帮分发下去,那么它详细分发的逻辑是如何的呢?

邓凡平深入理解android(卷2)^^^深入理解andro_深入浅出android_深入理解android内核设计思想

图1

A.它首先会先调用自身的onInterceptTouchEvent方法,调用此方式的目的是为了,先使自己这个控件判断下能否需要把此事件拦截下来,如果拦截下来,那么就代表自己这个控件需要处理这个事件,所以这时会调用自身onTouchEvent来对这个事件进行响应。

图2

B.如果不拦截下来,那么就会有后续的事件向下传递的流程。将这个事件释放给子控件。现在子控件接收到了这个事件,上文提过,一个事件到达了一个View以及ViewGroup,就会最先调用这个控件的dispatchTouchEvent,所以这时,事件到达了子控件的dispatchTouchEvent方法,如果这个控件仍然是一个ViewGroup的类型,那么事件再次分发的逻辑依然遵守A流程的逻辑。

图3

C.如果这个子控件只是一个View,而不是ViewGroup,那么这时,事件分发的逻辑略有不同。由于View没有onInterceptTouchEvent的方式,所以当一个事件到达这个View的dispatchTouchEvent的之后,dispatchTouchEvent就调用不到onInterceptTouchEvent,它会直接调用onTouchEvent的方式,直接使这个View来响应此事件。

邓凡平深入理解android(卷2)^^^深入理解andro_深入理解android内核设计思想_深入浅出android

图4

通过ABC三个流程,我们能够够特别清楚地知道传递的逻辑。父控件传递给子控件,当没有控件拦截事件的话,将会一层层地向上传递,直到最终一个子控件。

接下来我们预测事件响应的方向。我们了解onTouchEvent是有返回值的,要么是true,要么是false,不同的返回值会有不同的体现。

图5

如上图所示,如果ViewGroupB拦截了事件,那么这时事件都会由ViewGroupB来响应,调用ViewGroupB中的onTouchEvent,此时ViewGroupB中的onTouchEvent的返回值有两种可能,一种是true,一种是false,如果返回true,则代表ViewGroupB消费了此事件,事件此时中止。如果返回的值是false,那么这时这个事件会回传给父控件,调用到父控件的onTouchEvent方法,由父控件来进行响应,那父控件的onTouchEvent也是相同的逻辑。要么消费事件,要么回传给父控件的父控件。

此时,就可以得出我们一般所说的两个方向:

1.事件传递的方向:父控件→子控件

2.事件响应的方向:子控件→父控件

当然,仅仅是这个论断是能够满足我们实际开发的还要,我们必须更细致的剖析。这里有一个细节上的难题需要切记,就是事件分为Down事件、Move事件、Up事件,任何一种事件都遵守事件释放和响应的逻辑方法,很多开发者常常会觉得Down-Move-Up连在一起才是一个事件的形成,这种看法是不对的。

深入理解android内核设计思想_深入浅出android_邓凡平深入理解android(卷2)^^^深入理解andro

事件的起点是由Down事件起初的,然后形成一系列的Move事件,最后往往以Up事件结束。当Down事件造成的之后,会由父控件传递给子控件,Move事件也由父控件传递给子控件,Up事件也由父控件传递给子控件。它们都遵循相同的释放事件的逻辑流程。不过Down事件最后响应的结果,会妨碍到后续事件的执行。这句话是哪个意思呢?

我们看图6,如果Down事件释放到了子View上,但是子View的onTouchEvent对于这个Down事件的处理是return了一个false,这样的结果就是会导致父View的onTouchEvent的调用,同时也有另外一个后果,那就是后续的Move事件、Up事件就都传递不到子View上。所以,如果一个View要处理滑动事件,也就是Move事件的话,那么它必定不能在onTouchEvent中,对Down事件return false。

如果Down事件到了父View上,父View需要调用自身的onInterceptTouchEvent判断是否对这个Down事件进行拦截,如果拦截,return了true,那么这个事件都会到父View的onTouchEvent中进行响应。如果这时父View的onTouchEvent也返回了true,那么代表这个父View响应了Down事件。不过此处有一点不太一样的地方是,事件释放到父View的onTouchEvent方法是因为自身的onInterceptTouchEvent方法判定拦截避免的,而不是由子View回传出去的,在这些状况下,当Move事件、Up事件释放至父View的之后,它其实不会传递给子View,并且,也不再调用自身的onInterceptTouchEvent方法。

理解事件释放的基本逻辑,对于工作过程中缓解滑动事件冲突更加有帮助。比如我们此刻有一个父控件ViewPager,这个ViewPager其中一个Item是ScrollView,此时会出现哪些问题呢?当ViewPager滑动到ScrollView这个条目的之后,再左右滑动,发现ViewPager再也左右滑动不了了。这是为什么呢?我们结合图6一起来分析一下。

1.我们都清楚ViewPager是无法横向滑动的控件,而ScrollView是横向滑动的控件,当Down事件造成的之后,此时会由ViewPager传递给ScrollView,ViewPager没有对Down事件拦截,ScrollView也不会对这个Down事件进行拦截,所以事件都会释放给ScrollView的孩子,也就是类似于图6中的子View,子View如果没有对Down事件响应,那么最终会到ScrollView中的onTouchEvent,而ScrollView的onTouchEvent对于这个Down事件返回了true,代表ScrollView消费了这个Down事件。

2.接下来开始滑动手指,产生一系列的Move事件。Move事件只是由ViewPager传递给ScrollView。由于Down事件是被ScrollView的onTouchEvent中消费的,所以Move事件就不会传递给ScrollView的子控件了。一系列的Move事件只是在ScrollView的onTouchEvent中被执行。

3.最后的Up事件只是由ScrollView中的onTouchEvent消费。

从上述1到3的方法中,我们看起来无论是Down事件、Move事件还是Up事件,最后全部都是被ScrollView所消费。从头到尾ViewPager的onTouchEvent都没有得到执行。而ViewPager之所以无法左右滑动,正是因为ViewPager的onTouchEvent里面的代码逻辑产生的效果。ViewPager的onTouchEvent没有执行,这个ViewPager当然就不能否左右滑动了。所以解决上述难题,就是在于能否使ViewPager中的onTouchEvent方法执行。

我们可以自定义一个MyViewPager继承ViewPager,重写onInterceptTouchEvent方法,如果我们在onInterceptTouchEvent方法中直接野蛮地return一个true,此时就代表无论是Down事件、Move事件,还是Up事件,全部都拦截下来了,拦截在MyViewPager中,我们可以觉得是图6中的ViewGroupB,既然拦截下来了所有事件,那么所有事件都会释放到MyViewPager的onTouchEvent,所以这时,这个MyViewPager一定可以左右滑动。

但是,由此会导致另外一个问题,就是这个ScrollView不能上下滑动了。这既是为什么呢?因为ScrollView能够上下滑动的代码逻辑在ScrollView中的onTouchEvent方法内,而这时事件既全部被MyViewPager拦截了出来,ScrollView完全得不到事件,onTouchEvent方法得不到执行,自然不能上下滑动。所以我们必须设置MyViewPager中的onInterceptTouchEvent的逻辑。

深入理解android内核设计思想_深入浅出android_邓凡平深入理解android(卷2)^^^深入理解andro

ViewPager只对左右滑动感兴趣,而ScrollView对上下滑动这个动作感兴趣,所以我们只必须在MyViewPager的onInterceptTouchEvent中深入浅出android,根据多个Move事件,判断是左右滑动还是上下滑动,如果是左右滑动,return true将事件拦截下来,如果是上下滑动,return false将事件释放给ScrollView,这样才能解决难题了。

所以,对于Down事件深入浅出android,我们通常都不进行拦截,判断是否拦截得依据一些列的Move事件能够得出确切的条件能否成立。

Cancel事件的产生:

刚才我们说了事件通常有三个,Down、Move、Up,这三个事件比较好理解。其实也有一种事件就是Cancel事件。它代表哪个意思呢?

还是回到图6,如果一个Down事件造成了,这个Down事件从ViewGroupA传递到ViewGroupB,最终到达子View,被子View的onTouchEvent消费,return了true,那么这时Down事件就中止了。接下来后续的Move事件也会从ViewGroupA传递给ViewGroupB,也就是说ViewGroupA和ViewGroupB会比子View更先获得Move事件,那即使ViewGroupA跟ViewGroupB比子View更先取得Move事件,那么它们当中的任何一个都有也许在某一个Move事件中,把这个Move事件帮拦截下来,一旦Move事件被拦截下来了,子View肯定就拿不到这个Move事件了,不过,此时子View会造成一个新的事件,就是Cancel事件。

所以一个正常的事件序列是 Down→Move→Up,这样才被觉得是一个正常的事件序列。如果一个View响应的Down事件,可是仍被没有正常结尾,Move事件以及Up事件被拦截了,此时非正常结尾的状况都会帮子View产生一个新的事件Cancel。

子控件可以影响父控件是否拦截的行为

子控件是可以干预父控件是否拦截事件的结果。通过在子View中dispatchTouchEvent中增加一行代码即可。getParent().requestDisallowInterceptTouchEvent(true);这行代码就可以请求父控件不要拦截事件。

很可能不太明白这句话的含义,既然事件一定是先到达父控件,然后才到达子View,那也就是getParent().requestDisallowInterceptTouchEvent(true);这句话是在父控件是否拦截判断结束以后才调用,怎么可改变父控件是否拦截的结果呢,这里存在一个执行先后顺序的疑惑。

其实是这种的,getParent().requestDisallowInterceptTouchEvent(true);达到的效果不是修改父控件对本次事件能否拦截的结果,而妨碍的是后续事件。比如子View在Down事件中调用了getParent().requestDisallowInterceptTouchEvent(true);这行代码,那么在后续Move事件、Up事件造成到达父控件的之后,父控件就不会再拦截了。所以getParent().requestDisallowInterceptTouchEvent(true);只会影响Move事件跟Up事件,影响不到Down事件。


本文来自电脑杂谈,转载请注明本文网址:
https://www.pc-fly.com/a/shouji/article-138282-1.html

    相关阅读
      发表评论  请自觉遵守互联网相关的政策法规,严禁发布、暴力、反动的言论

      热点图片
      拼命载入中...