最近在做一个效果,viewgroup要响应滑动事件,它的一个子view还要响应点击事件。为了实现该效果,不得不重新研究Android 事件处理流程。
理解下面三个方法的左右对实现该效果有很大的帮助,
public boolean dispatchTouchEvent(MotionEvent event)
public boolean onTouchEvent(MotionEvent event)
public boolean onInterceptTouchEvent(MotionEvent event)
其实View中没有onInterceptTouchEvent方法。
至于这三个方法的作用,我大致都比较清楚。只是对于不同的返回值代表的效果,需要做测试。
经过测试,dispatchTouchEvent方法在返回true或者false的时候,都不会向下传递事件,也就是说子view的dispatchTouchEvent方法不会被调用。这两者不同的是,true表示DOWN事件后续的事件会继续接收,而false则不会接收。当返回super.dispatchTouchEvent的时候,也就是调用ViewGroup的dispatchTouchEvent方法,则会调用子view的dispatchTouchEvent方法。在ViewGroup的dispatchTouchEvent中会调用onInterceptTouchEvent方法,该方法表示是否拦截事件。在ViewGroup中的实现很简单,如下:
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
表示默认是不拦截的。
如果不拦截,在在ViewGroup的dispatchTouchEvent方法中会调用每个子view的dispatchTouchEvent方法,关键代码如下:
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder ?
getChildDrawingOrder(childrenCount, i) : i;
final View child = children[childIndex];
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//在这里调用每个子view的dispatchTransformedTouchEvent方法
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = childIndex;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
其中child.dispatchTouchEvent(event)这句在执行的时候如果是该child是Viewgroup会继续递归调用上述viewgroup中的dispatchTouchEvent方法。如果child是一个View的话,会调用View中的dispatchTouchEvent方法,该方法实现如下:
/**
* 返回true表示该事件被消化,否则返回false
*/
public boolean dispatchTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
return true;
}
if (onTouchEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
在下面这个判断中,会调用View的onTouchListener的onTouch方法。
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
return true;
}
从上面分析中可以看出事件处理的大致流程。在
dispatchTouchEvent方法中会调用到
onInterceptTouchEvent方法来决定是否调用子View的
dispatchTouchEvent方法和onTouchEvent方法。