Android中onInterceptTouchEvent與onTouchEvent

onInterceptTouchEvent:

onInterceptTouchEvent 是在ViewGroup裏面定義的。Android中的layout佈局類一般都是繼承此類的。onInterceptTouchEvent是用於攔截手 勢事件的,每個手勢事件都會先調用onInterceptTouchEvent。

onTouchEvent:

onTouchEvent同樣也是在view中定義的一個方法。處理傳遞到view 的手勢事件。手勢事件類型包括ACTION_DOWN,ACTION_MOVE,ACTION_UP,ACTION_CANCEL等事件。

其 中Layout裏的onInterceptTouchEvent默認返回值是false,這樣touch事件會傳遞到View控件,Layout裏的 onTouch默認返回值是false, View裏的onTouch默認返回值是true,當我們手指點擊屏幕時候,先調用ACTION_DOWN事件,當onTouch裏返回值是true的時 候,onTouch回繼續調用ACTION_UP事件,如果onTouch裏返回值是false,那麼onTouch只會調用ACTION_DOWN而不 調用ACTION_UP.

爲了讓當家更容易理解我寫了一個簡單的Demo.自定義了Layout與View,Android工程目錄如下:

新建一個MyLayout.java代碼如下:
package com.tutor.touch;

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

public class MyLayout extends FrameLayout {

    
    public MyLayout(Context context){
        super(context);
    }
    
    public MyLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e(TouchDemoActivity.TAG, "MyLayout onInterceptTouchEvent.");
        Log.e(TouchDemoActivity.TAG,"MyLayout onInterceptTouchEvent default return " 
        + super.onInterceptTouchEvent(ev));
        return super.onInterceptTouchEvent(ev);
    }
    
    
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e(TouchDemoActivity.TAG, "MyLayout onTouchEvent.");
        Log.e(TouchDemoActivity.TAG,"MyLayout onTouchEvent default return " 
        + super.onTouchEvent(event));
        return super.onTouchEvent(event);
    }
}
然後新建一個MyView.java代碼如下:
package com.tutor.touch;

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

public class MyView extends Button {

    public MyView(Context context){
        super(context);
    }
    
    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e(TouchDemoActivity.TAG, "MyView onTouchEvent.");
        Log.e(TouchDemoActivity.TAG,"MyView onTouchEvent default return " 
        + super.onTouchEvent(event));
        return super.onTouchEvent(event);
    }

}
其中TouchDemoActivity代碼如下:
package com.tutor.touch;

import android.app.Activity;
import android.os.Bundle;

public class TouchDemoActivity extends Activity {
    public static final String TAG = "TouchDemoActivity";
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}
上面所有的佈局文件main.xml代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<com.tutor.touch.MyLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width
="fill_parent"
    android:layout_height
="fill_parent"
     
>

    <com.tutor.touch.MyView
        
android:layout_width="fill_parent"
        android:layout_height
="wrap_content"
        android:text
="@string/hello" />

</com.tutor.touch.MyLayout>
運行上述Android工程效果如下:
點擊紅色區域,觸發了MyView裏的onTouch事件查看logcat,如下圖:

點擊綠色區域,則觸發了MyLayout裏的onTouch事件,查看logcat,如下圖:

上 面倆個截圖都是用系統默認值,可以得出結論:onInterceptTouchEvent默認返回值是false,MyLayout裏 onTouchEvent默認返回值是false,所以只消費了ACTION_DOWN事件,MyView裏onTouch默認返回值是true,調用了 倆次:ACTION_DOW,ACTION_UP。

下面我們把MyLayout.java裏的onInterceptTouchEvent的return值修改爲true,代碼如下:

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e(TouchDemoActivity.TAG, "MyLayout onInterceptTouchEvent.");
        Log.e(TouchDemoActivity.TAG,"MyLayout onInterceptTouchEvent default return " 
        + super.onInterceptTouchEvent(ev));
        return true;
    }

 

運行工程,繼續點擊紅色區域,查看logcat,發現MyView的onTouch事件沒有被調用,也就是被攔截瞭如下圖所示:

讓我們繼續實驗,讓onInterceptTouchEvent的返回值繼續爲false,將MyView裏的onTouchEvent的返回值修改爲false,即MyView裏的onTouchEvent修改如下:
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e(TouchDemoActivity.TAG, "MyView onTouchEvent.");
        Log.e(TouchDemoActivity.TAG,"MyView onTouchEvent default return " 
        + super.onTouchEvent(event));
        return false;
    }
運行工程,繼續點擊紅色區域,查看logcat,如下圖:

根據上圖,我們可以看出MyView裏的OnTouchEvent只消費了一次點擊事件也就是ACTION_DOWN,還沒有執行ACTION_UP,然後跑到MyLayout裏又去執行了OnTouchEvent事件。

