天天看點

Android 觸摸事件分發和處理機制解析(一)Activity篇

在我們自定義view或者嵌套view時,經常需要處理滑動事件,點選事件等各種互動事件。在處理過程中,我們可能會遇到事件不響應,滑動和點選事件沖突等問題,這時候,如果我們了解Android觸摸事件的分發和處理,處理起來就會得心應手。

剛開始接觸Android觸摸事件分發和處理機制的時候,往往會一頭霧水,因為處理觸摸事件的地方太多了。

比如,我們可以對某個activity裡的view設定onTouchListenter(),也可以在activity裡重載onTouchEvent()方法,那麼到底是誰會處理觸摸事件呢?

又比如,activity裡可以重載dispatchTouchEvent()方法,而自定義layout裡也可以重載dispatchTouchEvent()方法,那麼在activity裡用到自定義layout的時候,應該用哪個方法處理觸摸事件的分發呢?

一、開始之前,先記住下面觸摸事件涉及的三個重要方法:

public boolean dispatchTouchEvent(MotionEvent ev)   //觸摸事件分發
public boolean onInterceptTouchEvent(MotionEvent event)     //觸摸事件攔截
public boolean onTouchEvent(MotionEvent event)      //觸摸事件處理
           

ViewGroup包含所有這三個方法。如果我們要自定義ViewGroup(比如常見的FrameLayout,ListView等都繼承自ViewGroup),則可以有選擇地重載這三個方法。

Activity和View不包含第二個onInterceptTouchEvent(MotionEvent event)即事件攔截方法,包含其它兩個。

是以在我們建立的activity和自定義View(比如自定義Button等)中,可以有選擇地重載dispatchTouchEvent(MotionEvent event) 和 onTouchEvent(MotionEvent event)。

看到這可能大家會有疑問,不是還有我們經常用到的setOnTouchListener()以及OnTouchListener接口裡的onTouch()方法嗎?怎麼沒提?

——這些方法,放到《Android 觸摸事件分發和處理機制解析(三)——View篇》裡會講。

那麼既然Activity,ViewGroup,View裡都有事件處理相關方法,那麼發生觸摸事件時,事件處理的先後順序是怎樣的呢?

二、記住第二條:觸摸事件是從Activity的dispatchTouchEvent()開始處理的。

對新手來說,記住就好,本文不做深究。以此為前提,我們來看Activity類的dispatchTouchEvent()的源碼。

/**
     * 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);
    }
           

————————————————————分割線———————————————————————-

三、在分析源碼之前,先記住第三條,觸摸事件的組成:

一般來說,一個完整的觸摸事件包括按下,滑動,擡起三步。按下,滑動,擡起對應的MotionEvent的action分别為MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE 和 MotionEvent.ACTION_UP。

當我們的手指對螢幕進行操作時,系統會把我們的操作封裝成MotionEvent對象,傳入觸摸事件的處理方法中。

是以,當我們點一下螢幕,Activity的dispatchTouchEvent()就會收到若幹個MotionEvent事件。具體順序是,先收到按下事件,處理完成後,再收到滑動事件(0個或多個)依次處理完成,最後傳入一個擡起事件。

————————————————————分割線———————————————————————-

好,知道了這些,再一步一步看上面Activity的dispatchTouchEvent()的源碼:

if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
           

如果傳入的是按下的事件,則調用onUserInteraction()。這個可以不用管,onUserInteraction()在Activity類裡是個空方法,是讓我們重載以處理各種裝置互動的。

往下看:

if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
           

這兩步是重點,在這裡我們看到了一個熟悉的身影,即Activity的onTouchEvent()方法。是以看到這裡,我們剛開始說要記住的,Activity的關于觸摸事件的兩個方法都現身了,而且onTouchEvent()被調用的地方就這一處。

可以得出:假如getWindow().superDispatchTouchEvent(ev)傳回true的話,activity的onTouchEvent()就不會執行了,否則,dispatchTouchEvent()傳回的就是onTouchEvent()的傳回值。

那麼,getWindow().superDispatchTouchEvent(ev)是怎麼處理的呢?

點源碼,getWindow()方法傳回一個Window對象,而Window是個抽象類,其唯一實作類是PhoneWindow類。看下PhoneWindow的superDispatchTouchEvent()方法:

@Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
           

它調用了DecorView的superDispatchTouchEvent()方法進行處理。繼續看DecorView的superDispatchTouchEvent()方法:

public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }
           

又扔給父類處理了,因為DecorView繼承自FrameLayout,而FrameLayout裡沒有重寫此方法,最後還是扔給了FrameLayout的父類ViewGroup,是以最終又走到了ViewGroup的dispatchTouchEvent()方法。這是之前提到的ViewGroup的三個重要方法之一。

看到這兒,我們先不管ViewGroup的dispatchTouchEvent()了。來梳理一下:

Activity的onTouchEvent()方法會不會執行,取決于getWindow().superDispatchTouchEvent()的傳回值。

而後者最後又調用了ViewGroup的dispatchTouchEvent()作為傳回值。是以,如果ViewGroup(這個ViewGroup就是DecorView,是頁面的根視圖)的dispatchTouchEvent()傳回false,則Activity的onTouchEvent()就會執行,否則就得不到執行。

看下Activity的onTouchEvent()源碼:

/**
     * Called when a touch screen event was not handled by any of the views
     * under it.  This is most useful to process touch events that happen
     * outside of your window bounds, where there is no view to receive it.
     *
     * @param event The touch screen event being processed.
     *
     * @return Return true if you have consumed the event, false if you haven't.
     * The default implementation always returns false.
     */
    public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }

        return false;
    }
           

很簡單,傳回true表示觸摸事件被消費了,不用别人再處理;傳回false表示沒有被消費。預設情況下傳回false。

本篇學習的東西:

1. 觸摸事件是從Activity的 dispatchTouchEvent() 開始處理的。

2. Activity的dispatchTouchEvent() 中調用了它的 onTouchEvent(),這也是它的onTouchEvent()唯一的調用 。

3. Activity的dispatchTouchEvent()又間接調用了該Activity的根ViewGroup的dispatchTouchEvent()。onTouchEvent()會不會被執行,取決于後者的傳回值。如果它傳回true,則Activity的onTouchEvent() 就不會執行了。

————————————————————分割線———————————————————————-

擴充問題:如果我們建立了一個activity,像下面這樣重寫它的兩個方法,運作列印會怎樣呢?

@Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(TAG, "onTouchEvent() called with: event = [" + event + "]");
        return super.onTouchEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.d(TAG, "dispatchTouchEvent() called with: ev = [" + ev + "]");
        return true;    //改為return false列印結果是一樣的
    }
           

運作之後,觸摸螢幕,會不斷打出dispatchTouchEvent的Log,但是不會打onTouchEvent的Log,因為onTouchEvent得不到執行了。這樣就攔截了這個頁面的所有觸摸事件(包括點選事件)。

上面我們從Activity中追蹤到了ViewGroup的dispatchTouchEvent()方法,那下一步,我們就來分析ViewGroup的觸摸事件分發和處理:

Android 觸摸事件分發和處理機制解析(二)ViewGroup篇