讲讲Android的事件分发机制

这里写图片描述
Android的事件分发机制涉及的知识点很多,为了方便记忆和知识总结,小编决定专门开一篇文章,记录相关知识。
话不多说,我们直入主题。

面试场景

今天找到了几个自己之前校招时记录的关于事件分发的几个面试题。

讲讲Android的事件分发机制

基本会遵从Activity => ViewGroup => View的顺序进行事件分发,然后通过调用onTouchEvent()方法进行事件的处理。我们在项目中一般会对
MotionEvent.ACTION_DOWN,MotionEvent.ACTION_UP, MotionEvent.ACTION_MOVE分情况进行操作。

有去查看源码中的事件拦截方法吗?了解过相关源码吗?

事件拦截分为内部拦截法外部拦截法,对于外部拦截法,我们可以重写ViewGroup的onInterceptEvent();对于内部拦截法,我们可以通过
requestDisallowInterceptTouchEvent()来控制父容器是否拦截。

在一个列表中,同时对父View和子View设置点击事件,优先响应哪个?为什么会这样?

优先响应子View,因为当父View决定不拦截子View后,就会调dispatchTouchEvent,ViewGroup的这个方法会先去遍历调用子View的dispatchTouchEvent,如果都返回false,才会走到父View的onTouchEvent

这三个问题只是简单的热身,要想了解Android的事件分发机制,还是得熟读源码。关于事件分发,应该分Activity、ViewGroup、View这三种情况来讲。

而且,说到事件分发,有三个非常重要的方法也不得不提。

  • dispatchTouchEvent()
  • onTouchEvent()
  • onInterceptTouchEvent()

Activity的事件分发机制

顾名思义,dispatchTouchEvent()是负责事件分发的。当点击事件产生后,事件会传递给当前Activity,这时会调用Activity的dispatchTouchEvent(),我们来看源码。
这里写图片描述
在上面这段代码中,我们来看下getWindow().superDispatchTouchEvent()getWindow()明显是获取Window,这个就是我们很熟的PhoneWindow了。我们直接看看PhoneWindowsuperDispatchTouchEvent()到底做了什么操作。
这里写图片描述
直接调用了DecorViewsuperDispatchTouchEvent()方法。DecorView继承于FrameLayout,作为顶层View,是所有界面的父类。而FrameLayout作为ViewGroup的子类,所以直接调用了ViewGroupdispatchTouchEvent()

ViewGroup的事件分发机制

我们通过查看ViewGroupdispatchTouchEvent()可以发现。
我们分段来看dispatchTouchEvent()。
这里写图片描述
注:本文中源码都经过了小编删减,只展示与事件分发相关逻辑。
我们来看如上代码中的红框部分:ACTION_DOWN事件一定会交由ViewGroup处理,子View没办法拦截,因为resetTouchState()会把requestDisallowInterceptTouchEvent()所置的标志位重置

我们再来看红框外的部分:首先定义了一个变量intercept来表示是否拦截事件。

其中采用了onInterceptTouchEvent()intercept进行赋值。大多数情况下,onInterceptTouchEvent()返回值为false,但我们可以重写onInterceptTouchEvent()来改变它的返回值,我们往下看后面这个intercept是如何被用到的。
这里写图片描述
可以看到,当intercept是false时,会通过for循环去遍历ViewGroupchild,然后调用dispatchTransformedTouchEvent(),如果dispatchTransformedTouchEvent()返回值是true,就去调用addTouchTarget()
而当intercept是true时,就不会去遍历ViewGroupchild,也更不会调用child的
dispatchTransformedTouchEvent()了。
这里我们还需要看下dispatchTransformedTouchEvent()addTouchTarget()的源码是如何实现的。
这里写图片描述
for循环里,传入给dispatchTransformedTouchEvent()child不为null,所以调用的是子View的dispatchTouchEvent()
如果子View的dispatchTouchEvent()返回true,则调用addTouchTarget()
这里写图片描述
这个方法里,我们只需要注意一点:给mFirstTouchTarget赋值。

我们来整理一下思路:
当事件传递给ViewGroup,先去遍历调用child的dispatchTouchEvent(),如果有child的dispatchTouchEvent()返回了truemFirstTouchTarget就被赋值,否则mFirstTouchTarget就为null

至此,dispatchTouchEvent()就快看完了,我们来看下mFirstTouchTarget是如何被使用到的。
这里写图片描述
如上,如果mFirstTouchTargetnull,就去调用super.dispatchTouchEvent()

我们知道ViewGroupsuperView,所以,当ViewGroup的所有child的dispatchTouchEvent()都返回false,就调用父容器的dispatchTouchEvent()

总结一下,ViewGroup的事件传递过程可以通过如下伪代码表示:

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

View的事件分发机制

上节我们讲到,当ViewGroup的所有child都没有消费事件,就调用super.dispatchTouchEvent(),而ViewGroupsuperView,所以我们来看下ViewdispatchTouchEvent()
这里写图片描述
ViewdispatchTouchEvent()已经说的很清楚了:onTouchListener的优先级大于onTouchEvent。如果onTouchListener返回true了,就不调用onTouchEvent了。
我们下面再看看onTouchEvent()的源码。
这里写图片描述
在手指抬起的时候都会调用performClick。如果设置了onClickListener,就会调用onClickListener.onClick

总结

1、Android事件分发总是遵循Activity => ViewGroup => View的传递顺序
2、onTouchListener的优先级大于onTouchEvent。

参考

https://blog.csdn.net/jiangwei0910410003/article/details/17504315
https://www.cnblogs.com/qlky/p/6675882.html
https://blog.csdn.net/jaysong2012/article/details/45535521
https://www.jianshu.com/p/d3758eef1f72

猜你喜欢

转载自blog.csdn.net/colinandroid/article/details/81202045
今日推荐