在Android系統上,有多種方法截取使用者跟應用程式互動的事件。當考慮使用者介面的事件時,方法是要截獲來自跟使用者互動的那個特定的View對象的事件。View類提供這種能力。
在用於組成布局的的各種View類中,你可以注意到幾個公用的用於UI事件的回調方法。在各自對象上的動作發生時,Android架構來調用這些方法。例如,當觸摸一個View對象(如一個按鈕)時,該對象的onTouchEvent()方法會被調用。但是,為了截獲這個事件,你必須繼承這個View類,並重寫這個方法。然而為了處理這類事件而擴充每個View對象是不切實際的。這就是為什麼View類還包含了一個帶有讓你更容易定義回調方法的嵌套介面集合,這些叫做事件監聽器的介面是用於捕獲使用者跟UI互動的入口。
雖然多數情況下,你會使用事件監聽器來監聽與使用者的互動,但是,有些時候為了建立一個定製的組件,你要擴充一個View類。你可能想要擴充Button類使它更精緻。在這種情況中,你就要能夠使用事件處理器給你的類定義預設的事件行為。
事件監聽器
一個事件監聽器是包含單一回調方法的View類中的一個介面,當這個View的被註冊的監聽器由使用者跟UI中對應項目的互動行為而觸發時,Android架構會調用這些方法。
在事件監聽器介面中包含了以下回調方法:
onClick()
源自View.OnClickListener介面。當使用者觸摸這個該項目(在觸摸模式中),或讓焦點落在該項目上時,這個方法被調用。
onLongClick()
源自View.OnLongClickListener介面使用者長時間觸摸(在觸摸模式中),或長時間按下該項目時,這個方法會被調用。
onKey()
源自View.OnKeyListen介面,當使用者讓焦點落在在該項目上,並且按下或釋放裝置上的一個鍵時,這個方法會被調用。
onTouch()
源自View.OnTouchListener介面,當使用者執行一個符合觸屏事件的動作(包括一個按下動作、一個釋放動作,或者任何在螢幕上的手勢)時,這個方法被調用。
onCreateContextMenu()
源自ViewOnCreateContextMenuListener介面,當一個操作功能表被建立時,這個方法被調用。
這些方法是各自介面的唯一成員。要定義這些方法並用它們來處理來事件,就要在Activity中實現這些嵌套的介面,或者把它作為一個匿名類來定義。然後,把你實現的執行個體傳遞給各自的View.set…Listener()方法。(例如,調用setOnClickListener()方法,並把OnClickListener()的實現傳遞給它)。
下例顯示了如何給一個Button註冊一個on-click監聽器:
// Create an anonymous implementation of OnClickListener
private OnClickListener mCorkyListener = new OnClickListener() {
public void onClick(View v) {
// do something when the button is clicked
}
};
protected void onCreate(Bundle savedValues) {
...
// Capture our button from layout
Button button = (Button)findViewById(R.id.corky);
// Register the onClick listener with the implementation above
button.setOnClickListener(mCorkyListener);
...
}
你可能也發現包OnClickListener作為Activity的一部分來實現會更方便。這樣會避免外部類的載入和對象空間的分配。如:
public class ExampleActivity extends Activity implements OnClickListener {
protected void onCreate(Bundle savedValues) {
...
Button button = (Button)findViewById(R.id.corky);
button.setOnClickListener(this);
}
// Implement the OnClickListener callback
public void onClick(View v) {
// do something when the button is clicked
}
...
}
我們注意到,上例代碼中的onClick()回調方法沒有傳回值,但是其他的一些事件監聽器方法必須要返回一個布爾值。其原因要依賴具體的事件。以下是原因說明:
1. onLongClick()---這個方法返回一個布爾值,用來指明是否因你已經使用了這個事件而不應該執行進一步的動作。也就是說,如果返回true,表明你已經處理了這個事件,並且事件應該就此停止;如果返回false,表明沒有處理這個事件,因而這個事件應該繼續傳遞給任何其他的on-click監聽器。
2. onKey()---這個方法返回一個布爾值,用來指明是否因你已經使用了這個事件而不應該執行進一步的動作。也就是說,如果返回true,表明你已經處理了這個事件,並且事件應該就此停止;如果返回false,表明你沒有處理這個事件,並且這個事件應該繼續傳遞給任何其他的on-key監聽器。
3. onTouch()---這個方法返回一個布爾值,用來指明你的監聽器是否使用這個事件。重要的是這個事件能夠有多個彼此跟隨的動作。因此,如果在接收到按下動作事件時返回了false,表明你不使用這個事件,並且對來自這個事件的後續動作也不感興趣。因此,你將不會調用這個事件中的任何其他動作,如手勢或事件的最終動作。
請記住,按鍵事件始終是發送給當前View的焦點對象。它們從View階層樹的頂層開始調度,然後依次向下,直到它們到達合適的目標。如果當前的View(或View的子View)有焦點,那麼通過dispatchKeyEvent()方法你能夠看到事件的執行軌跡。你可以有選擇的捕獲通過的View對象的按鍵事件,因為你能夠接受到Activity內部的onKeyDown()和onKeyUp()兩個事件。
注意:Android會首先呼叫事件處理器,其次才會調用類定義中適當的預設處理器。如,如果從這些方法中返回true,那麼事件監聽器中會停止把事件傳播給其他的事件監聽器,並也會阻塞對View中預設的事件處理器的回調。因此只有確定要終止事件傳播時才返回true。
事件處理器
如果你是從View類開始構建一個定製的組件,那麼你要定義幾個預設的回調方法作為預設的事件處理器。在“定製組件”的專題中,你會學到一些共同的用於事件處理的回調方法,它們是:
1. onKeyDow(int, KeyEvent)---當一個鍵被按下時,會調用這個方法;
2. onKeyUp(int, KeyEvent)---當一個被按下的鍵彈起時,會調用這個方法;
3. onTrackballEvent(MotionEvent)---當軌跡球滾動時,會調用這個方法;
4. onTouchEvent(MotionEvent)---當個觸屏動作發生時,會調用這個方法;
5. onFoucusChanged(Boolean, int, Rect)---當一個View對象獲得或失去焦點時,會調用這個方法。
還有一些其他的你應該瞭解的方法,它們不是View類的一部分,但是能夠直接的影響到你處理事件的方式。因此,在管理布局中更複雜的事件時,要考慮這些方法:
1. Activity.dispatchTouchEvent(MotionEvent)---這個方法允許你在觸屏事件被分發被對應的視窗之前截獲所有的觸屏事件。
2. ViewGroup.onInterceptTouchEvent(MotionEvent)---這個方法允許一個ViewGroup對象查看分發給它的子View對象的事件。
3. ViewParent.requestDisallowInterceptTouchEvent(Boolean)---在父View對象上調用這個方法,指明父View對象是否應該截獲onInterceptTouchEvent(MotionEvent)觸屏事件。
觸屏模式
當使用者用方向鍵或滑鼠滾輪瀏覽一個使用者介面時,就需要給可操作的項目一個焦點(如按鈕),以便使用者能夠看到介面在接收輸入。但是,如果裝置有觸摸的能力,並且使用者通過觸摸的方式來介面進行互動,那麼就不再需要高亮顯示項目,或是把焦點給一個特殊的View對象。這樣,就有了名叫“觸摸模式”的互動模式。
對於一個有觸摸能力的裝置,一旦使用者觸控螢幕幕,這個裝置就會進入觸摸模式。從這點開始,只有個isFocusableInTouchMode()方法的傳回值是true的哪個View對象才是可設定焦點,如文本編輯構件。其他的View對象是可觸摸的,如button,在觸摸它的時候不需要焦點,它們只是簡單的在被按下的時候觸發on-click監聽器。
任何時刻,只要使用者點擊了一個方向鍵或滾動了滑鼠滾輪,裝置就會退出觸摸模式,並且尋找一個需要焦點的View對象。現在,使用者可以恢複不用觸控螢幕就能夠跟使用者介面進行互動的模式。
觸屏模式狀態是被整個系統保持的(所有的視窗和Activity)。你能夠調用isInTouchMode()來查看裝置當前是否是觸屏模式。
處理焦點
Android架構會處理焦點的活動來響應使用者的輸入,這包括在View對象被刪除或隱藏,或一個新View對象變成有效時焦點的改變。View對象通過isFocusable()方法來表明它們是否有意願擷取焦點。調用setFocusable()可以改變當前的焦點對象。在觸摸模式中,你可以用isFocusableIntouchMode()方法來查詢一個View對象是否允許有焦點。你能夠用setFocusableInTouchMode()方法來改變這種設定。
焦點的移動是基於在特定的方向中尋找最近的可接受焦點的View對象的演算法,很少的情況下,這種演算法會與開發人員的期望行為不匹配,在這種情況下,你能夠用下列的布局檔案中的XML屬性來明確的覆蓋預設的演算法:nextFocusDown、nextFocusLeft、nextFocusRight、和nextFocusUp。給離開焦點的View對象添加這些屬性中的一個,在這個屬性值中指定焦點要移到下一個View對象的id,如:
<LinearLayout
android:orientation="vertical"
... >
<Button android:id="@+id/top"
android:nextFocusUp="@+id/bottom"
... />
<Button android:id="@+id/bottom"
android:nextFocusDown="@+id/top"
... />
</LinearLayout>
通常,在這個垂直布局中,從第一個Button的向上瀏覽不會去任何地方,同樣從第二個Button的向下瀏覽也不會去任何地方。現在,top按鈕把bottom按鈕定義為下一個焦點獲得者(反之亦然),這樣瀏覽焦點就會在這兩個按鈕之間來回切換。
如果你喜歡在UI中聲明一個View對象作為可聚焦對象(傳統的不會這樣),就要把android:focusable的XML資料添加給對應的View對象,並在布局聲明中把屬性值設定為:true。在觸屏模式中,你也能夠用android:focusableInTouchMode屬性把一個View對象設定為可聚焦對象。
調用requestFocus()方法給一個特定的View對象設定焦點。
使用onFocusChange()方法來監聽焦時間點事件(當一個View對象接受或失去焦點時會發出通知)。