所以根據上面的內容總結如下:

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

ViewGroup裏的onTouchEvent默認值是false。

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





Android中跟Touch事件有關的事件有三個:
    public boolean dispatchTouchEvent(MotionEvent ev):傳遞Touch事件至target view(可以是自己)。
    public boolean onInterceptTouchEvent(MotionEvent ev):在ViewGroup中定義,用於攔截Touch事件的傳遞。
    public boolean onTouchEvent(MotionEvent event): Touch事件處理函數。

 

  先說下事件傳遞的兩種方式:
    隧道方式:從根元素依次往下傳遞直到最內層子元素或在中間某一元素中由於某一條件停止傳遞。 
    冒泡方式:從最內層子元素依次往外傳遞直到根元素或在中間某一元素中由於某一條件停止傳遞。

 

  Touch事件通過dispatchTouchEvent以隧道方式從上往下傳遞。如果在一個View中執行onTouchEvent時返回 true的話,接下來的事件(ACTION_DOWN後的ACTION_UP,及可能在中間包含的若干個ACTION_MOVE,從 ACTION_DOWN至ACTION_UP爲一個連續事件,這是自己想的,不知道準確否)仍會傳遞到這個View的onTouchEvent,如果返回 false的話,接下來的事件就不會再傳遞到這個View,而是執行其Parent View的onTouchEvent,每當一個View的onTouchEvent事件返回false,接下來的事件(如果還有的話)就會止步於這個 View的Parent View,每次上升一個層次,類似於冒泡方式。

 

  Touch事件傳遞過程中經過的元素都是一個View,但是事件處理的最外層的元素卻不是View,除下跟Window有關的事件,當一個 Touch事件發生的時候,會首先調用當前Activity的dispatchTouchEvent函數,然後纔將事件傳遞至下層的View元素。當 dispatchTouchEvent經過一個View往下傳遞的時候,如果這個View是一個ViewGroup,會調用其 onInterceptTouchEvent函數,這個函數表示是否攔截Touch事件,如果這個函數返回true,表示這個ViewGroup攔截了事 件的傳遞,Touch事件不會再往下傳遞給它的子View,而是由它處理,所以會調用它的onTouchEvent函數,如果在傳遞的過程中沒有 ViewGroup攔截事件,即經過的所有ViewGroup都返回false,那麼事件最終會傳遞至最內層的View,一般是一個Widget,當然也 可以是一ViewGroup(其內部不包含任何元素),如果最後事件傳遞到一View(非ViewGroup),那麼會首先調用這個View的 onTouchListener(如果設置了的話),如果onTouchListener返回false則繼續調用View的 onTouchEvent(默認返回true),如果最後事件傳遞到一ViewGroup(無子View),會調用它的onTouchEvent函數,默 認返回false。


  如果調用一個View的onTouchEvent函數時返回true的話,那麼接下來的Touch Event事件(ACTION_DOWN後的ACTION_UP,及可能在中間包含的若干個ACTION_MOVE,從ACTION_DOWN至 ACTION_UP爲一個連續事件,這是自己想的,不知道準確否)仍會傳遞到這個View並調用它的onTouchEvent函數,在 onTouchEvent函數中可以根據條件返回不同的值,如果某一次在此函數中返回了false那麼接下來的Touch Event事件就不會再傳遞到這個View,而會在其Parent View終止,調用其Parent View的onTouchEvent。如果所有的View都的onTouchEvent函數都返回false,那麼接下來的Touch Event事件會由Activity處理,即調用Activity的onTouchEvent函數。

 

  當調用ViewGroup的dispatchTouchEvent函數時,會首先調用onInterceptTouchEvent函數判斷有沒 有攔截事件,如果沒有攔截(返回false),則會依次調用這個ViewGroup的所有子View的dispatchTouchEvent函數。比如一 個FrameLayout上層疊了三個ViewGroup,那麼在這個FrameLayout的dispatchTouchEvent中會依次調用這三個 ViewGroup的dispatchTouchEvent函數,而在這三個ViewGroup的dispatchTouchEvent中也會依次調用他 們的子View的dispatchTouchEvent函數,直到其中一個View的dispatchTouchEvent返回true,表示已經處理了 這個Touch事件,不需要再調用這個View的Slibling Views。比如調用這三個層疊的ViewGroup的dispatchTouchEvent函數時,如果第一個ViewGroup的 dispatchTouchEvent函數就返回了true(已經消耗掉了這個事件),那麼其他兩個ViewGroup的 dispatchTouchEvent就不會再被調用。可以自定義一個ViewGroup的子類並重載他的dispatchTouchEvent函數,使 其處理過Touch事件後仍返回false,那麼就還會調用其他兄弟View的dispatchTouchEvent函數。

發佈了40 篇原創文章 · 獲贊 3 · 訪問量 33萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章