您的位置:首页 > 其它

关于事件分发的一些认识

2017-04-08 14:00 197 查看
首先得说个起着决定性作用的东西:ACTION_CANCEL事件

在一个博客中找到了关于cancel事件产生的原因:
http://tianshanxuester.github.io/android/2013/11/13/Android-%E8%A7%A6%E6%91%B8%E4%BA%8B%E4%BB%B6.html
要触发ACTION_CANCEL,就先得了解一个类ViewGroup,ViewGroup是一个放置其他views(子view)的特殊view,它是布局类(*Layout)、视图容器(ListView、GridView、HorizontalScrollView、TabHost等等很多)的基类。

也就是说ViewGroup一般是做为父视图来容纳、管理其他子视图的。既然管理,在用户手势操作过程中,就会存在父视图不希望子视图响应用户手势操作的情况。Android提供了一个函数public boolean onInterceptTouchEvent (MotionEvent ev),在用户手势操作时,系统先调用父视图(一个继承自ViewGroup的类)的这个函数,来决定当前手势操作是由父视图还是子视图来响应、处理。我们仔细看看这个函数名,函数名中有一个单词intercept,经过查词典,这个单词的中文意思是拦截。在用户的一个完整手势操作过程中(起自ACTION_DOWN,终于ACTION_UP),对于每一次的MotionEvent``Android都会调用该函数,向父视图查询是否拦截当前MotionEvent,如果父视图返回false:不拦截,则系统会调用子视图的onTouchEvent函数;如果父视图返回true:拦截,则系统调用父视图的onTouchEvent。等等,有人不禁要问了,如果在这个完整手势操作过程中,父视图初期返回false、后期返回true会是一个什么样的情况呢(捣乱的来了)?这个嘛,是这个样子的,一开始返回false,毫无疑问,子视图会被调用onTouchEvent,但凡父视图在函数onInterceptTouch中有一次返回了true,那这一完整手势操作内所有后续的MotionEvent都会调用父视图的onTouchEvent,即使父视图后期反悔而改成返回false也不行(没有后悔药)。在这种父视图先返回false,后返回true的情况下,子视图收不到后续的事件,而只是在父视图由返回false改成返回true(拦截)的时候收到ACTION_CANCEL事件

源码:点击打开链接

现在说说具体项目问题

我们知道,down事件发生的时候,如果子view的dispatchTouchEvent返回了true,那么在viewgroup的dispatchTouchEvent方法中mFirstTouchTarget会被赋值,代表有子view消耗了事件,根据viewgroup中事件分发的代码:

if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}我们假设down事件的时候子view的dispatchTouchEvent返回了true,并且在返回true之前执行了getParent().requestDisallowInterceptTouchEvent(false);按理来说disallowIntercept的值会被置为false,那么后续的move事件到来时,必定会走onIntercepteTouchEvent方法,这个本事是没有错的。可是在使用过程中,我的父容器是scrollview,子view是listview,在执行scrollview的onIntercepteTouchEvent时候,当满足一定条件scrollview的onIntercepteTouchEvent会返回true(scrollview在down的时候是返回false的,应该所有的父容器都是,要不然子view永远拿不到任何事件),down的时候返回了false,move的过程中返回了true,这会触发cancel事件,cancel事件触发之后

// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}


cancel事件触发之后 同样会执行resetTouchState方法,这个方法会将mFirstTouchTarget置为null,所以后续move事件传递到scrollview时不会执行onIntercepteTouchEvent方法,之前一直纠结为什么不执行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: