事件分发机制、滑动冲突详细讲解

来源:互联网 发布:空耳yaya淘宝 编辑:程序博客网 时间:2024/05/22 06:25
事件分发机制、滑动冲突

本章将介绍View的一个核心知识点:事件分发机制。事件分发机制不仅仅是核心知识点更是难点,不少初学者甚至中级开发者面对这个问题时都会觉得困惑。另外,View的另一大难题滑动冲突,他的解决方法的理论基础就是事件分发机制,因此掌握好View的事件分发机制是非常重要的。

事件分发过程由三个很重要的方法完成:dispatchTouchEvent,onInterceptTouchEvent和onTouchEvent。

dispatchTouchEvent
用来进行事件的分发。如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件。

onInterceptTouchEvent
在上述方法内部调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件。

onTouchEvent
在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收其他事件。

当一个View需要处理事件时,如果它设置了onTouchListener,那么onTouchListener中的onTouch方法会被调用。这时事件如何处理还要看onTouch的返回值,如果返回false,则当前View的onTouchEvent方法会被调用;如果返回true,那么onTouchEvent方法将不会被调用。由此可见,给View设置的OnTouchListener,其优先级比onTouchEvent要高。在onTouchEvent方法中,如果当前设置的有OnClickListener,那么它的onClick方法会被调用。可以看出,平时我们常用的OnClickListener,其优先级最低,即处于事件传递的尾端。

关于事件传递机制,这里给出一些结论。

1、同一个事件序列是指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,在这个过程中所产生的一系列事件,这个事件序列以down事件开始,中间含有数量不定的move事件,最终以up事件结束。

2、正常情况下,一个事件序列只能被一个View拦截且消耗。

3、某一个View一旦决定拦截,那么这一个事件序列都只能由它来处理(如果事件序列能够传递到它的话),并且它的onInterceptTouchEvent不会再被调用。这条也很好理解,就是说当一个View决定拦截一个事件后,那么系统会把同一个时间序列内的其他方法都交给它来处理,因此就不用再调用这个View的onInterceptTouchEvent去询问它是否要拦截啦。(这里需要提一下:是同一个事件序列,关于事件序列见1)

4、当某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一事件序列中的其他事件都不会再交给它来处理,并且事件将重新交由它的父元素去处理,即父元素的onTouchEvent会被调用。意思就是事件一旦交给一个View处理,那么它就必须消耗掉,否则同一事件序列中剩下的事件就不再交给它来处理了,这就好比上级交给程序员一件事,如果这件事没有处理好,短期内上级就不敢再把事情交给这个程序员做了,两者类似。

5、如果View不消耗除ACTION_DOWN以外的其他事件,那么这个点击事件会消失(当然了:点击事件需要消耗,DOWN 和 UP事件的),此时父元素的onTouchEvent并不会被调用,并且当前的View可以持续收到后续的事件,最终这些消失的点击事件会传递给Activity处理。

6、ViewGroup默认不拦截任何事件。Android源码中的ViewGroup的onInterceptTouchEvent方法默认不拦截false。

7、View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用。

8、View的onTouchEvnet默认都会消耗(返回true),除非它是不可点击的(clickable和longClickable同时为false)。View的longClickable属性默认都为false,clickable属性要分请情况,比如Button的click able属性默认为true,而TextView 的 clickable属性默认为false。

9、View的enable属性不影响onTouchEvent的默认返回值。哪怕一个View是disable状态的,只要它的clickable或则longClickable有一个为true,那么它的onTouchEvent就返回true。

10、onClick会发生的前提是当前View是可点击的,并且他收到了down和up的事件。

11、事件传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View,通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程,但是ACTION_DOWN事件除外。


滑动冲突方案

1、外部拦截法:
所谓外部拦截法是指点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件就不拦截,这样就可以解决滑动冲突的问题,这种方法比较符合点击事件的分发机制。外部拦截法需要重写父容器的onInterceptTouchEvent方法,在内部做相应的拦截接即可。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {

boolean intercepted = false;

switch (ev.getAction()) {

case MotionEvent.ACTION_DOWN:
intercepted = false;
break;

case MotionEvent.ACTION_MOVE:
if (父容器需要此事件) {
intercepted = true
} else {
intercepted = false;
}
break;

case MotionEvent.ACTION_UP:
intercepted = false;
break;
default:
break;

}

Log.e(KEY_TAG, "===================================onInterceptTouchEvent");
return intercepted;
}

2、内部拦截法:
内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就由父容器进行处理,这种方法和Android中的事件分发机制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,使用起来较外部拦截法稍显复杂。
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if(父容器需要此事件){
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
Log.e(KEY_TAG, "===================================dispatchTouchEvent");
return super.dispatchTouchEvent(event);
}
除了子元素需要做处理之外,父元素也还要默认拦截除了ACTION_DOWN以外的其他事件,这样当子元素调用parent.requestDisallowInterceptTouchEvent(false)方法时,父元素才能继续拦截所需的事件。

阅读全文
0 0
原创粉丝点击