上篇文章中已經瞭解到介面Activity的繪製完全依賴其載入的檢視元件View,不僅如此,使用者的每次觸控操作都可以在介面Activity內接收並響應,也可以直接傳遞給其中的某個檢視View響應。本文將針對這兩種使用者互動方式分別展開介紹。

介面內互動

介面響應

說到介面互動,很容易想到使用者在裝置螢幕上的觸控操作。可是螢幕那麼大要怎麼確定使用者觸控的位置呢?Android系統定義了一套螢幕座標規則,該規則不僅適用於當前的螢幕互動,在後文提及的動畫繪製及其他螢幕相關操作等都同樣適用。該規則將螢幕的左上角作為螢幕座標的原點,從左上角往右上角延伸的方向作為螢幕座標的x軸,從左上角往左下角延伸的方向作為螢幕座標的y軸

比如針對一款 1024x512 尺寸的TV裝置,其左下角的螢幕座標值為 (0, 512),右下角的螢幕座標值為 (1024, 512),右上角的螢幕座標值為 (1024, 0),左上角的螢幕座標值為 (0, 0)。

對螢幕的觸控位置有了衡量標準,是不是就可以根據不同的位置做觸控操作了呢?說到觸控操作,也需要細化之後單獨處理。Android系統將使用者操作行為,大致分為三種:按下行為滑動行為抬起釋放行為。這樣系統就可以根據每一個操作行為做單獨的響應處理了。

另外,使用者的操作物件,除了上文提到的硬體裝置螢幕以外,還有硬體裝置的按鍵(包括硬體按鍵和虛擬按鍵)。只不過對按鍵的操作行為只有按下行為抬起釋放行為兩種,而且按鍵的操作不需要用到螢幕座標相關內容。

基於上文的介紹,可以在介面Activity中可以分別重寫下邊三個方法對使用者的介面操作互動做出響應。

  • boolean onTouchEvent(MotionEvent event)

    在子檢視沒有處理的情況下,使用者對硬體裝置螢幕的每一個操作,都會回撥一次該方法。


    其引數android.view.MotionEvent事件類的例項化物件event。

    event.getAction()方法可以獲取當前事件行為,包括MotionEvent.ACTION_DOWN按下行為MotionEvent.ACTION_MOVE滑動行為MotionEvent.ACTION_UP抬起釋放行為等。

    event.getX()方法獲取當前操作的螢幕座標x軸值。

    同理event.getY()方法獲取當前操作的螢幕座標y軸值。

  • boolean onKeyDown(int keyCode, KeyEvent event)

    在子檢視沒有處理的情況下,使用者對硬體裝置按鍵的每一次按下行為,都會回撥一次該方法。


    引數一int型別的keyCode指定按鍵型別,一般其值與引數二event.getKeyCode()相等。

    引數二android.view.KeyEvent類的例項化物件event。

    event.getAction()方法同樣可以獲取當前事件行為,只有KeyEvent.ACTION_DOWN按下行為KeyEvent.ACTION_UP抬起釋放行為兩個行為值。

    event.getKeyCode()方法可以獲取觸發當前事件的按鍵型別,其值包括KeyEvent.KEYCODE_HOMEHOME鍵KeyEvent.KEYCODE_POWER電源鍵KEYCODE_VOLUME_UP音量增加鍵等。

  • boolean onKeyUp(int keyCode, KeyEvent event)

    在子檢視沒有處理的情況下,使用者對硬體裝置按鍵的每一次抬起釋放行為,都會回撥一次該方法。其兩個引數與上述onKeyDown()中的兩個引數類似。

檢視響應

相對來說,介面內的檢視響應要繁瑣一些,而能實現的效果也更多樣化。當把檢視View作為使用者的操作物件時,仍然可以重寫上述介面響應的三個方法,但是系統檢視往往也封裝了一層更加簡單粗暴的響應方法。

