Android笔记-View中的dispatchTouchEvent和onTouchEvent源码解析
2018-01-20 13:30
471 查看
上一篇:从ViewGroup的dispatchTouchEvent源码分析事件分发机制
本编说说View中的dispatchTouchEvent和onTouchEvent
View中的dispatchTouchEvent比较简单,因为view只能是最底层的,所以没有向下传递的机制,也没有拦截机制。
其dispatchTouchEvent方法源码如下:
我们看关键部分
上面的代码中我们看到:onFilterTouchEventForSecurity方法是用来方法过滤触摸事件以应用安全策略。一般返回的是true。代码如下:
继续看代码:
这边是判断:如果给view设置了onTouchListener,则会优先执行onTouchListener的
onTouch方法,如果onTouch返回true,表示事件已被消费,不会执行下面的onTouchEvent方法,如果onTouch返回false,表示事件没有被消费,会执行下面的onTouchEvent方法。
可在上一篇文章中知道:如果dispatchTouchEvent会返回result为(true),即通知上层控件事件已被消费,当上层控件接受到子view的dispatchTouchEvent返回true时候,不会执行自己的onTouchEvent方法。
我们再看onTouchEvent方法是如何消耗事件的:
上代码简单总结为:
1. 如果view的clickable为TRUE或者longclickable为true,则会消费事件。
2. view的disable属性不会影响view的事件是否消费,仍然由clickable决定。
3. 在ACTION_UP中会触发performClick方法,该方法会判断是否设置了点击事件,如果有则会触发点击事件。源码如下:
这个方法虽然有返回boolean类型的值,但是这个boolean值只能表示点击事件是否触发,在onTouchEvent里面没有用到。所以该方法也不会影响view中事件是否被消费。
至此整个事件分发机制解析完毕。
事件传递流程图:
事件消费机制流程图
本编说说View中的dispatchTouchEvent和onTouchEvent
View中的dispatchTouchEvent比较简单,因为view只能是最底层的,所以没有向下传递的机制,也没有拦截机制。
其dispatchTouchEvent方法源码如下:
/** * Pass the touch screen motion event down to the target view, or this * view if it is the target. * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. */ public boolean dispatchTouchEvent(MotionEvent event) { // If the event should be handled by accessibility focus first. if (event.isTargetAccessibilityFocus()) { // We don't have focus or no virtual descendant has it, do not handle the event. if (!isAccessibilityFocusedViewOrHost()) { return false; } // We have focus and got the event, then use normal event dispatch. event.setTargetAccessibilityFocus(false); } boolean result = false; if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); } final int actionMasked = event.getActionMasked(); if (actionMasked == MotionEvent.ACTION_DOWN) { // Defensive cleanup for new gesture stopNestedScroll(); } 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; } } if (!result && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } // Clean up after nested scrolls if this is the end of a gesture; // also cancel it if we tried an ACTION_DOWN but we didn't want the rest // of the gesture. if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && !result)) { stopNestedScroll(); } return result; }
我们看关键部分
//过滤触摸事件以应用安全策略 if (onFilterTouchEventForSecurity(event)) { //handleScrollBarDragging返回的是false,暂时忽略代码 ... //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true;//如果li.mOnTouchListener.onTouch消耗事件 } //如果li.mOnTouchListener.onTouch没有消耗事件 if (!result && onTouchEvent(event)) { result = true;//如果onTouchEvent消耗事件 } }
上面的代码中我们看到:onFilterTouchEventForSecurity方法是用来方法过滤触摸事件以应用安全策略。一般返回的是true。代码如下:
/** * Filter the touch event to apply security policies. * * @param event The motion event to be filtered. * @return True if the event should be dispatched, false if the event should be dropped. * * @see #getFilterTouchesWhenObscured */ public boolean onFilterTouchEventForSecurity(MotionEvent event) { //noinspection RedundantIfStatement if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0 && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) { // Window is obscured, drop this touch. return false; } return true; }
继续看代码:
//noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true;//如果li.mOnTouchListener.onTouch消耗事件 } //如果li.mOnTouchListener.onTouch没有消耗事件 if (!result && onTouchEvent(event)) { result = true;//如果onTouchEvent消耗事件 }
这边是判断:如果给view设置了onTouchListener,则会优先执行onTouchListener的
onTouch方法,如果onTouch返回true,表示事件已被消费,不会执行下面的onTouchEvent方法,如果onTouch返回false,表示事件没有被消费,会执行下面的onTouchEvent方法。
可在上一篇文章中知道:如果dispatchTouchEvent会返回result为(true),即通知上层控件事件已被消费,当上层控件接受到子view的dispatchTouchEvent返回true时候,不会执行自己的onTouchEvent方法。
我们再看onTouchEvent方法是如何消耗事件的:
/** * Implement this method to handle touch screen motion events. * <p> * If this method is used to detect click actions, it is recommended that * the actions be performed by implementing and calling * {@link #performClick()}. This will ensure consistent system behavior, * including: * <ul> * <li>obeying click sound preferences * <li>dispatching OnClickListener calls * <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when * accessibility features are enabled * </ul> * * @param event The motion event. * @return True if the event was handled, false otherwise. */ 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的CLICKABLE,LONG_CLICKABLE,CONTEXT_CLICKABLE决定 //如果有一个为true则为true final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE; //判断是否DISABLED,可以看出DISABLED不会消耗事件。 if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return clickable;//这边看出DISABLED不会消耗事件。 } //searchview中用到,这边暂时不管 if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } // 可以点击或者指示此视图可以在悬停或长按时显示工具提示。返回true if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { switch (action) { case MotionEvent.ACTION_UP: mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; if ((viewFlags & TOOLTIP) == TOOLTIP) { handleTooltipUp(); } if (!clickable) { removeTapCallback(); removeLongPressCallback(); mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; break; } 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: if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) { mPrivateFlags3 |= PFLAG3_FINGER_DOWN; } mHasPerformedLongPress = false; if (!clickable) { checkForLongClick(0, x, y); break; } 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, x, y); } break; case MotionEvent.ACTION_CANCEL: if (clickable) { setPressed(false); } removeTapCallback(); removeLongPressCallback(); mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; break; case MotionEvent.ACTION_MOVE: if (clickable) { drawableHotspotChanged(x, y); } // Be lenient about moving outside of buttons if (!pointInView(x, y, mTouchSlop)) { // Outside button // Remove any future long press/tap checks removeTapCallback(); removeLongPressCallback(); if ((mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; } break; } return true; } return false; }
上代码简单总结为:
1. 如果view的clickable为TRUE或者longclickable为true,则会消费事件。
2. view的disable属性不会影响view的事件是否消费,仍然由clickable决定。
3. 在ACTION_UP中会触发performClick方法,该方法会判断是否设置了点击事件,如果有则会触发点击事件。源码如下:
/** * Call this view's OnClickListener, if it is defined. Performs all normal * actions associated with clicking: reporting accessibility event, playing * a sound, etc. * * @return True there was an assigned OnClickListener that was called, false * otherwise is returned. */ 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); notifyEnterOrExitForAutoFillIfNeeded(true); return result; }
这个方法虽然有返回boolean类型的值,但是这个boolean值只能表示点击事件是否触发,在onTouchEvent里面没有用到。所以该方法也不会影响view中事件是否被消费。
至此整个事件分发机制解析完毕。
事件传递流程图:
事件消费机制流程图
相关文章推荐
- Android viewGoup.dispatchTouchEvent(ev)源码
- Android事件处理(一)——ViewGroup的dispatchTouchEvent 函数源码详解
- Android事件处理之View$dispatchTouchEvent(ev)
- Android事件分发详解(三)——ViewGroup的dispatchTouchEvent()源码学习
- Android View系统源码分析(四)—— 各种消息监测的基本实现方法&View.dispatchTouchEvent()
- Android事件分发详解(三)——ViewGroup的dispatchTouchEvent()源码学习
- Android事件分发详解(三)——ViewGroup的dispatchTouchEvent()源码学习
- Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)dispatchtouchevent,ontouch,ontouchevent,onclick
- android中viewgroup的dispatchTouchEvent方法源代码
- 从源码角度带你分析 Android View 事件分发 dispatchTouchEvent,onTouch,onTouchEvent,onClick逻辑顺序过程(一)
- Android事件分发详解(三)——ViewGroup的dispatchTouchEvent()源码学习
- Android事件分发详解(三)——ViewGroup的dispatchTouchEvent()源码学习
- Android事件分发04——View的dispatchTouchEvent
- Android dispatchTouchEvent View事件分发
- 自定义控件(视图)2期笔记14:自定义视图之View事件分发 dispatchTouchEvent,onTouch,onTouchEvent,onClick逻辑顺序过程
- android dispatchTouchEvent方法查找包含点击坐标的view;
- 【Android View】Android中View对触摸事件的处理和传递dispatchTouchEvent、onInterceptTouchEvent
- Android事件分发03——ViewGrop的dispatchTouchEvent
- Android事件处理(二)——View的dispatchTouchEvent 函数源码详解
- android View属性之dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent