最近在做一个效果,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方法。