在檢視中重寫介面響應的三個方法後,如果返回的結果為true,則上文介面響應中的三個方法將不會被回撥。

為什麼需要封裝一層響應方法呢?使用者對檢視的操作,往往就是點選(短時間內執行按下行為抬起釋放行為),長按(在執行按下行為後等待一段時間再執行抬起釋放行為),拖拽(在執行按下行為後執行一段滑動行為之後再執行抬起釋放行為)這些固定操作型別。如果每個檢視都要細分使用者的操作行為,就會有大量冗餘的操作型別判斷程式碼,所以AndroidSDK定義了一系列介面分別對應使用者的操作型別。檢視如果需要響應某個操作,只需要設定其操作型別介面的例項化物件,並在該物件中實現相關方法即可。而這些介面主要有以下三個。

  • View.OnClickListener介面

    需要實現onClick(View view)方法,在該方法內響應響應檢視View被使用者點選後的程式碼邏輯。
  • View.OnLongClickListener介面

    需要實現onLongClick(View view)方法,在該方法內響應響應檢視View被使用者長按後的程式碼邏輯。
  • View.OnDragListener介面

    需要實現onDrag(View v, DragEvent event)方法,在該方法內響應檢視View被使用者拖拽後的程式碼邏輯。

另外,不同的系統檢視也可能有單獨設定的響應方法,或者自定義檢視也會提供單獨的響應方法,例如列表檢視中的某一行資料被單獨點選後如何響應,這些都要根據具體的檢視類查詢並使用對應的響應方法,這裡不再贅述。

事件傳遞機制

在上文介面響應的三個方法中,關於他們被回撥的時機,有個前提是子檢視沒有處理,即子檢視的介面響應方法返回結果為false。這就涉及到Android系統的事件傳遞機制了。

我們知道介面Activity在建立之後會呼叫setContentView(int layoutId)載入根檢視View,而根視圖裡邊則可以內嵌一層層的子檢視。那麼,如果使用者將手指觸控到螢幕上,會觸發按下行為,該行為作為事件首先傳遞到根檢視中,之後根檢視再將該事件傳遞給子檢視,子檢視再將該事件傳遞給子檢視的子檢視,這樣按照載入時的巢狀順序一層層傳遞事件,稱之為事件分發

直到該事件傳遞到最後一層子檢視,或者某一層檢視不再繼續傳遞該事件,那麼該事件將在最後傳遞到的這層檢視中被首先處理。而每層檢視在收到傳遞進來的事件後,都有兩條路可以選擇,要麼將該事件繼續傳遞給子檢視,要麼自己處理該事件,如果選擇第二條路不再繼續傳遞子檢視而是自己處理該事件,稱之為事件攔截

一旦某層檢視處理了該事件,那麼其父層檢視將繼續處理該事件,之後是父層的父層檢視處理該事件,事件被這樣一層層處理,直到根檢視處理該事件結束,稱之為事件處理

在經歷了事件分發事件處理之後,這樣的一個事件傳遞機制就算完成了。而上文提到的每一個事件,都是如此。

上述過程在程式碼中的實現,只需要針對事件分發事件攔截事件處理分別定義一個可重寫的方法即可。能夠重寫該方法的位置主要是android.app.Acitivtyandroid.view.View中,由於事件攔截只會發生在子檢視的傳遞過程中,在介面中並不需要,所以事件攔截對應的方法只在android.view.GroupView中重寫。

  • boolean dispatchTouchEvent (MotionEvent event)

    當某個事件被分發到該檢視時,系統回撥檢視中的該方法。返回結果表示當前事件是否被處理。
  • boolean onInterceptTouchEvent(MotionEvent event)

    當某個事件被分發到該檢視後,系統會回撥檢視中的該方法,根據其返回結果判斷是否攔截該事件交由當前檢視處理。預設返回結果為false,表示不攔截該事件,將會繼續回撥子檢視的dispatchTouchEvent()。返回結果為true時,表示攔截該事件,將會回調當前檢視的onTouchEvent().
  • boolean onTouchEvent (MotionEvent event)

    當某個事件輪到該檢視被處理時,系統會回撥檢視中的該方法。返回結果表示當前事件是否被處理。

