Android筆記:觸摸事件的分析與總結----TouchEvent處理機制

   其他相關博文:

   Android筆記:觸摸事件的分析與總結----MotionEvent對象

   Android筆記:觸摸事件的分析與總結----TouchEvent處理機制

   Android筆記:觸摸事件的分析與總結----多點觸控

    


    Android中的事件類型分爲按鍵事件和屏幕觸摸事件。TouchEvent是屏幕觸摸事件的基礎事件,要深入瞭解屏幕觸摸事件的處理機制,就必須掌握TouchEvent在整個觸摸事件中的轉移和處理過程。此處將對TouchEvent處理機制的學習做個小小的總結和備記。

    當屏幕中包含一個ViewGroup,而這個ViewGroup又包含一個子view,這個時候android系統如何處理Touch事件呢?到底是 ViewGroup來處理Touch事件,還是子view來處理Touch事件呢?

    這問題涉及到與每個View或者ViewGroup的子類都具有的三個和TouchEvent處理密切相關的方法:

1)dispatchTouchEvent(MotionEvent ev)     這個方法用來分發TouchEvent

2)onInterceptTouchEvent(MotionEvent ev)  這個方法用來攔截TouchEvent

3)onTouchEvent(MotionEvent ev)           這個方法用來處理TouchEvent


    其中view類和Activity中都有dispatchTouchEvent()和onTouchEvent()兩個方法。ViewGroup繼承自View,而且還新添了一個onInterceptTouchEvent()方法。

    這三個方法的返回值都是boolean值,對於返回結果,如果return true,那麼表示該方法消費了此次事件,如果return false,那麼表示該方法並未處理完全,該事件仍然需要以某種方式傳遞下去繼續等待處理。


一、dispatchTouchEvent

    dispatchTouchEvent(MotionEventev) 這個方法用來分發TouchEvent,默認返回false。

    先看下Activity中的註釋和方法:

    /**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     * 
     * @param ev The touch screen event.
     * 
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTouchEvent(MotionEvent ev)
    {
        if (ev.getAction() == MotionEvent.ACTION_DOWN)
        {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev))
        {
            return true;
        }
        return onTouchEvent(ev);
    }


    註釋說明:它會被調用處理觸摸屏事件,可以重寫覆蓋此方法來攔截所有觸摸屏事件在這些事件分發到窗口之前。通常應該處理觸摸屏事件,一定要調用這個實現。當返回值爲true時,表示這個事件已經被消費了。

    源碼簡要說明下,onUserInteraction()是個空方法可忽略其影響。getWindow()返回當前Activity的頂層窗口Window對象。Window類的唯一子類是PhoneWindow,查看PhoneWindow的superDispatchTouchEvent()方法,裏面又直接調用DecorView類的superDispatchTouchEvent()方法。DecorView是PhoneWindow的一個final的內部類並且繼承FrameLayout的,也是Window界面的最頂層的View對象。DecorView類的superDispatchTouchEvent()方法又是調用了父類FrameLayout的dispatchTouchEvent()方法。而FrameLayout中並沒有dispatchTouchEvent()方法,所以最後調用的還是ViewGroup的dispatchTouchEvent()方法。最後再通過ViewGroup的dispatchTouchEvent()方法將TouchEvent分發到其子View上。

     ViewGroup和View中的dispatchTouchEvent()代碼較多,具體的源碼分析可參見:【轉】Android筆記:觸摸事件的分析與總結----Touch事件分發方法dispatchTouchEvent()源碼分析

     其他參考資料:http://blog.csdn.net/xiaanming/article/details/21696315。  


二、onInterceptTouchEvent

    onInterceptTouchEvent()是ViewGroup的一個方法,目的是在系統向該ViewGroup及其各個childView觸發onTouchEvent()之前對相關事件進行一次攔截。

    ViewGroup中onInterceptTouchEvent()方法及註釋:

    /**
     * Implement this method to intercept all touch screen motion events. This
     * allows you to watch events as they are dispatched to your children, and
     * take ownership of the current gesture at any point.
     * 
     * <p>
     * Using this function takes some care, as it has a fairly complicated
     * interaction with {@link View#onTouchEvent(MotionEvent)
     * View.onTouchEvent(MotionEvent)}, and using it requires implementing that
     * method as well as this one in the correct way. Events will be received in
     * the following order:
     * 
     * <ol>
     * <li>You will receive the down event here.
     * <li>The down event will be handled either by a child of this view group,
     * or given to your own onTouchEvent() method to handle; this means you
     * should implement onTouchEvent() to return true, so you will continue to
     * see the rest of the gesture (instead of looking for a parent view to
     * handle it). Also, by returning true from onTouchEvent(), you will not
     * receive any following events in onInterceptTouchEvent() and all touch
     * processing must happen in onTouchEvent() like normal.
     * <li>For as long as you return false from this function, each following
     * event (up to and including the final up) will be delivered first here and
     * then to the target's onTouchEvent().
     * <li>If you return true from here, you will not receive any following
     * events: the target view will receive the same event but with the action
     * {@link MotionEvent#ACTION_CANCEL}, and all further events will be
     * delivered to your onTouchEvent() method and no longer appear here.
     * </ol>
     * 
     * @param ev
     *            The motion event being dispatched down the hierarchy.
     * @return Return true to steal motion events from the children and have
     *         them dispatched to this ViewGroup through onTouchEvent(). The
     *         current target will receive an ACTION_CANCEL event, and no
     *         further messages will be delivered here.
     */
    public boolean onInterceptTouchEvent(MotionEvent ev)
    {
        return false;
    }


    onInterceptTouchEvent()默認返回了false,註釋的大意爲重寫該方法可以實現對觸屏事件的攔截,使用該方法需要特別注意的是,該方法與View類的onTouchEvent(MotionEvent)或者View.onTouchEvent(MotionEvent)方法具有複雜的關聯機制。結合onTouchEvent(),總結下onInterceptTouchEvent()大致的規則爲:


