Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> View的事件分發機制

View的事件分發機制

編輯:關於Android編程

View的事件分發機制,也稱為View的事件攔截機制,在說事件分發機制之前,需要對MotionEvent對象就行分析,也就是點擊事件,MotionEvent是手指接觸屏幕後所產生的一系列事件,典型的事件類型有如下幾種:

ACTION_DOWN——手指剛接觸屏幕;

ACTION_MOVE——手指在屏幕上移動:

ACTION_UP——手指在屏幕上松開的一瞬間。

點擊屏幕後松開,事件順序 DOWN->UP

點擊屏幕滑動一會再松開,事件順序為DOWN->MOVE->.....MOVE->UP

所謂點擊事件的分發過程,其實就是對MotionEvent事件的分發過程,即當一個MotionEvent產生了以後,系統需要把這個事件傳遞給一個具體的View,而這個傳遞的過程就是分發過程。分發過程由三個很重要的方法來共同完成;分別是dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent。

 

public boolean dispatchTouchEvent(MotionEvent event)

用來進行事件的分發。從這個方法的名稱就能看出來它的含義。如果事件能夠傳遞給當前View,那麼此方法一定會被調用,返回結果受當前View的onTouchEvent和下級View的dispatchTouchEvent方法的影響,表示是否消耗當前事件。

 

public boolean onInterceptTouchEvent(MotionEvent ev)

在上述方法內部調用,用來判斷是否攔截某個事件,如果當前View攔截了某個事件,那麼在同一個事件序列中,此方法不會被再次調用,返回結果表示是否攔截當前事件。

 

public boolean onTouchEvent(MotionEvent event)

在dispatchTouchEvent方法內部調用,用來處理點擊事件,返回結果表示是否消耗當前事件,如果不消耗,則在同一個事件序列中,當前View無法再次接收到事件。

我們知道View結構是樹形結構,也就是說,View可以放在ViewGroup裡面,通過不同的組合來實現不同的樣式。

現在假設一個情況:

一個View放在ViewGroup1中,這個ViewGroup1又放在另外一個ViewGroup2中,現在來分析點擊事件的分發過程。

\

布局如上圖所示。

ViewGroup1最外層的ViewGroup ViewGroup2中間的ViewGroup。View最底層的View。

他們代碼都很簡單,都只是重寫了時間攔截核處理的幾個方法。

代碼貼上:

 

package com.qian;

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

public class ViewGroup1  extends LinearLayout{

	private String tag = "ViewGroup1";
	@SuppressLint("NewApi") 
	public ViewGroup1(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}
	public ViewGroup1(Context context, AttributeSet attrs) {
		super(context, attrs);
	}
	public ViewGroup1(Context context) {
		super(context);
	}
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		Log.i(tag, "onTouchEvent");
		return super.onTouchEvent(event);
	}
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		Log.i(tag, "onInterceptTouchEvent");
		return super.onInterceptTouchEvent(ev);
	}
	@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		Log.i(tag, "dispatchTouchEvent");
		return super.dispatchTouchEvent(ev);
	}
}
package com.qian;

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

public class ViewGroup2  extends LinearLayout{

	private String tag = "ViewGroup2";
	@SuppressLint("NewApi") 
	public ViewGroup2(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}
	public ViewGroup2(Context context, AttributeSet attrs) {
		super(context, attrs);
	}
	public ViewGroup2(Context context) {
		super(context);
	}
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		Log.i(tag, "onTouchEvent");
		return super.onTouchEvent(event);
	}
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		Log.i(tag, "onInterceptTouchEvent");
		return super.onInterceptTouchEvent(ev);
	}
	@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		Log.i(tag, "dispatchTouchEvent");
		return super.dispatchTouchEvent(ev);
	}
}

最後View1的代碼:

 

 

package com.qian;

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

public class View1 extends TextView{

	private String tag = "View1";
	public View1(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
	}
	public View1(Context context, AttributeSet attrs) {
		super(context, attrs);
	}
	public View1(Context context) {
		super(context);
	}
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		Log.i(tag , "onTouchEvent");
		return super.onTouchEvent(event);
	}
	@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		Log.i(tag , "dispatchTouchEvent");
		return super.dispatchTouchEvent(ev);
	}
}

layout布局:

 

 



    

	
	    
	    


    

布局效果如圖所示:

  

\

 

從上面的代碼可以看出,ViewGroup級別比View的級別要高一點,比View多了方法。onInterceptTouchEvent,這個方法看名字就能猜到是事件攔截機制的核心方法,由於View中已經不能再放子元素了,所以它沒有這個方法。

上面的代碼只是添加了一些Log而已,布局改了一下北京顏色,主要是方便觀察。現在點擊一下中間的View1所在的綠色的區域,打印的Log如下:

\

 

 

從打印的Log可以得出事件分發或者說事件傳遞的順序為,從dispatchTouchEvent和onInterceptTouchEvent方法看:

ViewGroup1——>ViewGroup2——>View1

而事件的處理順序可以從onTouchEvent方法看出:

View1——>ViewGroup2——>ViewGroup1

事件傳遞的返回值表示 true,攔截 false 不攔截繼續往下傳遞 默認為false

事件處理的返回值也類似, true 處理了 不用審核了 false 給上級處理 默認值false

事件傳遞過程,這裡暫時先不管dispatchTouchEvent這個方法,這個方法一般不太會改這個,所以這裡主要關注onInterceptTouchEvent方法,它的返回值就表示是否攔截事件。所以可以可以把上面的整個事件過程整理為下面一張圖:

\

下面再改一下代碼,加深理解.

在ViewGroup1中修改onInterceptTouchEvent,返回true

 

	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		Log.i(tag, "onInterceptTouchEvent");
		return true;
		//return super.onInterceptTouchEvent(ev);
	}

