事件传递原则:先捕获然后冒泡
在捕获阶段,事件先由外部的View接收,然后传递给其内层的View,依次传递到更够接收此事件的最小View单元,完成事件捕获过程;
在冒泡阶段,事件则从事件源的最小View单元开始,依次向外冒泡,将事件对层传递。
- Android中不同的控件所具有的事件分发、拦截和响应稍有不同,主要表现在Activity本身不具有事件拦截,不是ViewGroup的最小view单元也不具有事件拦截(因为它没有自己的子View)。
Android中事件分发
事件分发:dispatchTouchEvent(MotionEvent ev);自身可以消费事件返回(true)
- 当有事件的时候,先有当前的Activity捕获,
- 如果返回true自身消费了,如果不想activity做任何事情可以直接返回true ;
- 如果返回false交给上层的onTouchEvent消费时间,如果是activity的就直接交给系统消费
- 如果返回super.dispatchTouchEvent(),
- 如果是activity交给ViewGroup的diapatchTouchEvent(),
- 如果是子View就给自身的onTouchEvent消费
- 如果是ViewGroup就交给自身的onInterceptTouchEvent()处理
- 当有事件的时候,先有当前的Activity捕获,
事件拦截:只有ViewGroup拥有
- 如果返回true表示拦截事件交给本层的onTouchEvent()处理事件
- 如果返回false或者super.onInterceptTouchEvent(ev)都不拦截事件交给子View的dispatchTouchEvent()处理事件
事件处理:onTouchEvent();
- 如果返回true表示事件由本层消费以后完结,不会再向上冒泡传递;
- 如果返回false,或者默认的super.onTouchEvent()事件将在本层做处理后上层View冒泡传递给上层View的onTouchEvent()做处理
总结为:
从以上过程中可以看出,dispatchTouchEvent无论返回true还是false,事件都不再进行分发,
只有当其返回super.dispatchTouchEvent(ev),才表明其具有向下层分发的愿望,
但是是否能够分发成功,则需要经过事件拦截onInterceptTouchEvent的审核。事件是否具有冒泡特是由onTouchEvent的返回值决定的。
注意: ACTION_DOWN是跟着时间传递一起走的,而ACTION_MOVE与ACTION_UP
ACTION_DOWN事件在哪个控件消费了(return true), 那么ACTION_MOVE和ACTION_UP就会从上往下(通过dispatchTouchEvent)做事件分发往下传,就只会传到这个控件,不会继续往下传;
如果ACTION_DOWN事件是在dispatchTouchEvent消费,那么事件到此为止停止传递,三个传递是一样儿的;
如果ACTION_DOWN事件是在onTouchEvent消费的,那么会把ACTION_MOVE或ACTION_UP事件传给该控件的onTouchEvent处理并结束传递。
事件冲突的解决方法(如:pullToRefresh跟viewPager的冲突)
分两种情况: 外部拦截法 和内部拦截法
外部拦截法:
在ViewGroup的onInterceptTouchEvent(MotionEvent ev)中判断ev的ACTION_DOWN(return false),ACTION_MOVE;ACTION_UP(return false理由同DOWN)属性,注意ACTION_DOWN不能返回true,否则剩下的两个事件将只能有自身消费不能传递给子View;在ACTION_MOVE中判断上下左右移动的位置,分别设置为true拦截,false不拦截给子类处理即可;
/** * 拦截事件 * @param ev * @return */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean intercepted = false; int x = (int) ev.getX(); int y = (int) ev.getY(); switch (ev.getAction()) { /*如果拦截了Down事件,则子类不会拿到这个事件序列,整个事件只能有本身处理*/ case MotionEvent.ACTION_DOWN: lastXIntercept = x; lastYIntercept = y; intercepted = false; if (!mScroller.isFinished()) { mScroller.abortAnimation(); intercepted = true; } break; case MotionEvent.ACTION_MOVE: final int deltaX = x - lastXIntercept; final int deltaY = y - lastYIntercept; /*根据条件判断是否拦截该事件*/ if (Math.abs(deltaX) > Math.abs(deltaY)) { intercepted = true; } else { intercepted = false; } break; case MotionEvent.ACTION_UP: intercepted = false; break; } lastXIntercept = x; lastYIntercept = y; return intercepted; }
内部拦截法
主要是requestDisallowInterceptTouchEvent(boolean),这个标志可以决定父控件是否拦截事件,如果设置了这个标志则不拦截,如果没设这个标志,它就会调用父控件的onInterceptTouchEvent()来询问父控件是否拦截。但这个标志对Down事件无效。
if (actionMasked == MotionEvent.ACTION_DOWN) { cancelAndClearTouchTargets(ev); //清楚标志 resetTouchState(); } // Check for interception. final boolean intercepted; 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; }
重写子View的diapatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent event) { ... switch (action) { case MotionEvent.ACTION_MOVE: /** 跟上面的 !disallowIntercept 对应,当为true的时候表示父控件不拦截 false:拦截事件父类消费 */ getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: if(子元素需要处理此事件) getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_UP: { break; } ... return super.dispatchTouchEvent(event); ; }
同时重写父类的onInterceptTouchEvent(),可以通过继承重写的方法
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { /** 当为MotionEvent.ACTION_DOWN不拦截事件否则子类将不可能收到传递的事件 */ int action=ev.getAction(); if(action==MotionEvent.ACTION_DOWN){ return false; }else { return true; } }