1. down事件首先會傳遞到onInterceptTouchEvent()方法。

2. 如果該ViewGroup的onInterceptTouchEvent()在接收到down事件處理完成之後return false,那麼後續的move, up等事件將繼續會先傳遞給該ViewGroup,之後才和down事件一樣傳遞給最終的目標View的onTouchEvent()處理。

3. 如果該ViewGroup的onInterceptTouchEvent()在接收到down事件處理完成之後return true,那麼後續的move, up等事件將不再傳遞給onInterceptTouchEvent(),而是和down事件一樣傳遞給該ViewGroup的onTouchEvent()處理,注意,目標view將接收不到任何事件。

4. 如果最終需要處理事件的view的onTouchEvent()返回了false,那麼該事件將被傳遞至其上一層次的view的onTouchEvent()處理。

5. 如果最終需要處理事件的view 的onTouchEvent()返回了true,那麼後續事件將可以繼續傳遞給該view的onTouchEvent()處理。

    


三、onTouchEvent

    onTouchEvent()的處理機制詳見此文:

    Android筆記:觸摸事件的分析與總結----MotionEvent對象



四、TouchEvent處理範例

    此處創建一個包含自定義LinearLayout(ViewGroup類)和自定義TextView(View類)的Activity來分析觸摸屏幕時TouchEvent的處理機制。

    效果圖如下:

wKiom1Qj1aTiY14EAACGBwMjfvc945.jpg


    activity_main.xml代碼如下:

<?xml version="1.0" encoding="utf-8"?> 
<com.example.d_touchevent.MyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:orientation="vertical" 
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent" 
    android:gravity="center" > 
       <com.example.d_touchevent.MyTextView 
            android:layout_width="100dp" 
            android:layout_height="100dp" 
            android:id="@+id/tv" 
            android:text="測試" 
            android:textSize="40sp" 
            android:textStyle="bold" 
            android:background="#F0F00F" 
            android:textColor="#0000FF"/> 
</com.example.d_touchevent.MyLinearLayout>


    MainActivity.java代碼如下:

package com.example.d_touchevent;

import android.os.Bundle;
import android.app.Activity;
import android.util.Log;
import android.view.MotionEvent;

/**
 * 參考資料:http://glblong.blog.51cto.com/3058613/1559320
 * @author zeng
 *
 */
