相信大家看过很多事件分发拦截的机制了,很多原理就不再多说了,说点不同的吧,那就是拦截的重点只是拦截下来不再给子View了,但是这里与父View的交互还需要再处理的。
来写个Demo试试吧,比较简单,就是父ViewGroup包了一个子View。
@Override
public boolean onInterceptTouchEvent(MotionEvent arg0) {
if (arg0.getAction() == MotionEvent.ACTION_DOWN) {
Log.e("父","父拦截down");
}
if (arg0.getAction() == MotionEvent.ACTION_MOVE) {
Log.e("父","父拦截move");
return true;
}
if (arg0.getAction() == MotionEvent.ACTION_UP) {
Log.e("父","父拦截up");
}
return super.onInterceptTouchEvent(arg0);
}
@Override
public boolean onTouchEvent(MotionEvent arg0) {
if (arg0.getAction() == MotionEvent.ACTION_DOWN) {
Log.e("父","父处理down");
return true;
}
if (arg0.getAction() == MotionEvent.ACTION_MOVE) {
Log.e("父","父处理move");
return true;
}
if (arg0.getAction() == MotionEvent.ACTION_UP) {
Log.e("父","父处理up");
return true;
}
return super.onTouchEvent(arg0);
}
子View的关键代码
@Override
public boolean onInterceptTouchEvent(MotionEvent arg0) {
if (arg0.getAction() == MotionEvent.ACTION_DOWN) {
Log.e("子","子拦截down");
}
if (arg0.getAction() == MotionEvent.ACTION_MOVE) {
Log.e("子","子拦截move");
}
if (arg0.getAction() == MotionEvent.ACTION_UP) {
Log.e("子","子拦截up");
}
return true;
}
@Override
public boolean onTouchEvent(MotionEvent arg0) {
if (arg0.getAction() == MotionEvent.ACTION_DOWN) {
Log.e("子","子处理down");
return true;
}
if (arg0.getAction() == MotionEvent.ACTION_MOVE) {
Log.e("子","子处理move");
return true;
}
if (arg0.getAction() == MotionEvent.ACTION_UP) {
Log.e("子","子处理up");
return true;
}
return super.onTouchEvent(arg0);
}
这里子View是完全想得到事件的整个控制的,可以看到两个方法返回都是true,以前的理解是子View获得一个事件的处理权后并返回true那么后续的事件都可以由自己处理。现在我们试下子View已经处理完Down的事件后,对Move事件,父布局此时想拦截并处理,会发生什么效果。
05-02 11:47:12.552 21913-21913/com.demo.zhang E/父: 父拦截down
05-02 11:47:12.552 21913-21913/com.demo.zhang E/子: 子拦截down
子处理down
05-02 11:47:12.592 21913-21913/com.demo.zhang E/父: 父拦截move
05-02 11:47:12.608 21913-21913/com.demo.zhang E/父: 父处理move
05-02 11:47:12.625 21913-21913/com.demo.zhang E/父: 父处理move
05-02 11:47:12.644 21913-21913/com.demo.zhang E/父: 父处理move
05-02 11:47:12.658 21913-21913/com.demo.zhang E/父: 父处理move
05-02 11:47:12.675 21913-21913/com.demo.zhang E/父: 父处理move
05-02 11:47:12.694 21913-21913/com.demo.zhang E/父: 父处理move
05-02 11:47:12.713 21913-21913/com.demo.zhang E/父: 父处理move
05-02 11:47:12.725 21913-21913/com.demo.zhang E/父: 父处理move
05-02 11:47:12.742 21913-21913/com.demo.zhang E/父: 父处理move
05-02 11:47:12.757 21913-21913/com.demo.zhang E/父: 父处理move
05-02 11:47:12.774 21913-21913/com.demo.zhang E/父: 父处理move
05-02 11:47:12.791 21913-21913/com.demo.zhang E/父: 父处理move
05-02 11:47:12.880 21913-21913/com.demo.zhang I/IgnoreTouchEvent: ignore up event : x = 269.5, ignore = false, ignoreUp = false, pkg = com.demo.zhang
05-02 11:47:12.880 21913-21913/com.demo.zhang E/父: 父处理up
看到了么,从第四行开始父ViewGroup已经拦截下来Move事件,并交给自己处理了(前提父ViewGroup是想处理的,在TouchEvent返回了true) 如果父布局不处理还会往上传递这个事件,这和普通的情况并没什么不一样。所以拦截主要是针对子布局的,而不是父布局的。
那么如果子View需要自己做不再给父布局管理,那么怎么办呢,有一个方法是
getParent().requestDisallowInterceptTouchEvent(true);
这样子View就可以处理剩余的Down Move Up事件了。
然后再说分发和拦截的区别
分发dispatchTouchEvent
Android中所有的事件都必须经过这个方法的分发,然后决定是自身消费当前事件还是继续往下分发给子控件处理。返回true表示不继续分发,事件被消费。返回false则继续往下分发,如果是ViewGroup则分发给onInterceptTouchEvent进行判断是否拦截该事件,而且在ViewGroup中会调用interceptTouchEvent()方法来判断是否继续向下分发。在View中是根据View的OnTouchEvent来决定返回值,当onTouch消费事件的时候,View的dispatchTouchEvent会返回true告诉父ViewGroup自己已经消耗掉这个事件。
不要太过在意它的返回值,看源码关注它的过程,它的过程是会调用自身的拦截以及子View的消费事件来派发的,也就是在这个过程中可能子View已经正在消费事件了(派发的过程中有调用子View的dispatchTouchEvent,而在那里又调用子View自身的onTouchEvent)。
所以这个对于一个ViewGroup来说这个返回值重点是告诉它上层的父ViewGroup,这个事件我已经消费掉了(包含子View,也可能是子View消费了)
所以也可以说当返回false的时候,是回传给父的dispatchTouchEvent,告诉它我不需要消费这个事件,让父自己处理。在源码中就可以看到父的dispatchTouchEvent在判断子的dispatchTouchEvent是false的时候就去调用自身的onTouchEvent,如果自己也不消费,继续回传它的父ViewGroup的dispatchTouchEvent。
所以总结就是父ViewGroup的dispatchTouchEvent关注的是事件在它以及它包含的子View里有没有被消费掉。
如果interceptTouchEvent 返回 true ,也就是拦截掉了,则交给它的 onTouchEvent 来处理。此时已经完全进入interceptTouchEvent。