本篇博文是Android觸摸事件分發機制系列博文的第二篇,主要是從解讀View類的源碼入手,根據源碼理清View事件分發原理,并掌握View事件分法機制。特別聲明的是,本源碼解讀是基于最新的Android6.0版本。
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 amp;amp; li.mOnTouchListener != null amp;amp; (mViewFlags amp; ENABLED_MASK) == ENABLED amp;amp; li.mOnTouchListener.onTouch(this, event)) { result = true;// 意味著這個View需要事件分發 } // 如果上面的if沒有讓result為true 且 onTouchEvent(event)返回為true,則result=true if (!result amp;amp; onTouchEvent(event)) { result = true; } } if (!result amp;amp; 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 amp;amp; !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 amp;amp; li.mOnTouchListener != null amp;amp; (mViewFlags amp; ENABLED_MASK) == ENABLED amp;amp; 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 amp;amp; li.mOnTouchListener != null amp;amp; (mViewFlags amp; ENABLED_MASK) == ENABLED amp;amp; li.mOnTouchListener.onTouch(this, event))
也就是說onTouch能夠得到執行需要兩個前提條件,第一mOnTouchListener不能為null;第二當前View必須是ENABLED的。
附上View內部的監聽信息類ListenerInfo 源碼
這里我們知道點擊、長按點擊、上下文點擊等監聽都是在此類中定義的就可以了。
static class ListenerInfo { protected OnFocusChangeListener mOnFocusChangeListener; private ArrayListlt;OnLayoutChangeListenergt; mOnLayoutChangeListeners; protected OnScrollChangeListener mOnScrollChangeListener; private CopyOnWriteArrayListlt;OnAttachStateChangeListenergt; 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 amp; ENABLED_MASK) == DISABLED) {// 如果View的狀態為DISABLED不可以用狀態 if (action == MotionEvent.ACTION_UP amp;amp; (mPrivateFlags amp; PFLAG_PRESSED) != 0) { setPressed(false);// 設置View的狀態 } // 如果我們把View的點擊、長按點擊、上下文點擊中的任意一個設置為true,則return返回true,不可用狀態下照樣消費此事件 return (((viewFlags amp; CLICKABLE) == CLICKABLE || (viewFlags amp; LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags amp; 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 amp; CLICKABLE) == CLICKABLE || (viewFlags amp; LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags amp; CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { switch (action) { case MotionEvent.ACTION_UP:// 抬起操作事件 boolean prepressed = (mPrivateFlags amp; PFLAG_PREPRESSED) != 0; if ((mPrivateFlags amp; PFLAG_PRESSED) != 0 || prepressed) { // take focus if we don't have it already and we should in // touch mode. boolean focusTaken = false; if (isFocusable() amp;amp; isFocusableInTouchMode() amp;amp; !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 amp;amp; !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 amp; 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 amp;amp; 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的事件分發的流程
Tags: 安卓開發
文章來源:http://blog.csdn.net/mynameishuangshuai/article/de