public class MainActivity extends Activity
{
    private String TAG = "Activity ---  ";

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        boolean b = super.onTouchEvent(event);
        
        
        int action = event.getAction();
        
        switch (action)
        {
        
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG , "ACTION_DOWN   --- " + TAG + "onTouchEvent          處理 --- " + b);
                
                break;
            
            case MotionEvent.ACTION_MOVE:
                
                Log.e(TAG, "ACTION_MOVE    --- " + TAG + "onTouchEvent          處理 --- " + b);
                
                break;
            
            case MotionEvent.ACTION_UP:
                
                Log.e(TAG, "ACTION_UP     --- " + TAG + "onTouchEvent          處理 --- " + b);
                
                break;
            
            case MotionEvent.ACTION_CANCEL:
                
                Log.e(TAG, "ACTION_CANCEL --- " + TAG + "onTouchEvent          處理 --- " + b);
                
                break;
        
        }
        
        
        return b;
    }
    
    
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev)
    {
        int action = ev.getAction();
        
        switch (action)
        {
        
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "ACTION_DOWN   --- " + TAG + "dispatchTouchEvent    分發 --- ");
                
                break;
            
            case MotionEvent.ACTION_MOVE:
                
                Log.e(TAG, "ACTION_MOVE   --- " + TAG + "dispatchTouchEvent    分發 --- ");
                
                break;
            
            case MotionEvent.ACTION_UP:
                
                Log.e(TAG, "ACTION_UP     --- " + TAG + "dispatchTouchEvent    分發 --- ");
                
                break;
            
            case MotionEvent.ACTION_CANCEL:
                
                Log.e(TAG, "ACTION_CANCEL --- " + TAG + "dispatchTouchEvent    分發 --- ");
                
                break;
        
        }
        return super.dispatchTouchEvent(ev);
    }
    
}


    MyLinearLayout.java代碼如下:

package com.example.d_touchevent;

import android.widget.LinearLayout;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;

/**
 * 參考資料:http://glblong.blog.51cto.com/3058613/1559320
 * @author zeng
 *
 */
public class MyLinearLayout extends LinearLayout
{
    private final String TAG = "L佈局    ---  ";
    
    public MyLinearLayout(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        
        Log.e(TAG, TAG);
    }
    
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev)
    {
        int action = ev.getAction();
        
        switch (action)
        {
        
            case MotionEvent.ACTION_DOWN:
                Log.e("", " ");
                Log.e("", " ");
                Log.e(TAG, "ACTION_DOWN   --- " + TAG + "dispatchTouchEvent    分發 --- ");
                
                break;
            
            case MotionEvent.ACTION_MOVE:
                
                Log.e(TAG, "ACTION_MOVE   --- " + TAG + "dispatchTouchEvent    分發 --- ");
                
                break;
            
            case MotionEvent.ACTION_UP:
                
                Log.e(TAG, "ACTION_UP     --- " + TAG + "dispatchTouchEvent    分發 --- ");
                
                break;
            
            case MotionEvent.ACTION_CANCEL:
                
                Log.e(TAG, "ACTION_CANCEL --- " + TAG + "dispatchTouchEvent    分發 --- ");
                
                break;
        
        }
        return super.dispatchTouchEvent(ev);
    }
    
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev)
    {
        boolean b = false;
        
        int action = ev.getAction();
        switch (action)
        {
        
            case MotionEvent.ACTION_DOWN:
                
                Log.e(TAG, "ACTION_DOWN   --- " + TAG + "onInterceptTouchEvent 攔截 --- " + b);
                
                break;
            
            case MotionEvent.ACTION_MOVE:
                
                Log.e(TAG, "ACTION_MOVE   --- " + TAG + "onInterceptTouchEvent 攔截 --- " + b);
                
                break;
            
            case MotionEvent.ACTION_UP:
                
                Log.e(TAG, "ACTION_UP     --- " + TAG + "onInterceptTouchEvent 攔截 --- " + b);
                
                break;
            
            case MotionEvent.ACTION_CANCEL:
                
                Log.e(TAG, "ACTION_CANCEL --- " + TAG + "onInterceptTouchEvent 攔截 --- " + b);
                
                break;
        
        }
        
        return b;
        
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent ev)
    {
        boolean b = true;
        
        int action = ev.getAction();
        switch (action)
        {
        
            case MotionEvent.ACTION_DOWN:
                
                Log.e(TAG, "ACTION_DOWN   --- " + TAG + "onTouchEvent          處理 --- " + b);
                
                break;
            
            case MotionEvent.ACTION_MOVE:
                
                Log.e(TAG, "ACTION_MOVE   --- " + TAG + "onTouchEvent          處理 --- " + b);
                
                break;
            
            case MotionEvent.ACTION_UP:
                
                Log.e(TAG, "ACTION_UP     --- " + TAG + "onTouchEvent          處理 --- " + b);
                
                break;
            
            case MotionEvent.ACTION_CANCEL:
                
                Log.e(TAG, "ACTION_CANCEL --- " + TAG + "onTouchEvent          處理 --- " + b);
                
                break;
        
        }
        
        return b;
    }
}


    MyTextView.java代碼如下:

