Android 編程下 Touch 事件的分發和消費機制

Android 中與 Touch 事件相關的方法包括:dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)、onTouchEvent(MotionEvent ev);可以響應這些方法的控件包括:ViewGroup 及其子類、Activity。方法與控件的對應關係以下表所示:android

Touch 事件相關方法   方法功能    ViewGroup        Activity    
  public boolean dispatchTouchEvent(MotionEvent ev) 事件分發  Yes  Yes
  public boolean onInterceptTouchEvent(MotionEvent ev)  事件攔截  Yes  No
  public boolean onTouchEvent(MotionEvent ev) 事件響應  Yes  Yes

從這張表中咱們能夠看到 ViewGroup 及其子類對與 Touch 事件相關的三個方法均能響應,而 Activity 對 onInterceptTouchEvent(MotionEvent ev) 也就是事件攔截不進行響應。另外須要注意的是 View 對 dispatchTouchEvent(MotionEvent ev) 和 onInterceptTouchEvent(MotionEvent ev) 的響應的前提是能夠向該 View 中添加子 View,若是當前的 View 已是一個最小的單元 View(好比 TextView),那麼就沒法向這個最小 View 中添加子 View,也就沒法向子 View 進行事件的分發和攔截,因此它沒有 dispatchTouchEvent(MotionEvent ev) 和 onInterceptTouchEvent(MotionEvent ev),只有 onTouchEvent(MotionEvent ev)。git

1、Touch 事件分析github

事件分發:public boolean dispatchTouchEvent(MotionEvent ev)app

Touch 事件發生時 Activity 的 dispatchTouchEvent(MotionEvent ev) 方法會以隧道方式(從根元素依次往下傳遞直到最內層子元素或在中間某一元素中因爲某一條件中止傳遞)將事件傳遞給最外層 View 的 dispatchTouchEvent(MotionEvent ev) 方法,並由該 View 的 dispatchTouchEvent(MotionEvent ev) 方法對事件進行分發。dispatchTouchEvent 的事件分發邏輯以下:工具

  • 若是 return true,事件會分發給當前 View 並由 dispatchTouchEvent 方法進行消費,同時事件會中止向下傳遞;
  • 若是 return false,事件分發分爲兩種狀況:
  1. 若是當前 View 獲取的事件直接來自 Activity,則會將事件返回給 Activity 的 onTouchEvent 進行消費;
  2. 若是當前 View 獲取的事件來自外層父控件,則會將事件返回給父 View 的  onTouchEvent 進行消費。
  • 若是返回系統默認的 super.dispatchTouchEvent(ev),事件會自動的分發給當前 View 的 onInterceptTouchEvent 方法。

事件攔截:public boolean onInterceptTouchEvent(MotionEvent ev) 佈局

在外層 View 的 dispatchTouchEvent(MotionEvent ev) 方法返回系統默認的 super.dispatchTouchEvent(ev) 狀況下,事件會自動的分發給當前 View 的 onInterceptTouchEvent 方法。onInterceptTouchEvent 的事件攔截邏輯以下:測試

  • 若是 onInterceptTouchEvent 返回 true,則表示將事件進行攔截,並將攔截到的事件交由當前 View 的 onTouchEvent 進行處理;
  • 若是 onInterceptTouchEvent 返回 false,則表示將事件放行,當前 View 上的事件會被傳遞到子 View 上,再由子 View 的 dispatchTouchEvent 來開始這個事件的分發;
  • 若是 onInterceptTouchEvent 返回 super.onInterceptTouchEvent(ev),事件默認會被攔截,並將攔截到的事件交由當前 View 的 onTouchEvent 進行處理。

事件響應:public boolean onTouchEvent(MotionEvent ev)spa

在 dispatchTouchEvent 返回 super.dispatchTouchEvent(ev) 而且 onInterceptTouchEvent 返回 true 或返回 super.onInterceptTouchEvent(ev) 的狀況下 onTouchEvent 會被調用。onTouchEvent 的事件響應邏輯以下:xml

  • 若是事件傳遞到當前 View 的 onTouchEvent 方法,而該方法返回了 false,那麼這個事件會從當前 View 向上傳遞,而且都是由上層 View 的 onTouchEvent 來接收,若是傳遞到上面的 onTouchEvent 也返回 false,這個事件就會「消失」,並且接收不到下一次事件。
  • 若是返回了 true 則會接收並消費該事件。
  • 若是返回 super.onTouchEvent(ev) 默認處理事件的邏輯和返回 false 時相同。

到這裏,與 Touch 事件相關的三個方法就分析完畢了。下面的內容會經過各類不一樣的的測試案例來驗證上文中三個方法對事件的處理邏輯。繼承

