前言:下面是事件分发流程,其中蓝色表示Activity,粉色表示ViewGroup,黄色表示View
总结:
1、事件是通过父控件流向子控件,父控件可以通过自身dispatchTouchEvent、onInterceptTouchEvent返回值控制是否传递给子控件
2、子控件可以通过getParent().requestDisallowInterceptTouchEvent()来影响父控件是否拦截事件。
注:该方法只能拦截Down以后的事件(因为ViewGroup在dispatchTouchEvent方法中分发Down事件会初始化拦截标识,从而会执行其onInterceptTouchEvent()方法,此时事件还未分发到子view)核心思想是子view已经拥有Down事件,之后的其他事件需要父控件来处理时,采用该方法让父控件处理事件,此时系统会给子view发送cancel事件,因此重写ViewGroup的onInterceptTouchEvent方法要对Down事件做不拦截处理,其他事件根据需求拦截即可
//子view
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
//不让父ViewGroup拦截之后的事件(父不调用onInterceptTouchEvent)
getParent().requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
//让父ViewGroup拦截之后的事件(父调用onInterceptTouchEvent)
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
}
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
//父ViewGroup
public boolean onInterceptTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN){
//DOWN事件不拦截从而继续分发给子view
return super.onInterceptTouchEvent(event);
}else{
return true;
}
}
3、如父控件先响应移动事件,当满足一定条件时需要将移动事件给子控件,此时通过requestDisallowInterceptTouchEvent是无作用的,如下ViewGroup源码。父控件响应事件则mFirstTouchTarget为空且不为DOWN事件,所以都还用不到disallowIntercept也进入不了onInterceptTouchEvent方法。这里可以先将一次MOVE事件修改为DOWN然后调用super.dispatchTouchEvent重新进行一轮的事件分发,之后将MOVE事件直接分发即可。
public boolean dispatchTouchEvent(MotionEvent ev) {
......
//为DOWN事件时重置状态,从而使下面的disallowIntercept在该事件一定为false
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
final boolean intercepted;
// DOWN事件或存在消费事件的子控件
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
......
}
4、onClick(View v)和onLongClick(View v)是在onTouchEvent方法中判断是否执行的。
Down事件添加延时长按任务,(1)若任务到达执行时间未抬起手指,则执行长按事件。当手指抬起时判断已经执行过长按事件则不执行点击事件.(2)若抬起手指长按事件未到达执行时间,则移除长按任务并执行点击事件。
onClick:触发时机是在ACTION_UP中,根据是否执行过长按事件判断,若执行过则不执行,否则执行。
onLongClick:是在ACTION_DOWN中,开启延时任务,若任务到时前,未进行取消则执行长按事件
(1)想一下父控件设置长按事件,子控件设置点击事件,长按子控件后抬起会是怎样的效果?
答:尽快是长按操作,但事件是由子控件来处理,因为其没有设置长按事件,所以在抬起时判断没有执行长按事件则执行点击事件。
(2)两个同层次的按钮A、B,分别设置点击事件,手指放到按钮A上,移动手指至按钮B,然后再将手指移动至按钮A上抬起手指,事件传递过程是怎么样的?
答:按下按钮A,由A响应ACTION_DOWN事件,将背景设置为按压效果,然后移动手指,ACTION_MOVE由按钮A响应;当手指移至按钮B时,ACTION_MOVE始终由按钮A响应,此时由于手指已不在控件A上,会将按压效果清除;再移动至按钮A,虽然ACTION_MOVE由A响应,但不再设置背景按压效果;当手指抬起时,ACTION_UP虽然有按钮A响应,但由于没有按压状态不进行点击事件。(按钮B始终未响应过任何事件,都是由按钮A响应,此过程没有触发ACTION_CANCLE事件,按压背景是由按钮A在ACTION_DOWN时显示,在移出时的ACTION_MOVE事件中清除)
5、我们可以根据实际需求对照事件分发流程在适合的方法中进行控制来解决事件冲突。