一張圖看懂Touch事件的傳遞
2、Touch事件在View中的傳遞
Touch事件的傳遞涉及到的兩個主體——ViewGroup和View,三個方法——dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()。
我們將通過下面這張圖分析Touch事件的傳遞(ViewGroup1只是為了輔助分析,以下的事件分析都是從ViewGroup2開始的)。
由於點選事件是由ACTION_DOWN、ACTION_MOVE和ACTION_UP共同組成的,所以在分析Touch事件傳遞的時候,為了能夠分析透徹,我們結合上圖分別分析以上三種點選事件。
首先看ACTION_DOWN,(由於ViewGroup1與ViewGroup2的處理邏輯一樣,所以我們從ViewGroup2開始分析)當ACTION_DOWN傳遞到ViewGroup2時,首先呼叫ViewGroup2的dispatchTouchEvent()方法,dispatchTouchEvent()方法呼叫onInterceptTouchEvent()判斷ViewGroup2是否攔截,如果ViewGroup2攔截ACTION_DOWN,將會呼叫ViewGroup2的onTouchEvent()處理ACTION_DOWN事件;如果ViewGroup2不攔截ACTION_DOWN,ACTION_DOWN會傳到View,呼叫View的dispatchTouchEvent(),dispatchTouchEvent()方法呼叫onTouchEvent()方法處理ACTION_DOWN事件,如果onTouchEvent()方法返回true,ViewGroup2中就會新增Target;如果View的onTouchEvent()方法返回false,就會呼叫ViewGroup2的onTouchEvent()方法。同理,如果ViewGroup2的onTouchEvent()方法返回true,ViewGroup1中就會新增Target。
然後我們分析ACTION_MOVE和ACTION_UP(由於這兩種Touch事件的處理邏輯相同,我們只分析其中一種,以ACTION_MOVE為例,這裡我們也從ViewGroup2來分析),當ACTION_MOVE傳到ViewGroup2之後,就會呼叫ViewGroup2的dispatchTouchEvent()方法,如果ViewGroup2的Target為null,ViewGroup2預設攔截ACTION_MOVE事件,之後呼叫ViewGroup2的onTouchEvent()方法處理ACTION_MOVE事件;如果ViewGroup2的Target不為空,呼叫ViewGroup2的onInterceptTouchEvent()方法判斷是否攔截,如果ViewGroup2不攔截,ACTION_MOVE事件就會傳遞到View中,呼叫View的dispatchTouchEvent()方法,View的dispatchTouchEvent()方法呼叫onTouchEvent()方法處理ACTION_MOVE事件;如果ViewGroup2攔截ACTION_MOVE,就會給View傳一個ACTION_CANCEL事件。
以下是ViewGroup和View類的關鍵程式碼
ViewGroup的關鍵程式碼:
/**
* {@inheritDoc}
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
/**其他程式碼,暫不關心**/
……
boolean handled =
false;
if (onFilterTouchEventForSecurity(ev)) {
……
// Check for interception.
final boolean intercepted;
/**
* @ 1
*
* 注意接收到MotionEvent.ACTION_DOWN事件要判斷是否需要攔截,如果
* mFirstTouchTarget不為空也要判斷事件是否需要攔截,
* 如果mFirstTouchTarget為空,則其他事件一律攔截
* /
if(actionMasked == MotionEvent.ACTION_DOWN||mFirstTouchTarget!=null)
{
intercepted = onInterceptTouchEvent(ev);
} else {intercepted = true;
}
……
if (!canceled && !intercepted) {
……
final View[] children = mChildren;
for (int
i = childrenCount - 1; i >=
0; i--) {
……
//將Touch事件傳到child
if(dispatchTransformedTouchEvent(ev,false,child,idBitsToAssign)){
……
/*
* @ 2
*
* 如果子控制元件接收了Touch事件,
* 設定mFirsttouchTarget,mFirsttouchTarget是一個單鏈表
*/
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
//如果mFirstTouchTarget,說明子控制元件沒有處理ActionDown,就判斷當前View是否處理if (mFirstTouchTarget
== null) {
//判斷本View是否處理Touch事件
handled = dispatchTransformedTouchEvent(ev, canceled,
null,
TouchTarget.ALL_POINTER_IDS);
}else
{
TouchTarget predecessor =
null;
TouchTarget target =
mFirstTouchTarget;
while
(target != null) {
final
TouchTarget next = target.next;
if(alreadyDispatchedToNewTouchTarget && target==newTouchTarget){
handled =
true;
} else
{
/**
* 注意下面的程式碼,如果當前Touch被攔截了,而且target存在,
* 就給target傳一個MotionEvent.ACTION_CANCEL,
* 同時將target清空,這樣當下一個Touch事件傳到當前View的時候
* 就會交由當前View處理
*/
final boolean
cancelChild=resetCancelNextUpFlag(target.child)||intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
……
}
}
return handled;
}
/**ViewGroup的攔截方法 **/
public boolean
onInterceptTouchEvent(MotionEvent ev) {
return false;//沒開玩笑,ViewGroup預設是不攔截Touch事件的
}
/**傳遞Touch事件的方法**/
private boolean
dispatchTransformedTouchEvent(MotionEvent event,
boolean cancel,View child, int
desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child ==
null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
……
final
MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child ==
null || child.hasIdentityMatrix()) {
if (child ==
null) {
handled = super.dispatchTouchEvent(event);
} else {
……
handled = child.dispatchTouchEvent(event);
}
return handled;
}
} else {
……
}
if
(child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
……
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
View的關鍵程式碼
/**View 分配點選事件 **/
public boolean
dispatchTouchEvent(MotionEvent event) {
……
boolean result =
false;
……
if (onFilterTouchEventForSecurity(event)) {
/**如果設定了點選事件監聽器,而且當前View是ENABLE的,
* 而且監聽器響應Touch事件,返回true
**/
ListenerInfo li = mListenerInfo;
if (li !=
null && li.mOnTouchListener
!= null
&& (mViewFlags
& ENABLED_MASK) ==
ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
/**如果onTouchEvent返回true,就說明分配了Touch事件**/
if (!result && onTouchEvent(event)) {
result = true;
}
}
……
return result;
}
public boolean
onTouchEvent(MotionEvent event) {
……
if ((viewFlags &
ENABLED_MASK) == DISABLED) {
……
/**只要當前View設定了CLICKABLE或LONG_CLICKABLE或CONTEXT_CLICKABLE,就返回true*/
return (((viewFlags &
CLICKABLE) ==
CLICKABLE
|| (viewFlags &
LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) ==
CONTEXT_CLICKABLE);
}
/***如果設定了代理,而且設定的代理可以處理點選事件,就返回true****/
if (mTouchDelegate
!= null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
/**只要當前View設定了CLICKABLE或LONG_CLICKABLE或CONTEXT_CLICKABLE,就返回true*/
if (((viewFlags &
CLICKABLE) == CLICKABLE
||
(viewFlags & LONG_CLICKABLE) ==
LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) ==
CONTEXT_CLICKABLE) {
//touch事件的處理邏輯
……
return true;
}
return false;
}
當Touch事件傳遞到Activity之後會呼叫Activity的dispatchTouchEvent方法,這個方法如下:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
可以看到,這個方法會呼叫getWindow.superDispatchTouchEvent()方法。
getWindow會返回mWindow屬性,通過搜尋發現:
mWindow = new PhoneWindow(this);
可以看到在PhoneWindow中實現了superDispatchTouchEvent()方法,我們需要看下PhoneWindow的原始碼。
以下是PhoneWindow的部分原始碼
public class PhoneWindow extends Window implements MenuBuilder.Callback {
// This is the top—level view of the window, containing the window decor.
private DecorView mDecor;
。。。。。。
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
。。。。。
private final class DecorView extends FrameLayout {
。。。。。。
public boolean superDispatchTouchEvent(KeyEvent event) {
return super.dispatchTouchEvent(event);
}
。。。。。。
}
}
由上可見PhoneWindow的superDispatchtouchEvent()方法中呼叫了mDecor物件的superDispatchtouchEvent()方法,這個方法中會呼叫super.dispatchTouchEvent()方法,因為DecorView繼承自FramLayout,所以這樣就將Touch事件由Activity傳到了View中。
如果有一個點選事件所有的View均不處理,就會交由Activity處理,此時會呼叫Activity的onTouchEvent(),程式碼如下:
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
參考:http://blog.csdn.net/yangzl2008/article/details/7908509
http://www.cnblogs.com/linjzong/p/4191891.html
《Android開發藝術探索》
感謝以上大牛!!!