2、Touch 案例介紹

一樣在開始進行案例分析以前,我須要說明測試案例的結構,由於全部的測試都是針對這一個案例來進行的,測試中只是經過修改每一個控件中與 Touch 事件相關的三個方法的返回值來體現不一樣的狀況。先來看張圖:

Touch 事件案例佈局 UI

上面的圖爲測試案例的佈局文件 UI 顯示效果,佈局文件代碼以下:

複製代碼

<?xml version="1.0" encoding="utf-8"?>
<cn.sunzn.tevent.TouchEventFather xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#468AD7"
    android:gravity="center"
    android:orientation="vertical" >

    <cn.sunzn.tevent.TouchEventChilds
        android:id="@+id/childs"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_gravity="center"
        android:background="#E1110D"
        android:text="@string/hello" />

</cn.sunzn.tevent.TouchEventFather>

複製代碼

藍色背景爲一個自定義控件 TouchEventFather,該控件爲外層 View,繼承自 LinearLayout,實現代碼以下:

複製代碼

package cn.sunzn.tevent;

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

public class TouchEventFather extends LinearLayout {

    public TouchEventFather(Context context) {
        super(context);
    }

    public TouchEventFather(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e("sunzn", "TouchEventFather | dispatchTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
        return super.dispatchTouchEvent(ev);
    }

    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i("sunzn", "TouchEventFather | onInterceptTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
        return super.onInterceptTouchEvent(ev);
    }

    public boolean onTouchEvent(MotionEvent ev) {
        Log.d("sunzn", "TouchEventFather | onTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
        return super.onTouchEvent(ev);
    }

}

複製代碼

紅色背景爲一個自定義控件 TouchEventChilds,該控件爲內層 View,爲 TouchEventFather 的子 View,一樣繼承自 LinearLayout,實現代碼以下:

複製代碼

package cn.sunzn.tevent;

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

public class TouchEventChilds extends LinearLayout {

    public TouchEventChilds(Context context) {
        super(context);
    }

    public TouchEventChilds(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e("sunzn", "TouchEventChilds | dispatchTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
        return super.dispatchTouchEvent(ev);
    }

    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i("sunzn", "TouchEventChilds | onInterceptTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
        return super.onInterceptTouchEvent(ev);
    }

    public boolean onTouchEvent(MotionEvent ev) {
        Log.d("sunzn", "TouchEventChilds | onTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
        return super.onTouchEvent(ev);
    }

}

複製代碼

接着實現 Activity 的代碼,由於控件全部的事件都是經過 Activity 的 dispatchTouchEvent 進行分發的;除此以外還須要重寫 Activity 的 onTouchEvent 方法,這是由於若是一個控件直接從 Activity 獲取到事件,這個事件會首先被傳遞到控件的 dispatchTouchEvent 方法,若是這個方法 return false,事件會以冒泡方式返回給 Activity 的 onTouchEvent 進行消費。實現代碼以下:

複製代碼

package cn.sunzn.tevent;

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

public class TouchEventActivity extends Activity {

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.w("sunzn", "TouchEventActivity | dispatchTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
        return super.dispatchTouchEvent(ev);
    }

    public boolean onTouchEvent(MotionEvent event) {
        Log.w("sunzn", "TouchEventActivity | onTouchEvent --> " + TouchEventUtil.getTouchAction(event.getAction()));
        return super.onTouchEvent(event);
    }

}

複製代碼

最後再附上 TouchEventUtil 的代碼,TouchEventUtil 中並無作多少事情,只是將以上 2 個自定義控件中各個方法的 MotionEvent 集中到一個工具類中並將其對應的動做以 String 形式返回,這樣處理更便於實時觀察控件的事件。代碼以下:

package cn.sunzn.tevent;

import android.view.MotionEvent;

public class TouchEventUtil {
    
    public static String getTouchAction(int actionId) {
        String actionName = "Unknow:id=" + actionId;
        switch (actionId) {
        case MotionEvent.ACTION_DOWN:
            actionName = "ACTION_DOWN";
            break;
        case MotionEvent.ACTION_MOVE:
            actionName = "ACTION_MOVE";
            break;
        case MotionEvent.ACTION_UP:
            actionName = "ACTION_UP";
            break;
        case MotionEvent.ACTION_CANCEL:
            actionName = "ACTION_CANCEL";
            break;
        case MotionEvent.ACTION_OUTSIDE:
            actionName = "ACTION_OUTSIDE";
            break;
        }
        return actionName;
    }
    
}

 

實例代碼:https://github.com/wangzhiyuan888/TouchEvent

相關文章
相關標籤/搜索