1. 程式人生 > >一張圖看懂Touch事件的傳遞

一張圖看懂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;
}

3、Touch事件是怎樣由Activity傳到View或ViewGroup的?

當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開發藝術探索》

感謝以上大牛!!!