Kotlin进阶-8-View的事件分发详解

目录

1、介绍

2、事件分发

3、源码分析

3.1、ViewGroup.dispatchTouchEvent()

4、View的滑动冲突

4.1、滑动冲突的处理规则

4.2、外部拦截法

4.3、内部拦截法


1、介绍

View的事件分发机制,其实就是手指触摸屏幕造成的事件(点击、滑动、长按等)的传递规则,而传递对象就是MotionEvent,View的事件分发机制其实就是研究MotionEvent的传递规则。

点击事件的分发过程由三个很重要的方法共同完成:dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent()

注意:onInterceptTouchEvent()方法只有在ViewGroup中有,Activity和View中都没有该方法。

2、事件分发

下图是我写的简单的三层布局,当我点击Son的时候,它打印出了这三个方法的调用记录。

从输出结果我们可以看出:(输出结果中的0 = ACTION_DOWN)

1、dispatchTouchEvent()方法会从Activity开始一层一层地向下级View调用

2、onTouchEvent()方法会在dispatchTouchEvent()方法调用到最底层View之后,再从最底层View一层一层地往上回调。

它们的传递顺序就是这样的,如果你拦截了其中任何一个,那么后续的事件都不会发生。

3、onInterceptTouchEvent会方法伴随着dispatchTouchEvent()方法存在,它的作用就是拦截ViewGroup的事件,不让它继续向下传递。

4、当我们给View设置OnTouchListener之后,所有事件会被自动拦截在该View中,上级View的onTouchEvent()将都不会被调用;

而且OnTouchListener中的onTouch()事件优先级高于onTouchEvent()事件,如果onTocuh()的返回结果为ture,那么该View的onTouchEvent()事件将不会被调用。

3、源码分析

当一个点击事件发生时,首先会传递给我们的Activity,这个可以从上面的打印结果看出来,而在Activity的源码中,我们会发现它会将dispatchTouchEvent()事件向我们的ViewGroup传递,传递的过程如下:

Activity--->PhoneWindow--->DecorView--->ViewGroup---->后面就是我们XML布局中的View了

详细调用过程可以看下图:

上图说明点击事件是如何从Activity传递到我们的布局中的View,我们通过这个传递过程,也能获取到我们的XML布局中layout的View。 

如下图:我们的XML中的View可以通过红色框的代码来获取到,这样你应该对我们Android中View的层级关系有一个了解了把。

下图2中红色框中的ContentViews就是我们的Xml布局中的layoutView。

3.1、ViewGroup.dispatchTouchEvent()

上面我们讲解的点击事件是怎么从Activity传递到我们ViewGroup中的,现在来详细看一下ViewGroup.dispatchTouchEvent()方法怎么调用onInterceptTouchEvent()方法和onTouchEvent()方法的。

详细解析可以看这里。

4、View的滑动冲突

常见的滑动冲突场景可以简单的分为如下三种:

  • 场景1---------外部滑动方向和内部滑动方向不一致;
  • 场景2---------外部滑动方向和内部滑动方向一致;
  • 场景3---------上面两种情况的嵌套。

场景1 : 主要是将ViewPager和Fragment配合使用所组成的页面滑动效果,在这种组合中,你可以通过左右滑动来切换页面,每个页面的Fragment中如果刚好还有一个ListView或者RecyclerView的话,那就会构成我们上面场景1 所说的滑动冲突。

但是ViewPager内部帮我们解决了这个冲突,所以使用ViewPager和Fragment组合是不会有滑动冲突的,如果你使用ScrollView和 RecyclerView组合的话,就会出现滑动冲突,造成的结果:两层只有一层可以滑动。

场景2 : 这种情况稍微复杂一点,当内外两层都朝同一个方向滑动的话,当我们手指滑动时候,应用可能不知道我们具体想要滑动哪层,造成结果可能是,两层滑动起来很卡顿,或者只有一层滑动。

场景3 :看起来会有点复杂,但是对于这三曾来说,只要上面的处理规则,处理好外层和中层,中层和内层的滑动冲突就可以解决问题了。

4.1、滑动冲突的处理规则

场景1 :的处理规则其实很简答, 就是根据我们滑动手势,来判断是横向滑动,还是竖向滑动,当横向滑动的话, 我们让外部View(ScrollView)来拦截该滑动事件,如果是竖向滑动的话,我们让内部的view(RecyclerView)来拦截滑动事件就可以了。

场景2 :的处理规则显然不能根据滑动手势来处理了,这个时候得根据我们的业务来处理,比如业务规定快速滑动是让内部View滑动,长按后滑动是让外部View滑动等。

场景3 : 的处理规则,就是上面两种规则的结合体。

4.2、外部拦截法

对于场景1 我们采用的第一种解决方法就是外部拦截法,所谓的外部拦截法就是当点击事件经过父类View(外部View)的时候,我们将想要拦截的事件拦截下来,就比如我们在ScrollView中,拦截横向滑动的事件,然后竖向滑动的事件不拦截,就给子View处理。

外部拦截法需要重写父类View的onInterceptTouchEvent()方法,然后在其中拦截想要拦截的方法。

伪代码如下:

注意1 :对于ACTION_DOWN事件,我们必须返回false,这是因为一旦父容器拦截了ACTION_DOWN事件,那么后续的ACTION_MOVE和ACTION_UP都将被拦截,子View将接受不到任何点击滑动事件。

注意 2 :如果对于ACTION_UP事件没有业务要求拦截的话,我们要尽量不拦截它,因为我们一旦拦截了ACTION_UP事件,那么它的子View的onClick事件将不会触发。

4.3、内部拦截法

内部拦截法是指父容器不拦截任何事件,所有事件都传递给子View,如果子View需要此事件就直接消费掉,否则就交由父容器进行处理,这种方法和Android的事件分发机制有点不符,需要配合requestDisallowInterceptTouchEvent()使用。

伪代码如下:

1、首先看Father_View,它的onInterceptTouchEvent()方法拦截了除ACTION_DOWN以外的所有事件。

2、然后看Son_View,当ACTION_DOWN事件传递给Son_View的时候,它调用了

  parent.requestDisallowInterceptTouchEvent(true)

这让Father_ViewonInterceptTouchEvent()方法不再拦截任何事件,所有事件交给了Son_View处理,当子类判断这是一个横向滑动的时候,它又调用了

 parent.requestDisallowInterceptTouchEvent(false)

Father_View拦截了该横行滑动事件,其余的事件都交给了Son_View处理。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

原创文章 120 获赞 34 访问量 28万+

猜你喜欢

转载自blog.csdn.net/qq_34589749/article/details/105838571
今日推荐