处理点击事件的方法
View的层级
我们知道View的结构是树形的结构,View可以放在ViewGroup中,这个ViewGroup也可以放到另一个ViewGroup中,这样层层的嵌套就组成了View的层级。
什么是点击事件分发
当我们点击屏幕,就产生了触摸事件,这个事件被封装成了一个类:MotionEvent。而当这个MotionEvent产生后,那么系统就会将这个MotionEvent传递给View的层级,MotionEvent在View的层级传递的过程就是点击事件分发。
点击事件分发的重要方法
点击事件有三个重要的方法它们分别是:
- dispatchTouchEvent(MotionEvent ev):用来进行事件的分发
- onInterceptTouchEvent(MotionEvent ev):用来进行事件的拦截,在dispatchTouchEvent()中调用,需要注意的是View没有提供该方法
- onTouchEvent(MotionEvent ev):用来处理点击事件,在dispatchTouchEvent()方法中进行调用
为了了解这三个方法的关系,我们先来看看ViewGroup的dispatchTouchEvent()方法的部分源码
\frameworks\base\core\java\android\view\ViewGroup.java
1 | @Override |
在dispatchTouchEvent()方法中调用了onInterceptTouchEvent()方法来判断是否拦截事件,来看看onInterceptTouchEvent()方法:
1 | public boolean onInterceptTouchEvent(MotionEvent ev) { |
除了鼠标事件,onInterceptTouchEvent()方法默认返回false,不进行拦截,接着来看看dispatchTouchEvent()方法剩余的部分源码:
1 | public boolean dispatchTouchEvent(MotionEvent ev) { |
我们看到了for循环,首先遍历ViewGroup的子元素,判断子元素是否能够接收到点击事件,如果子元素能够接收到则交由子元素来处理。接下来看看注释1的dispatchTransformedTouchEvent()方法中实现了什么:
1 | private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, |
如果有子View则调用子View的dispatchTouchEvent(event)方法。如果ViewGroup没有子View则调用super.dispatchTouchEvent(event),ViewGroup是继承View的,我们再来看看View的dispatchTouchEvent(event):
1 | public boolean dispatchTouchEvent(MotionEvent event) { |
我们看到如果OnTouchListener不为null并且onTouch()方法返回true,则表示事件被消费,就不会执行onTouchEvent(event),否则就会执行onTouchEvent(event)。再来看看onTouchEvent()方法的部分源码:
1 | public boolean onTouchEvent(MotionEvent event) { |
上面可以看到只要View的CLICKABLE和LONG_CLICKABLE一个为true,那么onTouchEvent就会返回true消耗这个事件。CLICKABLE和LONG_CLICKABLE代表View可以被点击和长按点击,可以通过View的setClickable和setLongClickable方法来设置,也可以通过View的setOnClickListenter和setOnLongClickListener来设置,他们会自动将View的设置为CLICKABLE和LONG_CLICKABLE。
接着在ACTION_UP事件会调用performClick()方法:
1 | public boolean performClick() { |
如果View设置了点击事件OnClickListener,那么它的onClick()方法就会被执行。
点击事件分发的传递规则
看到这里我们就可以知道点击事件分发的这三个重要方法的关系,用伪代码来简单表示就是:
1 | public boolean dispatchTouchEvent(MotionEvent ev) { |
点击事件由上而下的传递规则
当点击事件产生后会由Activity来处理在传递给Window再传递给顶层的ViewGroup,一般在事件传递中只考虑ViewGroup的onInterceptTouchEvent()方法,因为一般情况我们不会去重写dispatchTouchEvent()方法。
对于根ViewGroup,点击事件首先传递给它的dispatchTouchEvent()方法,如果该ViewGroup的onInterceptTouchEvent()方法返回true,则表示它要拦截这个事件,这个事件就会交给它的onTouchEvent()方法处理,如果onInterceptTouchEvent()方法返回false,则表示它不拦截这个事件,则交给它的子元素的dispatchTouchEvent()来处理,如此的反复下去。如果传递给最底层的View,View是没有子View的,就会调用View的dispatchTouchEvent()方法,一般情况下最终会调用View的onTouchEvent()方法。
举个现实的例子,就是我们的应用产生了重大的bug,这个bug首先会汇报给技术总监那:
技术总监(顶层ViewGroup)→技术经理(中层ViewGroup)→工程师(底层View)
技术总监不拦截,把bug分给了技术经理,技术经理不拦截把bug分给了工程师,工程师没有下属只有自己处理了。
事件由上而下传递返回值规则为:true,拦截,不继续向下传递;false,不拦截,继续向下传递。
点击事件由下而上的传递规则
点击事件传给最底层的View,如果他的onTouchEvent()方法返回true,则事件由最底层的View消耗并处理了,如果返回false则表示该View不做处理,则传递给父View的onTouchEvent()处理,如果父View的onTouchEvent()仍旧返回返回false,则继续传递给改父View的父View处理,如此的反复下去。
再返回我们现实的例子,工程师发现这个bug太难搞不定(onTouchEvent()返回false),他只能交给上级技术经理处理,如果技术经理也搞不定(onTouchEvent()返回false),那就把bug传给技术总监,技术总监一看bug很简单就解决了(onTouchEvent()返回true)。
事件由下而上传递返回值规则为:true,处理了,不继续向上传递;false,不处理,继续向上传递。
点击事件传递时的其他问题
- 上面源码我们看到:如果我们设置了OnTouchListener并且onTouch()方法返回true,则onTouchEvent()方法不会被调用,否则则会调用onTouchEvent()方法,可见OnTouchListener的优先级要比onTouchEvent()要高。在OnTouchEvent()方法中,如果当前设置了OnClickListener则会执行它的onClick()方法。
- View的OnTouchEvent()方法默认都会返回true,除非它是不可点击的也就是CLICKABLE和LONG_CLICKABLE都为false。
原文:http://liuwangshu.cn/application/view/5-dispatchingevents.html