让我自己看源码是看不懂的,还好有这么多大神。
ViewGroup 在分发事件时会调用子 View 的 dispatchTouchEvent 方法。
对于 View (非 ViewGroup)来说,dispatchTouchEvent 方法被理解为 "handleEvent" 可能会更会合适。
该方法的返回值表示 Event 是否被 View 消费了,true 表示消费了。
public class MyView extends View { public MyView(Context context) { super(context); } public MyView(Context context, AttributeSet attrs) { super(context, attrs); } public MyView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean dispatchTouchEvent(MotionEvent event) { Log.e("MyView", "dispatchTouchEvent start" ); boolean result = super.dispatchTouchEvent(event); Log.e("MyView", "dispatchTouchEvent end " + result); return result; } }
点击后日志如下:
03-25 10:48:00.296 18114-18114/com.unicorn.viewtry E/MyView: dispatchTouchEvent start
03-25 10:48:00.296 18114-18114/com.unicorn.viewtry E/MyView: dispatchTouchEvent end false
基本 View 并没有消费掉 Event。
来看看 View 是如何 handle event 的,重点的部分可能就这么点,源码是 6.0 版本的。
boolean result = false; 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; } return result;
findViewById(R.id.myView).setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { Log.e("MyView", "onTouchListener onTouch start"); Log.e("MyView", "onTouchListener onTouch end " + false); // 不消费事件 return false; } });点击后日志如下:
findViewById(R.id.myView).setEnabled(false);那么 OnTouch 事件不会被触发。
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); }注释上说,disabled 的 View 依旧会消费事件,即使它什么也没做。
if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } }假如设置了 TouchDelegate ,那么由它来负责。
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { // 之后分析 return true; } return false;如果 View 是 clickable 或者 long_clickable 的,则消费事件。
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;
private void checkForLongClick(int delayOffset) { if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) { mHasPerformedLongPress = false; if (mPendingCheckForLongPress == null) { mPendingCheckForLongPress = new CheckForLongPress(); } mPendingCheckForLongPress.rememberWindowAttachCount(); postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout() - delayOffset); } }这里 post 了一个 500 毫秒的延时事件。
private final class CheckForLongPress implements Runnable { private int mOriginalWindowAttachCount; @Override public void run() { if (isPressed() && (mParent != null) && mOriginalWindowAttachCount == mWindowAttachCount) { if (performLongClick()) { mHasPerformedLongPress = true; } } } public void rememberWindowAttachCount() { mOriginalWindowAttachCount = mWindowAttachCount; } }如果 500 毫秒后 View 依旧是 pressed 状态,则执行 performLongClick 函数,并将返回结果保存在 mHasPerformedLongPress 中。
public boolean performLongClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); boolean handled = false; ListenerInfo li = mListenerInfo; if (li != null && li.mOnLongClickListener != null) { handled = li.mOnLongClickListener.onLongClick(View.this); } if (!handled) { handled = showContextMenu(); } if (handled) { performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); } return handled; }在 performLongClick 函数中 OnLongClick 事件被触发。
case MotionEvent.ACTION_UP: 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;如果 OnLongClickListener 被执行且返回 true,那么 perfermClick 函数不会执行。
public boolean performClick() { final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); return result; }
长按日志如下:
03-25 13:25:22.730 15131-15131/com.unicorn.viewtry E/MyView 0: dispatchTouchEvent start
03-25 13:25:22.730 15131-15131/com.unicorn.viewtry E/MyView 0: onTouchListener onTouch start
03-25 13:25:22.731 15131-15131/com.unicorn.viewtry E/MyView 0: onTouchListener onTouch end false
03-25 13:25:22.731 15131-15131/com.unicorn.viewtry E/MyView 0: onTouchEvent start
03-25 13:25:22.731 15131-15131/com.unicorn.viewtry E/MyView 0: onTouchEvent end true
03-25 13:25:22.731 15131-15131/com.unicorn.viewtry E/MyView 0: dispatchTouchEvent end true
03-25 13:25:23.230 15131-15131/com.unicorn.viewtry E/MyView: onLongClickListener onLongClick start
03-25 13:25:23.230 15131-15131/com.unicorn.viewtry E/MyView: onLongClickListener onLongClick end false
03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView 1: dispatchTouchEvent start
03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView 1: onTouchListener onTouch start
03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView 1: onTouchListener onTouch end false
03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView 1: onTouchEvent start
03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView 1: onTouchEvent end true
03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView 1: dispatchTouchEvent end true
03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView: onClickListener onClick start
03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView: onClickListener onClick end false
这里有个小细节,当 Down event(0 和 1 分别代表 Down 和 Up) 被 View 消费后。
View 才会收到对应的 Up event,这就涉及到 ViewGroup 是如何分发事件了。
推荐:
http://blog.csdn.net/dmk877/article/details/48781845