事件分发
涉及的函数
dispatchTouchEvent
分发事件,View和ViewGroup中都存在。 return true:表示事件会由当前的View或ViewGroup消费掉,并且不再向下传递事件。 return false:表示事件不在往下传递,并且叫给上层onTouchEvent进行处理。
2.onInterceptTouchEvent
截获分发事件,只存在ViewGroup中。 return true:表示事件被拦截,该事件交给当前视图处理(OnTouchEvent)。 return false:表示事件没有被拦截继续往下传递。默认是false。
3.onTouchEvent
处理事件,只存在View中。 return true:表示事件已经被处理。 return false:表示事件没有被处理,继续向上传递事件。
看图说话
- 正常的事件处理是一个U型结构。
- 用户点击操作会从最外的Activity开始分发事件,Activity—— ViewGroup——>View。
- 事件处理会从View开始处理,View——>ViewGroup——>Activity。
- 如果dispatchTouchEvent返回true就说明已经dispatchTouchEvent已经消费掉了;如果返回false就回溯给上一个View进行处理。
- onInterceptTouchEvent如果是true,就截获事件交给当前视图处理;如果为返回false就继续往下传递事件,默认为false。
- onTouchEvent如果返回true,就说明事件已经被消费掉;如果返回false就继续往上传递事件。
请参考
图解 Android 事件分发机制 http://www.jianshu.com/p/7cf6eb4ce7a9
源代码分析(sdk 23)
ViewGroup.dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
/**
* 检查是否被遮住
*/
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
/**
* 在DOWN事件 初始化事件
*/
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
/**
* 检测是否被中断,如果中断
*/
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
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;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
/**
* 如果没有取消也没有中断,那么就搜寻所有的子视图
*/
if (!canceled && !intercepted) {
......
}
if (mFirstTouchTarget == null) {
/**
* 分发事件,在DOWN事件的时候
*/
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
/**
* 向目标对象发送事件,如果必要的情况下可以取消事件
*/
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
View.dispatchTounchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
.......
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
/**
* 判断是否是DOWN,如果是就停止滚动
*/
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
/**
* 判断是否被覆盖
*/
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
/**
* 判断li.mOnTouchListener是否为空
* 判断View是否是Enable
* 判断mOnTouchListener.onTouch返回是否为true
*
* 如果上述条件成立 dispatchTouchEvent直接返回true
*/
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
......
return result;
}
ViewGroup.onInterceptTouchEvent
public boolean onInterceptTouchEvent(MotionEvent ev) {
//不好意思默认就只有一句话
return false;
}
onTouchEvent
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
/**
* 在View Disable的情况下判断
*/
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
/**
* 这里开始onTouch都是返回true
*/
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
/**
* 对点击事件进行处理
* 主要处理performClick()
*/
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0);
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
}
}
break;
}
return true;
}
return false;
}