再來看一下Log:

 

\

跟我們設想的一樣,ViewGroup1攔截了事件,那麼ViewGroup2和View1就不能收到事件。所以Log中只能看到ViewGroup1的Log。ViewGroup1攔截事件的過程如下圖所示:

\

同樣,我們修改ViewGroup2的onInterceptTouchEvent,返回true ViewGroup1改回默認的false。打印的Log如下:

\

 

可以看到,這次是ViewGroup2攔截了事件。ViewGroup2攔截事件的過程如下圖所示:

\

這樣對事件的分發,攔截應該就比較清楚了,下面再看看對事件的處理,也就是onTouchEvent方法。這裡最底層的View1,每次處理完任務也就是事件,都需要向上級報告,需要上級的確認,所以事件處理需要返回false。那麼如果返回true,又是怎麼樣的呢?直接看Log打印。

 

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		Log.i(tag , "onTouchEvent");
		return true;
		//return super.onTouchEvent(event);
	}

\

 

 

此時View1處理事件的過程如下:

\

如果如果ViewGroup的2onTouchEvent方法true。

\

總結一下,用一段偽代碼

 

       @Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		
	   boolean isConsume = false;
	   if(onInterceptTouchEvent(ev)) {
		   isConsume = onTouchEvent(ev);
	   } else {
		   isConsume = dispatchTouchEvent(ev);
	   }
	   return isConsume;
	   
	}

通過這一段偽代碼,也理解了dispaTouchEvent方法的原理,雖然上面還說暫時不管,dispaTouchEvent方法從全局上把我整個傳遞過程。對於一個根ViewGroup來說,點擊事件產生後,首先會傳遞給它,這時它的dispaTouchEvent就會被調用,如果這個ViewGroup的onInterceptTouchEvent方法返回true就表示他要攔截當前事件,接著事件就會交給這個ViewGroup處理,即它的onTouchEvent就會被調用,如果這個ViewGroup的onInterceptTouchEvent方法返回false返回false就表示它不攔截這個事件,這時當前事件會繼續傳遞給它的子元素,接著子元素的dispaTouchEvent方法就會被調用,如此反復直到事件被最終處理。

 

順便提一下onTouchListener,當一個View需要處理事件時,如果它設置了onTouchListener,那麼onTouchListener中的onTouch方法會被調用,這時事件如何處理還要看onTouch的返回值,如果onTouch返回false,當前View的onTouch方法會被調用,反之不會被調用,由此可見,onTouchListener的優先級比onTouchEvent優先級要高。

 

再說onClickListener,在onTouchEvent中,如果當前View設置了onClickListener,那麼他的onClick方法會被調用,前提是onTouchEvent被調用,平時經常用得onClickListener,優先級最低,處於事件傳遞的尾端。

 

當一個點擊事件產生後,它的傳遞過程遵循如下順序:

Activity——>Window——>View

即事件總是先傳遞給Activity,Activity再傳遞給Window,最後Window再傳遞給頂級View。頂級View接受事件後,就會按照事件分發機制去分發事件。考慮一種情況,如果一個View的onTouchEvent返回false,那麼它的父容器的onTouchEvent方法就會被調用,一次類推,如果所有元素的onTouchEvent都返回false,那麼最終這個事件會傳遞給Activity去處理。這就是Activity為什麼也有onTouchEvent方法和dispatchTouchEvent方法,但是它沒有onInterceptTouchEvent方法,因為它處在整個事件傳遞的最高層,不需要onInterceptTouchEvent攔截事件這個方法。

 

總結一些View的分發機制的結論。

1)同一個事件序列是指觸摸屏幕的那一刻起,到手指離開屏幕的那一刻結束,在這個過程中所產生的一系列事件,這個事件徐柳以down事件開始,中間含有數量不定的move事件,最終以up事件結束。

2)正常情況下,一個事件序列只能被一個View蘭家且消耗,原因參考3),因為一旦一個元素攔截了某些事件,那麼同一個事件序列的所有事件都會交給它處理,但不是絕對的,可以強制。

3)某個View一旦絕對攔截,那麼這個一個事件序列都只能由它來處理,並且它的onInterceptTouchEvent方法不會被再次調用。

4)某個View一旦開始處理事件,如果不消耗down,onTouchEvent返回false,那麼這個一個事件序列都不會交給它來處理,並且事件將重新交給它的父容器去處理。

5)如果所有的View都不處理事件,這個事件最終會小時。

6)ViewGroup默認不攔截任何事件。

7)View沒有onInterceptTouchEvent方法,也就是不攔截事件,只要有時間傳遞給它,它的onTouchEvent方法就會被調用。

8)View的onTouchEvent默認都會消耗事件,也就是onTouchEvent方法返回true。除非它是不可點擊的(clickable和longclickable同時為false)。View的longclickable默認為false,clickable要看情況,比如Button默認是true,也就是Button默認會消耗事件,onTouchEvent方法返回true。但是TextView不是不可點擊的,onTouchEvent方法返回false

這裡說明一下為什麼最上面View1采用繼承TextView,因為TextView不可點擊,方便演示。

9)View的enable屬性不影響onTouchEvent的默認返回值。哪怕一個View是disable狀態的。主要clickable和longclickable有一個為true,那麼它的onTouchEvent方法就會返回true。

10)onClick會發生的前提是當前View是可點擊的,並且它收到了down和up事件。

11)事件傳遞過程是由外向內的,即事件總是先傳遞給父元素,然後再由父元素傳遞給子View,但是子元素可以通過requestDisallowTouchEvent方法干擾父元素的分發過程,但是down事件除外。

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved