自定義View系列教程06--詳解View的Touch事件處理
來源:程序員人生 發布時間:2016-07-05 14:16:39 閱讀次數:2525次
自定義View系列教程01--經常使用工具介紹
自定義View系列教程02--onMeasure源碼詳實分析
自定義View系列教程03--onLayout源碼詳實分析
自定義View系列教程04--Draw源碼分析及其實踐
自定義View系列教程05--示例分析
自定義View系列教程06--詳解View的Touch事件處理
自定義View系列教程07--詳解ViewGroup分發Touch事件
自定義View系列教程08--滑動沖突的產生及其處理
先上圖:

說在前面:
View的事件分發簡單記憶方法::dispathTouchEvent----->onTouchEvent------->onClick
如上圖,我把View的事件分發分為兩大塊:
第1塊:在dispatchTouchEvent()方法中。
1 首先判斷當前的OnTouchListener是不是為null。
2 判斷當前的控件是不是是ENABLED狀態。
3 判斷onTouch方法返回的是true還是false。
如果以上3步: 有任何1個步驟返回false。那末就調用onTouchEvent(onTouchEvent返回true,則dispathTouchEvent 返回true;返回false,則dispathTouchEvent 返回false。)
所有步驟都返回true。不調用其他方法,dispathTouchEvent ()返回true;
第2塊:在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去設置1個點擊事件
imageView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
...
}
});
//點擊事件的處理規則,mOnClickListener甚么時候傳遞進來的??
public boolean performClick() {
...
if (mOnClickListener != null) {
....
mOnClickListener.onClick(this);
...
}
...
}<pre name="code" class="java"> //view中setOnClickListener方法
public void setOnClickListener(OnClickListener l) {
//如果當前控件沒有點擊事件,設置1個點擊事件
if (!isClickable()) {
setClickable(true);
}
mOnClickListener = l;
}
--------------------------------我是華麗的分割線-----------------------ok請看正文---------------------------------------------------------------------------------------
在之前的幾篇文章中結合Andorid源碼還有示例分析完了自定義View的3個階段:measure,layout,draw。 在自定義View的進程中我們還常常需要處理View的Touch事件,這就觸及到了大伙常說的Touch事件的分發。其實,這1部份還是有些復雜的,而且有的地方不是很好理解,特別是對剛上路的新司機來講常常理不清楚,欲求不滿,欲罷不能——想弄懂卻又覺得難,想放棄又覺得舍不得。
好吧,我也經歷過這些痛楚,感同身受。
所以,我們就從相對而言比較簡單的View的Touch事件處理入手開始這部份知識的學習和總結。
滴滴,開車了,車門行將關閉。上車請刷卡,沒卡的乘客請投幣。
如果1個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事件是不是被消費。
在此,對該部份源碼的核心部份和主要邏輯做1個梳理
第1步:
調用TouchListener中的onTouch()處理Touch事件,請參見代碼第31⑶2行
該if判斷中1共包括了4個條件,必須同時滿足時才表示Touch事件被消費
- li != null
ListenerInfo是View中的1個靜態類,包括了幾個Listener,比如TouchListener,FocusChangeListener,LayoutChangeListeners,ScrollChangeListener等等。1般情況下它均不為null,所以我們不用過量關注它。 - li.mOnTouchListener != null
mOnTouchListener是由View設置的,比如mButton.setOnTouchListener()。所以如果View設置了Touch監聽那末,那末mOnTouchListener不空;反之,mOnTouchListener為null - (mViewFlags & ENABLED_MASK) == ENABLED
當前View可用(ENABLED)。通常可調用view.setEnabled( )設置View是不是可用 - li.mOnTouchListener.onTouch(this, event)
這1點實際上是在li.mOnTouchListener != null的基礎上繼續判斷。判斷TouchListener的onTouch( )方法是不是消耗了Touch事件。返回值為true表示消費掉該事件,false表示未消費。
在這4個條件中,我們通常最關心的就是最后1個:TouchListener的onTouch()方法。假設這4個條件中的任意1個不滿足,那末result仍為false;則進入下1步
第2步:
調用View本身的onTouchEvent()處理Touch事件,請參見代碼第36⑶8行
if (!result && onTouchEvent(event)) {
result = true;
}
嗯哼,看到了吧:如果在上1步中Touch事件被消費result為true,就不會履行這3行代碼了。該處調用了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;
}
這段代碼略微復雜1些,在此分析幾個核心點。
-
當View為disable時對Touch的處理,請參見代碼第7⑴6行。
若1個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的,但只要滿足這3個條件中的1個,它就會消費掉Touch事件但不再回調view的onClick( )方法
-
處理ACTION_DOWN,ACTION_MOVE,ACTION_UP事件等,請參見代碼第24⑴16行。
在此請注意在對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⑴21行。
在該處請特別注意:
如果View是enable的,只要該View滿足CLICKABLE和LONG_CLICKABLE和CONTEXT_CLICKABLE這3者的任意1個(請參見代碼第24⑵6行)不論當前的action是甚么,該onTouchEvent()返回的均是true(請參見代碼第118行);而且會在ACTION_UP時處理click事件。
同理,如果這3個條件都不滿足,該onTouchEvent()返回的是false。
也請注意1個細節:
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。
第3步:
返回Touch事件是不是被消費,請參見代碼第52行
以上就為View對Touch事件的主要步驟。
在此我畫了1個簡單的流程圖,現結合該圖和剛才的源碼分析對View的Touch事件處理流程做1個總結。

- View處理Touch事件的整體流程
dispatchTouchEvent()—>onTouch()—>onTouchEvent()—>onClick()
Touch事件最早傳入dispatchTouchEvent()中;如果該View存在TouchListener那末會調用該監聽器中的onTouch()。在此以后如果Touch事件未被消費,則會履行到View的onTouchEvent()方法,在該方法中處理ACTION_UP事件時若該View存在ClickListener則會調用該監聽器中的onClick() - onTouch()與onTouchEvent()和click3者的區分和聯系
2.1 onTouch()與onTouchEvent()都是處理觸摸事件的API
2.2 onTouch()屬于TouchListener接口中的方法,是View暴露給用戶的接口便于處理觸摸事件,而onTouchEvent()是Android系統本身對Touch處理的實現
2.3 先調用onTouch()后調用onTouchEvent()。而且只有當onTouch()未消費Touch事件才有可能調用到onTouchEvent()。即onTouch()的優先級比onTouchEvent()的優先級更高。
2.4 在onTouchEvent()中處理ACTION_UP時會利用ClickListener履行Click事件。所以Touch的處理是優先于Click的
2.5 簡單地說3者履行順序為:onTouch()–>onTouchEvent()–>onClick() - View沒有事件的攔截(onInterceptTouchEvent( )),ViewGroup才有,請勿混淆
關于View對Touch事件的處理就分析到此。
滴滴,到站了,下車的乘客們請往后門走。
原文鏈接:http://blog.csdn.net/lfdfhl/article/details/51559847
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