編輯:關於Android編程
先上圖:
說在前面:
View的事件分發簡單記憶方法::dispathTouchEvent----->onTouchEvent------->onClick
如上圖,我把View的事件分發分為兩大塊:
第一塊:在dispatchTouchEvent()方法中。
1 首先判斷當前的OnTouchListener是否為null。
2 判斷當前的控件是否是ENABLED狀態。
3 判斷onTouch方法返回的是true還是false。
如果以上三步:有任何一個步驟返回false。那麼就調用onTouchEvent(onTouchEvent返回true,則dispathTouchEvent 返回true;返回false,則dispathTouchEvent 返回false。)
所有步驟都返回true。不調用其他方法,dispathTouchEvent ()返回true;
第二塊:在onTouchEvent方法中。
1.當控件不可用時:
當控件有點擊事件,返回true,但不會調用onClick等點擊事件
當空間無點擊事件,返回false
2.當控件可用時:
無點擊事件,返回false
有點擊事件,就走Switch()判斷,判斷是move,down,up,cancle,最後返回true
在up時,會調用preformClick()------>onClick()事件。
附錄:
//iamgeView調用view上面setOnTouchListener方法。 imageView.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { //返回true,導致事件全部被響應 return false; } }); //給指定的iamgeView去設置一個點擊事件 imageView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { ... } }); //點擊事件的處理規則,mOnClickListener什麼時候傳遞進來的?? public boolean performClick() { ... if (mOnClickListener != null) { .... mOnClickListener.onClick(this); ... } ... }
//view中setOnClickListener方法 public void setOnClickListener(OnClickListener l) { //如果當前控件沒有點擊事件,設置一個點擊事件 if (!isClickable()) { setClickable(true); } mOnClickListener = l; }
--------------------------------我是華麗的分割線-----------------------ok請看正文---------------------------------------------------------------------------------------
在之前的幾篇文章中結合Andorid源碼還有示例分析完了自定義View的三個階段:measure,layout,draw。 在自定義View的過程中我們還經常需要處理View的Touch事件,這就涉及到了大伙常說的Touch事件的分發。其實,這一部分還是有些復雜的,而且有的地方不是很好理解,尤其是對於剛上路的新司機來說經常理不清楚,欲求不滿,欲罷不能——想搞懂卻又覺得難,想放棄又覺得捨不得。
好吧,我也經歷過這些痛楚,感同身受。
所以,我們就從相對而言比較簡單的View的Touch事件處理入手開始這部分知識的學習和總結。
滴滴,開車了,車門即將關閉。上車請刷卡,沒卡的乘客請投幣。
如果一個View(比如Button)接收到Touch,那麼該Touch事件首先會傳入到它的dispatchTouchEvent( )方法,所以我們從這裡開始學習View對Touch事件的處理。
/** * 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 (event.isTargetAccessibilityFocus()) { if (!isAccessibilityFocusedViewOrHost()) { return false; } event.setTargetAccessibilityFocus(false); } boolean result = false; if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); } final int actionMasked = event.getActionMasked(); if (actionMasked == MotionEvent.ACTION_DOWN) { stopNestedScroll(); } if (onFilterTouchEventForSecurity(event)) { 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); } if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && !result)) { stopNestedScroll(); } return result; }嗯哼,這段源碼不長,除了注釋就剩下不到100行了。該方法的輸入參數為event它表示Touch事件,這個很好理解;那麼它的返回值有是什麼含義呢?該boolean值表示的是Touch事件是否被消費。
在此,對該部分源碼的核心部分和主要邏輯做一個梳理
第一步:
調用TouchListener中的onTouch()處理Touch事件,請參見代碼第31-32行
該if判斷中一共包含了4個條件,必須同時滿足時才表示Touch事件被消費
這一點其實是在li.mOnTouchListener != null的基礎上繼續判斷。判斷TouchListener的onTouch( )方法是否消耗了Touch事件。返回值為true表示消費掉該事件,false表示未消費。
在這四個條件中,我們通常最關心的就是最後一個:TouchListener的onTouch()方法。假如這四個條件中的任意一個不滿足,那麼result仍為false;則進入下一步
第二步:
調用View自身的onTouchEvent()處理Touch事件,請參見代碼第36-38行
if (!result && onTouchEvent(event)) { result = true; }嗯哼,看到了吧:如果在上一步中Touch事件被消費result為true,就不會執行這三行代碼了。該處調用了onTouchEvent()若該方法返回值false那麼dispatchTouchEvent()的返回值也為false;反之,若該方法返回值為true,那麼dispatchTouchEvent()的返回值亦為true。
既然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(); if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE); } 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: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); } if (prepressed) { setPressed(true, x, y); } if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { removeLongPressCallback(); if (!focusTaken) { 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)) { mUnsetPressedState.run(); } removeTapCallback(); } mIgnoreNextUpEvent = false; break; case MotionEvent.ACTION_DOWN: mHasPerformedLongPress = false; if (performButtonActionOnTouchDown(event)) { break; } boolean isInScrollingContainer = isInScrollingContainer(); if (isInScrollingContainer) { mPrivateFlags |= PFLAG_PREPRESSED; if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPendingCheckForTap.x = event.getX(); mPendingCheckForTap.y = event.getY(); postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { 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); if (!pointInView(x, y, mTouchSlop)) { removeTapCallback(); if ((mPrivateFlags & PFLAG_PRESSED) != 0) { removeLongPressCallback(); setPressed(false); } } break; } return true; } return false; }這段代碼稍微復雜一些,在此分析幾個核心點。
當View為disable時對於Touch的處理,請參見代碼第7-16行。
若一個View是disable的,如果它是CLICKABLE或者LONG_CLICKABLE或CONTEXT_CLICKABLE的就返回true,表示消耗掉了Touch事件。
但是請注意,該view所對應的ClickListener.onClick( )不會有任何的響應。即官方文檔的描述:
A disabled view that is clickable still consumes the touch events, it just doesn’t respond to them.
若View雖然是disable的,但只要滿足這三個條件中的一個,它就會消費掉Touch事件但不再回調view的onClick( )方法
處理ACTION_DOWN,ACTION_MOVE,ACTION_UP事件等,請參見代碼第24-116行。
在此請注意在對於ACTION_UP的處理時調用了performClick(),請參見代碼第50行。
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; }在該方法中調用了view的mOnClickListener.onClick( ),請參見代碼第6行。
嗯哼,看到了吧:我們平常見得很多的Click事件是在View的onTouchEvent( )中處理ACTION_UP時調用的。
返回onTouchEvent()方法的輸出結果,請參見代碼第118-121行。
在該處請尤其注意:
如果View是enable的,只要該View滿足CLICKABLE和LONG_CLICKABLE以及CONTEXT_CLICKABLE這三者的任意一個(請參見代碼第24-26行)不論當前的action是什麼,該onTouchEvent()返回的均是true(請參見代碼第118行);而且會在ACTION_UP時處理click事件。
同理,如果這三個條件都不滿足,該onTouchEvent()返回的是false。
也請注意一個細節:
View的clickable屬性視不同的子View有所差異
比如:Button的clickable默認為true,但是TextView的clickable屬性默認為false。
View的longClickable屬性默認為false。
當然,我們可以通過代碼修改這些默認的屬性。
比如:setClickable()和setLongClickListener()可以改變View的CLICKABLE和LONG_CLICKABLE屬性。
除此以外,通過設置監聽器也可改變某些屬性。
比如:setOnClickListener()會將View的CLICKABLE設置為true;setOnLongClickListener()會將View的LONG_CLICKABLE設置為true。
第三步:
返回Touch事件是否被消費,請參見代碼第52行
以上就為View對於Touch事件的主要步驟。
在此我畫了一個簡單的流程圖,現結合該圖和剛才的源碼分析對View的Touch事件處理流程做一個總結。
Android系統自身對於Touch處理的實現
2.3 先調用onTouch()後調用onTouchEvent()。而且只有當onTouch()未消費Touch事件才有可能調用到onTouchEvent()。即onTouch()的優先級比onTouchEvent()的優先級更高。
2.4 在onTouchEvent()中處理ACTION_UP時會利用ClickListener執行Click事件。所以Touch的處理是優先於Click的
2.5 簡單地說三者執行順序為:onTouch()–>onTouchEvent()–>onClick()
View沒有事件的攔截(onInterceptTouchEvent( )),ViewGroup才有,請勿混淆
關於View對Touch事件的處理就分析到此。
滴滴,到站了,下車的乘客們請往後門走。
引言最近在工作中由於需要客制化系統的關系,接觸到了很多ViewPager相關的UI,發現很多底層原生的界面也還是依然采用ViewPager+Fragment的布局方式,事
最近公司要把百度地圖集成的項目中,於是我就研究了一天百度地圖的SDK,當前的版本:Android SDK v3.0.0 。 雖然百度地圖網上相關代碼比較多,
Android 5.0引入了一個全新的列表控件-RecyclerView,這個控件更為靈活,同時也擁有比ListView和GridView控件較多的優點:例如Item V
使用android studio有很長一段時間了,記得當初使用android studio的時候,最不適應的部分就是gradle了,現在android開發還是比較火熱,越