package com.example.d_touchevent;

import android.widget.TextView;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;

/**
 * 參考資料:http://glblong.blog.51cto.com/3058613/1559320
 * @author zeng
 *
 */
public class MyTextView extends TextView
{
    private final String TAG = "TextView ---  ";
    
    public MyTextView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }
    
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev)
    {
        int action = ev.getAction();
        
        switch (action)
        {
        
            case MotionEvent.ACTION_DOWN:
                
                Log.e(TAG, "ACTION_DOWN   --- " + TAG + "dispatchTouchEvent    分發 --- ");
                
                break;
            
            case MotionEvent.ACTION_MOVE:
                
                Log.e(TAG, "ACTION_MOVE   --- " + TAG + "dispatchTouchEvent    分發 --- ");
                
                break;
            
            case MotionEvent.ACTION_UP:
                
                Log.e(TAG, "ACTION_UP     --- " + TAG + "dispatchTouchEvent    分發 --- ");
                
                break;
            
            case MotionEvent.ACTION_CANCEL:
                
                Log.e(TAG, "ACTION_CANCEL --- " + TAG + "dispatchTouchEvent    分發 --- ");
                
                break;
        
        }
        return super.dispatchTouchEvent(ev);
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent ev)
    {
        boolean b = false;
        
        int action = ev.getAction();
        switch (action)
        {
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "ACTION_DOWN   --- " + TAG + "onTouchEvent          處理 --- " + b);
                
                break;
            
            case MotionEvent.ACTION_MOVE:
                
                Log.e(TAG, "ACTION_MOVE   --- " + TAG + "onTouchEvent          處理 --- " + b);
                
                break;
            
            case MotionEvent.ACTION_UP:
                
                Log.e(TAG, "ACTION_UP     --- " + TAG + "onTouchEvent          處理 --- " + b);
                
                break;
            
            case MotionEvent.ACTION_CANCEL:
                
                Log.e(TAG, "ACTION_CANCEL --- " + TAG + "onTouchEvent          處理 --- " + b);
                
                break;
        
        }
        
        return b;
        
    }
    

}


五、範例運行分析


    注:a.以下Logcat中,若沒有特別說明,dispatchTouchEvent()都按默認方法返回false。    

       b.爲方便,L佈局簡寫爲L,TextView簡寫爲T,Activity簡寫爲A,下同。


    1)點擊範例中的【測試】按鈕,運行日誌如下:

wKioL1Qj1u6AUjd2AALSLhLP6vA025.jpg

    結論:

    當ACTION_DOWN事件產生時,首先觸發了Activity的dispatchTouchEvent()方法;接着傳遞到ViewGroup上,觸發L佈局的dispatchTouchEvent()方法繼續分發TouchEvent;L佈局的onInterceptTouchEvent()方法爲false,即不會攔截TouchEvent的傳遞,因而繼續傳遞到ViewGroup裏的View對象TextView中,此時仍然先是調用了TextView的dispatchTouchEvent()方法來處理TouchEvent的分發。從上到下依次傳遞:Activity -> L佈局 -> TextView。

    同理,當ACTION_UP事件產生時,首先也是Activity的dispatchTouchEvent()方法,接着再到L佈局的dispatchTouchEvent()方法。

     

    2)L.dispatchTouchEvent() = true ,運行日誌如下:

