事件分发在android中非常重要,写了3篇文章总结其中的故事
android事件分发(三)重要的函数requestDisallowInterceptTouchEvent
概述
1、在父控件的onTouch里返回true,子控件就无法收到点击事件,对吗? 这是错误的,阻止事件往下分发得用onInterceptTouchEvent
2、事件传递由父控件传递到子控件,事件消费是子控件优先,子控件不消费就传递给父控件消费
3、子控件是clickable的,但是没有写onclick事件,但是父控件有onclick事件,那么点击子控件,父控件会响应吗?
不会,在onTouchEvent内部,只要控件是clickable或者longcickable的,那就会返回true,意味着子控件消费了事件,不会传递给父控件,这个时候如果想要子控件的点击触发父控件的事件,只能添加子控件的onClick事件,在onClick内部执行与父控件一致的操作
源码分析
public boolean dispatchTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
final float xf = ev.getX();
final float yf = ev.getY();
final float scrolledXFloat = xf + mScrollX;
final float scrolledYFloat = yf + mScrollY;
final Rect frame = mTempRect;
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
mMotionTarget = null;
}
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
ev.setAction(MotionEvent.ACTION_DOWN);
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;
final int count = mChildrenCount;
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
if (frame.contains(scrolledXInt, scrolledYInt)) {
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
if (child.dispatchTouchEvent(ev)) {
mMotionTarget = child;
return true;
}
}
}
}
}
}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
final View target = mMotionTarget;
if (target == null) {
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
return super.dispatchTouchEvent(ev);
}
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
ev.setAction(MotionEvent.ACTION_CANCEL);
ev.setLocation(xc, yc);
if (!target.dispatchTouchEvent(ev)) {
}
mMotionTarget = null;
return true;
}
if (isUpOrCancel) {
mMotionTarget = null;
}
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
ev.setLocation(xc, yc);
if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
mMotionTarget = null;
}
return target.dispatchTouchEvent(ev);
}
这个方法代码比较长,我们只挑重点看。首先在第13行可以看到一个条件判断,如果 disallowIntercept和!onInterceptTouchEvent(ev)两者有一个为true,就会进入到这个条件判断中。disallowIntercept是指是否禁用掉事件拦截的功能,默认是false,也可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改。那么当第一个值为false的时候就会完全依赖第二个值来决定是否可以进入到条件判断的内部,第二个值是什么呢?竟然就是对onInterceptTouchEvent方法的返回值取反!也就是说如果我们在onInterceptTouchEvent方法中返回false,就会让第二个值为true,从而进入到条件判断的内部,如果我们在onInterceptTouchEvent方法中返回true,就会让第二个值为false,从而跳出了这个条件判断。
流程图
onInterceptTouchEvent
SDK给出的说明如下:
· You will receive the down event here.
· The down event will be handled either by a child of this view group, or given to your own onTouchEvent() method to handle; this means you should implement onTouchEvent() to return true, so you will continue to see the rest of the gesture (instead of looking for a parent view to handle it). Also, by returning true from onTouchEvent(), you will not receive any following events in onInterceptTouchEvent() and all touch processing must happen in onTouchEvent() like normal.
· For as long as you return false from this function, each following event (up to and including the final up) will be delivered first here and then to the target's onTouchEvent().
· If you return true from here, you will not receive any following events: the target view will receive the same event but with the action ACTION_CANCEL, and all further events will be delivered to your onTouchEvent() method and no longer appear here.
各种情况总结
总结一下,首先定义down,move,move....,up为一系列动作,从手按下到手放开,基本的规则是:
一般来说我们定义一个viewgroup,不会重写 dispatchTouchEvent ,也不会设置onTouchListener,但是会重写onTouchEvent。
总结一下,事件处理的一些规律。
首先定义down,move,move....,up为一组事件,或者一个cycle(官方说法),从手按下到手放开。
我们从一个viewgroup的角度来分析下一组事件到来,会发生什么事?
可以看上边的流程图
我是viewgroup。
这里说的我的onTouchEvent严格意义上说,该是dispatchTouchEvent,只是dispatchTouchEvent一般都是调用onTouchEvent,所以我们可以简单的认为dispatchTouchEvent就是调用 onTouchEvent 。
基本的规则是:
1 down事件首先会传递到我的onInterceptTouchEvent()方法
2.0 onInterceptTouchEvent返回true我就会拦截事件,我的儿子们不可能收到这个事件,返回false就相当于神马都不干,把事件传递给子控件
2.1.0 down事件的onInterceptTouchEvent返回true,之后会调用我的view:dispatchTouchEvent(大部分情况其实就是onTouchEvent),如果我的onTouchEvent也返回true,那么之后的move事件和up事件都不会经过onInterceptTouchEvent,而是直接传递到onTouchEvent。简单的说,我在某刻拦下来了事件,那么后面的事件都会直接拦截,根本不再调用onInterceptTouchEvent。
2.1.1.down事件的onInterceptTouchEvent返回true,之后会调用自己的onTouchEvent,如果onTouchEvent也返回false,那后面的move,up事件都不会执行。为什么?还记得吗?onTouchEvent返回了false,那view:dispatchTouchEvent就是返回了false,而一个cycle内的前一个action的dispatchTouchEvent返回了false,后面的action直接就丢弃了。
2.2.0 如果down的时候onInterceptTouchEvent返回false,然后某个move的onInterceptTouchEvent返回了true,move的onTouchEvent也返回了true,那么之后的move和up等事件都不会触发onInterceptTouchEvent,而是直接传递给onTouchEvent(注意这里所有的onInterceptTouchEvent和onTouchEvent都是指父视图内的)
2.2.1 如果down的时候onInterceptTouchEvent返回false,那就丢给了儿子处理,儿子处理在onTouchEvent里返回了true,表示他处理好了,那下一个action move依然先传给我的onInterceptTouchEvent,我还可以拦截。以前有个错误的理解,我以为action给儿子了并且成功处理了之后,下一个action就直接给儿子了,其实不对,还是会先给父亲,父亲看看是否拦截,再给儿子。父亲比儿子霸道很多,父亲拦下了一个action,后面的action都默认拦下,但是父亲放过了一个action,下一个action来的时候,父亲还是会拦拦看,这其实就是某些滑动冲突的原因。
注意,这里说的onTouchEvent严格意义上说,该是dispatchTouchEvent,只是dispatchTouchEvent一般都是调用onTouchEvent
测试onInterceptTouchEvent
1、 parent 的onInterceptTouchEvent 为true,onTouchEvent为true
日志
parent onInterceptTouchEvent ACTION_DOWN
parent onTouchEvent ACTION_DOWN
parent onTouchEvent ACTION_MOVE
parent onTouchEvent ACTION_MOVE
parent onTouchEvent ACTION_UP
2、 parent 的onInterceptTouchEvent 为true,onTouchEvent为false
日志
parent onInterceptTouchEvent ACTION_DOWN
parent onTouchEvent ACTION_DOWN
测试代码
public class MyLayout extends LinearLayout {
public MyLayout(Context context) {
super(context);
}
public MyLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
String str=null;
switch(event.getAction()) {
case MotionEvent.ACTION_MOVE:
str="ACTION_MOVE";
break;
case MotionEvent.ACTION_CANCEL:
str="ACTION_CANCEL";
break;
case MotionEvent.ACTION_DOWN:
str="ACTION_DOWN";
break;
case MotionEvent.ACTION_UP:
str="ACTION_UP";
break;
}
LogUtil.d("parent onInterceptTouchEvent " + str);
return true;
// return super.onInterceptHoverEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
String str=null;
switch(event.getAction()) {
case MotionEvent.ACTION_MOVE:
str="ACTION_MOVE";
break;
case MotionEvent.ACTION_CANCEL:
str="ACTION_CANCEL";
break;
case MotionEvent.ACTION_DOWN:
str="ACTION_DOWN";
break;
case MotionEvent.ACTION_UP:
str="ACTION_UP";
break;
}
LogUtil.d("parent onTouchEvent " + str);
return false;
// return super.onTouchEvent(event);
}
}
public class MyButton extends Button {
public MyButton(Context context) {
super(context);
}
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
String str=null;
switch(event.getAction()) {
case MotionEvent.ACTION_MOVE:
str="ACTION_MOVE";
break;
case MotionEvent.ACTION_CANCEL:
str="ACTION_CANCEL";
break;
case MotionEvent.ACTION_DOWN:
str="ACTION_DOWN";
break;<a target=_blank href="http://blog.csdn.net/phelovhl/article/details/8989910">点击打开链接</a>
case MotionEvent.ACTION_UP:
str="ACTION_UP";
break;
}
LogUtil.d("child onTouchEvent"+str);
return super.onTouchEvent(event);
}
}