本篇內容將結合Android源碼來分析Android的事件傳遞機制。眾所周知,點按、滑動、觸摸構成了Android等智能設備的基本操作,幾乎所有的應用都通過對觸摸屏的操作來進行應用程序的使用。那麼,在Android中,觸摸事件是如何響應及傳遞的呢,通過本篇內容你將有一個初步的了解。
實驗環境
- OS X 10.9
- Eclipse(ADT)
- Android源碼版本:API Level 19(Android 4.4)
Android事件構成
在Android中,事件主要包括點按、長按、拖拽、滑動等,點按又包括單擊和雙擊,另外還包括單指操作和多指操作。所有這些都構成了Android中得事件響應。總的來說,所有的事件都由如下三個部分作為基礎:
- 按下(ACTION_DOWN)
- 移動(ACTION_MOVE)
- 抬起(ACTION_UP)
所有的操作事件首先必須執行的是按下操作(ACTION_DOWN),之後所有的操作都是以按下操作作為前提,當按下操作完成後,接下來可能是一段移動(ACTION_MOVE)然後抬起(ACTION_UP),或者是按下操作執行完成後沒有移動就直接抬起。這一系列的動作在Android中都可以進行控制。
我們知道,所有的事件操作都發生在觸摸屏上,而在屏幕上與我們交互的就是各種各樣的視圖組件(View),在Android中,所有的視圖都繼承於View,另外通過各種布局組件(ViewGroup)來對View進行布局,ViewGroup也繼承於View。所有的UI控制項例如Button、TextView都是繼承於View,而所有的布局控制項例如RelativeLayout、容器控制項例如ListView都是繼承於ViewGroup。所以,我們的事件操作主要就是發生在View和ViewGroup之間,那麼View和ViewGroup中主要有哪些方法來對這些事件進行響應呢?記住如下3個方法,我們通過查看View和ViewGroup的源碼可以看到:
View.java
public boolean dispatchTouchEvent(MotionEvent event) public boolean onTouchEvent(MotionEvent event)
public boolean dispatchTouchEvent(MotionEvent event) public boolean onTouchEvent(MotionEvent event) public boolean onInterceptTouchEvent(MotionEvent ev)
在View和ViewGroup中都存在dispatchTouchEvent和onTouchEvent方法,但是在ViewGroup中還有一個onInterceptTouchEvent方法,那這些方法都是幹嘛的呢?別急,我們先看看他們的返回值。這些方法的返回值全部都是boolean
型,為什麼是boolean型呢,看看本文的標題,「事件傳遞」,傳遞的過程就是一個接一個,那到了某一個點后是否要繼續往下傳遞呢?你發現了嗎,「是否」二字就決定了這些方法應該用boolean來作為返回值。沒錯,這些方法都返回true或者是false。在Android中,所有的事件都是從開始經過傳遞到完成事件的消費,這些方法的返回值就決定了某一事件是否是繼續往下傳,還是被攔截了,或是被消費了。
接下來就是這些方法的參數,都接受了一個MotionEvent
類型的參數,MotionEvent繼承於InputEvent,用於標記各種動作事件。之前提到的ACTION_DOWN、ACTION_MOVE、ACTION_UP都是MotinEvent中定義的常量。我們通過MotionEvent傳進來的事件類型來判斷接收的是哪一種類型的事件。到現在,這三個方法的返回值和參數你應該都明白了,接下來就解釋一下這三個方法分別在什麼時候處理事件。
dispatchTouchEvent
方法用於事件的分發,Android中所有的事件都必須經過這個方法的分發,然後決定是自身消費當前事件還是繼續往下分發給子控制項處理。返回true表示不繼續分發,事件沒有被消費。返回false則繼續往下分發,如果是ViewGroup則分發給onInterceptTouchEvent進行判斷是否攔截該事件。onTouchEvent
方法用於事件的處理,返回true表示消費處理當前事件,返回false則不處理,交給子控制項進行繼續分發。onInterceptTouchEvent
是ViewGroup中才有的方法,View中沒有,它的作用是負責事件的攔截,返回true的時候表示攔截當前事件,不繼續往下分發,交給自身的onTouchEvent進行處理。返回false則不攔截,繼續往下傳。這是ViewGroup特有的方法,因為ViewGroup中可能還有子View,而在Android中View中是不能再包含子View的(iOS可以)。
到目前為止,Android中事件的構成以及事件處理方法的作用你應該比較清楚了,接下來我們就通過一個Demo來實際體驗實驗一下。
Android事件處理
首先在Eclipse新建一個工程,並新建一個類RTButton繼承Button,用來實現我們對按鈕事件的跟蹤。
RTButton.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41public class RTButton extends Button { public RTButton(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent event) { switch (event.getAction) { case MotionEvent.ACTION_DOWN: System.out.println("RTButton---dispatchTouchEvent---DOWN"); break; case MotionEvent.ACTION_MOVE: System.out.println("RTButton---dispatchTouchEvent---MOVE"); break; case MotionEvent.ACTION_UP: System.out.println("RTButton---dispatchTouchEvent---UP"); break; default: break; } return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction) { case MotionEvent.ACTION_DOWN: System.out.println("RTButton---onTouchEvent---DOWN"); break; case MotionEvent.ACTION_MOVE: System.out.println("RTButton---onTouchEvent---MOVE"); break; case MotionEvent.ACTION_UP: System.out.println("RTButton---onTouchEvent---UP"); break; default: break; } return super.onTouchEvent(event); } }
在RTButton中我重寫了dispatchTouchEvent和onTouchEvent方法,並獲取了MotionEvent各個事件狀態,列印輸出了每一個狀態下的信息。然後在activity_main.xml中直接在根布局下放入自定義的按鈕RTButton。
activity_main.xml
"match_parent""match_parent"
接下來在Activity中為RTButton設置onTouch和onClick的監聽器來跟蹤事件傳遞的過程,另外,Activity中也有一個dispatchTouchEvent方法和一個onTouchEvent方法,我們也重寫他們並輸出列印信息。
MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75public class MainActivity extends Activity { private RTButton button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = (RTButton)this.findViewById(R.id.btn); button.setOnTouchListener(new OnTouchListener { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction) { case MotionEvent.ACTION_DOWN: System.out.println("RTButton---onTouch---DOWN"); break; case MotionEvent.ACTION_MOVE: System.out.println("RTButton---onTouch---MOVE"); break; case MotionEvent.ACTION_UP: System.out.println("RTButton---onTouch---UP"); break; default: break; } return false; } }); button.setOnClickListener(new OnClickListener { @Override public void onClick(View v) { System.out.println("RTButton clicked!"); } }); } @Override public boolean dispatchTouchEvent(MotionEvent event) { switch (event.getAction) { case MotionEvent.ACTION_DOWN: System.out.println("Activity---dispatchTouchEvent---DOWN"); break; case MotionEvent.ACTION_MOVE: System.out.println("Activity---dispatchTouchEvent---MOVE"