wKioL1Qj1y3gNZAUAAGG5oMOlwY678.jpg

   結論:

   此時,每個觸摸事件產生時,都只執行到L佈局的dispatchTouchEvent()方法,而不會繼續再傳遞並觸發其他方法。


    

    3)A.dispatchTouchEvent() = false  &&  L.dispatchTouchEvent() = false  &&  T.dispatchTouchEvent() = true,運行日誌如下:

wKiom1Qj1yqAJ1fMAAKa1HHWNC4703.jpg

    結論:

    由上可見,當TouchEvent由Activity傳遞到TextView時,執行到dispatchTouchEvent()後便結束了。也就是到TextView時,Android系統認爲ACTION_DOWN和ACITON_UP都已經被消費了,而沒有繼續分發下去。


    4)L.onInterceptTouchEvent = true   &&   L.onTouchEvent = true ,運行日誌如下:

wKiom1Qj10Tyr1MpAAObyesw05M744.jpg

    結論:

    這種情況下,L佈局處理了所有的TouchEvent。


    5)L.onInterceptTouchEvent = true   &&   L.onTouchEvent = false , 運行日誌如下:

wKioL1Qj1_PCsV5oAAO3Omd7D88812.jpg

   結論:

    L佈局只處理了ACTION_DOWN事件,而L佈局最外層的ctivity處理了TouchEvent。


    6)L.onInterceptTouchEvent=false  &&  L.onTouchEvent=true  &&  T.onTouchEvent=true , 運行日誌如下:

wKiom1Qj2DnTyBMeAASIoe2a38s538.jpg

    結論:

    TouchEvent完全由TextView處理。


    7)L.onInterceptTouchEvent=false  &&  L.onTouchEvent=true  &&  T.onTouchEvent=false , 運行日誌如下:

wKioL1Qj2IewoeuaAATViItVS0Y803.jpg

     結論:

     TextView只處理了ACTION_DOWN事件,LinearLayout處理了所有的TouchEvent。



六、分析總結

1.三個主要相關的方法的默認值

所有dispatchTouchEvent方法的默認值都是false。

ViewGroup裏的onInterceptTouchEvent默認值是false這樣才能把事件傳給View裏的onTouchEvent.

Activity和ViewGroup裏的onTouchEvent默認值都是false。

View裏的onTouchEvent返回默認值是true.這樣才能執行多次touch事件。



2.TouchEvent的處理流程

當TouchEvent發生時,首先Activity將TouchEvent傳遞給最頂層的View, TouchEvent最先到達最頂層 view 的 dispatchTouchEvent ,然後由  dispatchTouchEvent 方法進行分發,如果dispatchTouchEvent返回true ,則表示該觸摸事件已經被消費了,如果dispatchTouchEvent返回 false ,則交給這個 view 的 interceptTouchEvent 方法來決定是否要攔截這個事件,如果 interceptTouchEvent 返回 true ,也就是攔截掉了,則交給它的 onTouchEvent 來處理,如果 interceptTouchEvent 返回 false ,那麼就傳遞給子 view ,由子 view 的 dispatchTouchEvent 再來開始這個事件的分發。如果事件傳遞到某一層的子 view 的 onTouchEvent 上了,這個方法返回了 false ,那麼這個事件會從這個 view 往上傳遞,都是 onTouchEvent 來接收。而如果傳遞到最上面的 onTouchEvent 也返回 false 的話,這個事件就會“   消失”,而且接收不到下一次事件。



3.TouchEvent的處理流程圖

自己製作了個TouchEvent處理的流程圖,方便理清TouchEvent事件在各種UI對象以及對應方法中的處理機制。將流程圖與上面的運行日誌結合分析,發現對TouchEvent處理的機制清晰了很多。若有錯誤之處,歡迎指教。

wKioL1Qo01bh7FokAAKn6n5a4u0915.jpg








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章