MotionEvent
事件分发、拦截与消费
- 上表中勾和叉表示的是这3种事件的相关方法在Activity、ViewGroup、View中是否含有该方法
分发流程
Activity.dispatchTouchEvent()
>PhoneWindow.superDispatchTouchEvent()
>DecorView.superDispatchTouchEvent()
>ViewGroup.dispatchTouchEvent()
>View.dispatchTouchEvent()
>View.onTouchEvent()
onTouch、onClick之间的关系
public boolean dispatchTouchEvent(MotionEvent event) {
........省略
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
........省略
}
如果li.mOnTouchListener.onTouch()为false, result也为false,那么onTouchEvent()也会执行,ACTION_UP事件里performClickInternal() > performClick() > li.mOnClickListener.onClick(),说明了onTouch()先于onClick()执行。反过来,如果onTouch()为true,那么result也为true,if判断里onTouchEvent()就不会执行,onClick()也就不会被调用了。
ACTION_DOWN事件重要方法:
- buildTouchDispatchChildList():按照z轴对子view进行排序
- dispatchTransformedTouchEvent():事件分发给谁来处理
事件拦截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//disallowIntercept为true,则onInterceptTouchEvent()不执行
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;
}
内部拦截法:
ViewParent
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
//dispatchTouchEvent()会对ACTION_DOWN事件调用resetTouchState()
if (event.getAction() == MotionEvent.ACTION_DOWN) {
super.onInterceptTouchEvent(event);
return false;
}
return true;
}
ChildView
private int mLastX, mLastY;
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
getParent().requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
外部拦截法:
ViewParent
private int mLastX, mLastY;
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
mLastX = (int) event.getX();
mLastY = (int) event.getY();
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
return true;
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
return super.onInterceptTouchEvent(event);
}
以上只是2个例子,实际案例中具体情况具体分析,但知道原理了就好解决滑动冲突问题了:
父容器拦截事件:
onInterceptTouchEvent()=true
请求父容器不要拦截事件:
getParent().requestDisallowInterceptTouchEvent(true)
网友评论