关于事件分发的一些认识

来源:互联网 发布:多线程并发 数据共享 编辑:程序博客网 时间:2024/06/17 13:43

首先得说个起着决定性作用的东西: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方法,之前一直纠结为什么不执行。


0 0
原创粉丝点击