Android6.0源码解读之View点击事件分发机制
2016-10-23 21:22
351 查看
本篇博文是Android点击事件分发机制系列博文的第二篇,主要是从解读View类的源码入手,根据源码理清View点击事件分发原理,并掌握View点击事件分法机制。特别声明的是,本源码解读是基于最新的Android6.0版本。
各位童鞋可以参考下面链接进行系统学习
(一)Android6.0触摸事件分发机制解读
(二)Android6.0源码解读之View点击事件分发机制
(三)Android6.0源码解读之ViewGroup点击事件分发机制
(四)Android6.0源码解读之Activity点击事件分发机制
通过上面36行代码,我们得到以下结论:
对于一个根View来说,点击事件产生后,它的dispatchTouchEvent会被首先调用,而在dispatchTouchEvent中先执行的是onTouch方法,如果37行代码中没有让result返回true,则在在41行中才去执行onTouchEvent方法,因此可以得到的结论是:
结论1:在dispatchTouchEvent方法中先执行onTouch方法,后执行onClick方法(onClick方法在onTouchEvent方法中的performClick方法中执行)
结论2:只有当34行代码if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event))条件不成立时,才会调用onTouchEvent方法,此时的onTouchEvent返回值就是dispatchTouchEvent的返回值。
结论3:如果view为DISENABLED,则:onTouchListener里面内容不会执行,程序就会去执行onTouchEvent(event)方法,此时的onTouchEvent返回值就是dispatchTouchEvent的返回值。
结论4:如果onTouch方法返回true,并且消费了事件,那么就不会执行onTouchEvent方法,也就不可能执行其中的performClick方法里的onClick方法。
也就是说onTouch能够得到执行需要两个前提条件,第一mOnTouchListener不能为null;第二当前View必须是ENABLED的。
通过以上64行代码,我们可以得出如下结论:
结论1:onClick会发生的前提是当前View是可以点击的,并且它收到了down和up的事件
结论2:View的onTouchEvent默认都会消耗事件(返回true),除非它是不可点击的(clickable和longClickable同时为false)。View的longClickable属性默认都为false,clickable要分情况,比如Button的clickable属性默认为true,而TextView的clickable属性默认为false。
结论3:View的ENABLED属性不影响onTouchEvent的默认返回值,哪怕一个View是DISABLED状态的,只要它的clickable或者longClickable有一个为true,那么它的onTouchEvent就返回true
各位童鞋可以参考下面链接进行系统学习
(一)Android6.0触摸事件分发机制解读
(二)Android6.0源码解读之View点击事件分发机制
(三)Android6.0源码解读之ViewGroup点击事件分发机制
(四)Android6.0源码解读之Activity点击事件分发机制
View事件分发中的两个重要方法的源码解析
关于View事件分发,我们重点需要解读dispatchTouchEvent和onTouchEvent两个方法。(一)dispatchTouchEvent源码解析
/** * dispatchTouchEvent用来进行事件分发。如果事件能够传递给当前View,那么此方法一定会被调用, * 返回结果受当前view的onTouchEvent和下级的dispatchTouchEvent方法的影响,表示是否消耗当前的事件。 */ 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) { // 当我们手指按到View上时,其他的依赖滑动都要先停下 stopNestedScroll(); } // 如果当前View未被其他窗口遮盖住 if (onFilterTouchEventForSecurity(event)) { // ListenerInfo保存监听的信息 ListenerInfo li = mListenerInfo; // 如果监听li对象!=null 且我们通过setOnTouchListener设置了监听让li.mOnTouchListener != null // 且View为ENABLED 且如果onTouch的返回值为true,则把上面定义为false的result赋值为true // 则result=true if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true;// 意味着这个View需要事件分发 } // 如果上面的if没有让result为true 且 onTouchEvent(event)返回为true,则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; }
通过上面36行代码,我们得到以下结论:
对于一个根View来说,点击事件产生后,它的dispatchTouchEvent会被首先调用,而在dispatchTouchEvent中先执行的是onTouch方法,如果37行代码中没有让result返回true,则在在41行中才去执行onTouchEvent方法,因此可以得到的结论是:
结论1:在dispatchTouchEvent方法中先执行onTouch方法,后执行onClick方法(onClick方法在onTouchEvent方法中的performClick方法中执行)
结论2:只有当34行代码if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event))条件不成立时,才会调用onTouchEvent方法,此时的onTouchEvent返回值就是dispatchTouchEvent的返回值。
结论3:如果view为DISENABLED,则:onTouchListener里面内容不会执行,程序就会去执行onTouchEvent(event)方法,此时的onTouchEvent返回值就是dispatchTouchEvent的返回值。
结论4:如果onTouch方法返回true,并且消费了事件,那么就不会执行onTouchEvent方法,也就不可能执行其中的performClick方法里的onClick方法。
onTouch和onTouchEvent的区别
这两个方法都是在View的dispatchTouchEvent中调用的,onTouch方法优先于onTouchEvent方法执行。如果在onTouch方法中通过返回true将事件消费掉,onTouchEvent将不会再执行。另外,在源码34行中我们看到if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event))
也就是说onTouch能够得到执行需要两个前提条件,第一mOnTouchListener不能为null;第二当前View必须是ENABLED的。
附上View内部的监听信息类ListenerInfo 源码
这里我们知道点击、长按点击、上下文点击等监听都是在此类中定义的就可以了。static class ListenerInfo { protected OnFocusChangeListener mOnFocusChangeListener; private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners; protected OnScrollChangeListener mOnScrollChangeListener; private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners; public OnClickListener mOnClickListener;// 点击监听 protected OnLongClickListener mOnLongClickListener;// 长按点击监听 protected OnContextClickListener mOnContextClickListener;// 上下文点击监听 protected OnCreateContextMenuListener mOnCreateContextMenuListener; private OnKeyListener mOnKeyListener; private OnTouchListener mOnTouchListener; private OnHoverListener mOnHoverListener; private OnGenericMotionListener mOnGenericMotionListener; private OnDragListener mOnDragListener; private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener; OnApplyWindowInsetsListener mOnApplyWindowInsetsListener; }
(二)onTouchEvent源码解析
/** * onTouchEvent * @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(); if ((viewFlags & ENABLED_MASK) == DISABLED) {// 如果View的状态为DISABLED不可以用状态 if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false);// 设置View的状态 } // 如果我们把View的点击、长按点击、上下文点击中的任意一个设置为true,则return返回true,不可用状态下照样消费此事件 return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE); } // 如果View设置有代理,那么还会执行TouchDelegate的onTouchEvent if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } // 如果View的状态不是DISABLED而是ENABLED 且 View的点击、长按点击、上下文点击中的任意一个设置为true(比如说Button默认的点击事件肯定是true,而类似于ImageView则是false) if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { switch (action) { 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)) { // 如果View设置了OnClickListener,那么performClick方法内部会调用它的onClick方法 performClick();// 这里才是重点,正是它包含了Click点击事件 } } } 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; } // 判断它是否在滑动控件里面 boolean isInScrollingContainer = isInScrollingContainer(); // 如果当前View是一个滑动的View,我们触摸后它的子View会延迟一小段时间用于反馈 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; }
通过以上64行代码,我们可以得出如下结论:
结论1:onClick会发生的前提是当前View是可以点击的,并且它收到了down和up的事件
结论2:View的onTouchEvent默认都会消耗事件(返回true),除非它是不可点击的(clickable和longClickable同时为false)。View的longClickable属性默认都为false,clickable要分情况,比如Button的clickable属性默认为true,而TextView的clickable属性默认为false。
结论3:View的ENABLED属性不影响onTouchEvent的默认返回值,哪怕一个View是DISABLED状态的,只要它的clickable或者longClickable有一个为true,那么它的onTouchEvent就返回true
然后执行点击操作方法
/** * 执行点击操作方法 * @return True there was an assigned OnClickListener that was called, false * otherwise is returned. */ public boolean performClick() { final boolean result; final ListenerInfo li = mListenerInfo; // 如果我们设置了Click监听事件,那么这个事件肯定消费掉了 if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this);// 执行了onClick方法 result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); return result; }
最后分别看下设置点击、长按点击和上下文点击监听方法
public void setOnClickListener(@Nullable OnClickListener l) { if (!isClickable()) { // setOnClickListener会将View的CLICKABLE设置为true setClickable(true); } getListenerInfo().mOnClickListener = l; } public void setOnLongClickListener(@Nullable OnLongClickListener l) { if (!isLongClickable()) { // setOnLongClickListener会将View的LONG_CLICKABLE设置为true setLongClickable(true); } getListenerInfo().mOnLongClickListener = l; } public void setOnContextClickListener(@Nullable OnContextClickListener l) { if (!isContextClickable()) { // setOnContextClickListener会将View的CONTEXT_CLICKABLE设置为true setContextClickable(true); } getListenerInfo().mOnContextClickListener = l; }
小结:View的事件分发的流程
相关文章推荐
- Android6.0源码解读之ViewGroup点击事件分发机制
- Android6.0源码解读之Activity点击事件分发机制
- Android事件分发机制源码剖析(2)—顶层View对点击事件的分发过程
- Andriod 从源码的角度详解View,ViewGroup的Touch事件的分发机制
- Andriod 从源码的角度详解View,ViewGroup的Touch事件的分发机制
- Andriod 从源码的角度详解View,ViewGroup的Touch事件的分发机制
- Andriod 从源码的角度详解View,ViewGroup的Touch事件的分发机制
- Andriod 从源码的角度详解View,ViewGroup的Touch事件的分发机制
- Android应用开发原理之从ViewGroup源码分析ViewGroup的事件分发机制
- Andriod 从源码的角度详解View,ViewGroup的Touch事件的分发机制
- Android View 事件分发机制 源码解析
- Andriod 从源码的角度详解View,ViewGroup的Touch事件的分发机制
- Andriod 从源码的角度详解View,ViewGroup的Touch事件的分发机制
- Andriod 从源码的角度详解View,ViewGroup的Touch事件的分发机制
- Andriod 从源码的角度详解View,ViewGroup的Touch事件的分发机制
- Andriod 从源码的角度详解View,ViewGroup的Touch事件的分发机制
- Android View 事件分发机制 源码解析 (上)
- Andriod 从源码的角度详解View,ViewGroup的Touch事件的分发机制
- Andriod 从源码的角度详解View,ViewGroup的Touch事件的分发机制
- Andriod 从源码的角度详解View,ViewGroup的Touch事件的分发机制