概述
1、在父控件的onTouch里返回true,子控件就无法收到点击事件,对吗? 这是错误的,阻止事件往下分发得用onInterceptTouchEvent
2、事件传递由父控件传递到子控件,事件消费是子控件优先,子控件不消费就传递给父控件消费
3、子控件是clickable的,但是没有写onclick事件,但是父控件有onclick事件,那么点击子控件,父控件会响应吗?
不会,在onTouchEvent内部,只要控件是clickable或者longcickable的,那就会返回true,意味着子控件消费了事件,不会传递给父控件,这个时候如果想要子控件的点击触发父控件的事件,只能添加子控件的onClick事件,在onClick内部执行与父控件一致的操作
源码分析
![](http://img.aiuxian.com/tech/000/002/304/114_c9f_25c.jpg)
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为一系列动作,从手按下到手放开,基本的规则是:
1.down事件首先会传递到onInterceptTouchEvent()方法
2.onInterceptTouchEvent返回true就会拦截子控件的事件,返回false就相当于神马都不干,把事件传递给子控件
3.down事件的onInterceptTouchEvent返回true,之后会调用自己的onTouchEvent,如果onTouchEvent也返回true,那么之后的move事件和up事件都不会经过onInterceptTouchEvent,而是直接传递到onTouchEvent。
4.down事件的onInterceptTouchEvent返回true,之后会调用自己的onTouchEvent,如果onTouchEvent也返回false,那后面的move,up事件都不会执行
5.如果down的时候onInterceptTouchEvent返回false,然后某个move的onInterceptTouchEvent返回了true,move的onTouchEvent也返回了true,那么之后的move和up等事件都不会触发onInterceptTouchEvent,而是直接传递给onTouchEvent(注意这里所有的onInterceptTouchEvent和onTouchEvent都是指父视图内的)
注意,这里说的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;
case MotionEvent.ACTION_UP:
str="ACTION_UP";
break;
}
LogUtil.d("child onTouchEvent"+str);
return super.onTouchEvent(event);
}
}