介面間互動

上文介紹了針對一個介面Activity的互動響應,那麼兩個介面Activity之間如何互動呢?這就用到在載入介面一文中啟動Activity所使用的android.content.Intent意圖類了。不同於使用者與介面的互動,介面間互動主要是變數資料的共享,所以通過Intent支援的互動資料型別是有限的。

傳送資料介面

在啟動一個介面Activity之前要先建立意圖物件,在該意圖物件呼叫putExtras(Bundle bundle)方法,可以將要傳送的資料打包成android.os.Bundle型別的例項存入。

而該Bundle物件可以儲存的資料型別支援包括booleancharbyteshortintfloatdoublelong八種基本資料型別,String型別和實現Parcelable介面的任意型別,及其[]陣列或ArrayList陣列,和其他一些不常用型別。這些資料都是以key-value鍵值對的形式儲存在Bundle物件中。對於要儲存的不同資料型別,分別呼叫對應的putT(String key, T value)系列方法即可以引數一key和引數二value的形式存入,同樣可以呼叫對應的getT(String key)系列方法取出指定引數一key對應的value資料,這裡的T泛指支援的不同資料型別。

另外也可以在建立的意圖物件中直接呼叫putExtra(String key, T value)系列方法,將要傳送的資料直接以key-value鍵值對的形式存入,同樣也可以使用getTExtra(String key)系列方法取出指定引數一key對應的value資料,這裡的T同樣泛指Bundle可支援的不同資料型別。

在打包所有的資料後,就可以在當前介面Activity中繼續呼叫startActivity(Intent intent)系列方法啟動Intent意圖引數中指定的另一介面Activity了。

這裡的startActivity(Intent)方法是最簡單的啟動方法,另外還有startActivity(Intent, Bundle)在啟動時將要傳送的資料打包作為引數二傳入。

或者startActivityForResult(Intent intent, int requestCode)在啟動時傳入一個唯一值作為引數二,以區分啟動不同介面的意圖,在啟動的介面Activity返回後,系統會呼叫當前介面Activity中的onActivityResult(int requestCode, int resultCode, Intent data)方法,因此可以重寫該方法。並根據引數一的唯一性對之前啟動的不同介面意圖做區分處理。引數二是根據啟動介面不同關閉狀態所返回的結果值,預設為android.app.Activity.RESULT_CANCELED,另外也可以為android.app.Activity.RESULT_FIRST_USERandroid.app.Activity.RESULT_OK,其值需要在啟動介面返回時設定。引數三是從啟動介面返回的Intent型別,主要使用其中的Bundle打包資料型別物件,同樣其值可以在啟動介面返回時設定。

接收資料介面

作為接收資料的啟動介面Activity,在其繫結上下文環境之後,一般是在onCreate(Bundle savedInstanceState)方法中,可以使用getIntent()方法獲取傳遞進來的Intent意圖物件,獲取該物件之後自然就可以通過getBExtras()或一系列getTExtra(String key)獲取到打包的資料,這樣在啟動介面中就可以使用在啟動之前上一個介面Activtiy中的變數資料了。

而當啟動介面Activity在被使用者操作返回時,系統將回調該啟動介面的onBackPressed()方法,之後將該Activity從棧中移出並銷燬。所以可以重寫onBackPressed()方法,在該方法中呼叫setResult(int resultCode, Intent data)設定上文提到的返回時引數。

或者在啟動介面Activity程式碼中也可以主動呼叫finish()方法,以關閉當前介面。因此在呼叫finish()方法之前先呼叫setResult(int resultCode, Intent data)設定返回引數即可。