輸入事件
在Android中有多種方法可以用來攔截用戶與應用交互產生的事件。如果要處理用戶交互觸發的事件,比較合適的方式是在用戶交互的特定View對象中捕獲事件。View類提供了處理的方法。
在用來構建佈局的各種View類中,你可能注意到一些看起來可用於UI事件的公共回調方法。當這些對象上產生各自的行爲時,Android框架就會調用相應的回調方法。舉例來說,當View(比如按鈕)被觸碰時,那麼這個對象上的onTouchEvent()方法就會被調用。不管怎樣,爲了攔截這個事件,你必須擴展這個類並且重寫這個方法。然而,爲了處理這樣的事件就擴展每種View對象是不切實際的。這就是爲什麼View類還包含一些可以很方便定義回調方法的嵌套接口的原因。這些被稱爲 event listeners 的接口是你捕獲用戶交互產生事件的關鍵。
雖然大多數情況下都是使用事件監聽器來監聽用戶交互,但是有時候爲了構建自定義組件,也需要去擴展View類。可能你想要擴展 Button
類來使它功能更強大。在這種情況下,你就可以使用 event
handlers 爲該類定義默認的事件行爲。
事件監聽器
事件監聽器是View類中包含單一回調方法的接口。爲View註冊好監聽器後,用戶通過與UI中的控件交互會觸發監聽器,這時Android框架就會調用這些方法。
事件監聽器接口中包含以下回調方法:
onClick()
- 來自
View.OnClickListener
。當用戶觸摸這個控件(在觸摸模式下),或者通過導航鍵/軌跡球將焦點移動到這個控件後再按下相應的“enter”鍵或按下軌跡球時,該方法被調用。 onLongClick()
- 來自
View.OnLongClickListener
。當用戶觸摸並按住這個控件(在觸摸模式下),或者通過導航鍵/軌跡球將焦點移動到這個控件再按住相應的“enter”鍵或按住軌跡球時(保持一秒鐘),該方法被調用。 onFocusChange()
- 來自
View.OnFocusChangeListener
。當用戶使用導航鍵或軌跡球導航到或離開這個控件時,該方法被調用。 onKey()
- 來自
View.OnKeyListener
。當用戶把焦點放在這個控件上並按下或釋放設備上的物理鍵時,該方法被調用。 onTouch()
- 來自
View.OnTouchListener
。當用戶執行具備觸摸事件的操作時,包括按下,釋放或在屏幕上的任何手勢操作(該控件所在的區域內),該方法被調用。 onCreateContextMenu()
- 來自
View.OnCreateContextMenuListener
。當上下文菜單被創建時(長按操作的結果),該方法被調用。請查閱 Menus 開發指南中關於上下文菜單的討論。
這些方法是它們各自接口的唯一成員。如果要定義這些方法來處理事件,就要在Activity中實現這些嵌套的接口,或者定義一個匿名類。然後,把接口實現的實例傳遞給對應的View.set...Listener()
方法。(例如,實現 OnClickListener 接口並作爲參數傳遞給 setOnClickListener() 調用)
下面的例子演示瞭如何爲按鈕註冊on-click監聽器。
// 創建OnClickListener的匿名實現
private OnClickListener mCorkyListener = new OnClickListener() {
public void onClick(View v) {
// 按鈕被點擊時執行
}
};
protected void onCreate(Bundle savedValues) {
...
// 從佈局文件中獲取按鈕
Button button = (Button)findViewById(R.id.corky);
// 使用上面的實現註冊onClick監聽器
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);
}
// 實現OnClickListener回調
public void onClick(View v) {
// 按鈕按下時執行
}
...
}
注意上面事例中的 onClick()
回調沒有返回值,但是有些其他事件監聽器方法必須返回一個布爾值。原因就在於對應的事件。以下是一些情況說明:
- 這個方法返回一個布爾值,用來指明你是否已經處理了這個事件且不會被進一步傳遞。也就是說,返回true表示你已經處理了這個事件,事件傳遞到此爲止;返回false表示你還沒有處理這個事件,這個事件會繼續傳遞給其他on-click監聽器。onLongClick()
- 這個方法返回一個布爾值,用來指明你是否已經處理了這個事件且不會被進一步傳遞。也就是說,返回true表示你已經處理了這個事件,事件傳遞到此爲止;返回false表示你還沒有處理這個事件,這個事件會繼續傳遞給其他on-key監聽器。onKey()
-
- 這個方法返回一個布爾值,用來指明你的監聽器是否已處理了這個事件。重要的是這個事件可以包含彼此跟隨的多重操作。因此,如果在接收到按下操作事件時返回false,就表明你不會處理這個事件並且也不會對這個事件的後續操作感興趣了。如此一來,這個事件裏的任何操作都不會調用這個方法了,比如手勢動作或最後鬆開操作事件。onTouch()
請記住硬件按鍵事件總是會傳遞給當前焦點所在的View。它們會從View層次結構的頂部開始向下派發,直到抵達合適的目的地。如果你的View(或View的子節點)當前獲得了焦點,那麼你可以通過 dispatchKeyEvent() 方法查看事件的派發路線。通過View捕獲按鍵事件的另外一種選擇是,你也可以在Activity裏通過
和 onKeyDown()
接收所有事件。onKeyUp()
另外,在爲你的應用考慮文本輸入時,請注意許多設備只有軟鍵盤輸入法。這些輸入法不要求是實體按鍵的;有些可能使用語音輸入,手寫輸入等等。儘管輸入法是以實體鍵盤外觀的形式展示給用戶的,通常它不會觸發 onKeyDown() 一類的事件。你絕對不要構建需要按下特定按鍵來控制的UI,除非你想要限制你的應用只能在具備物理鍵盤的設備上使用。不要依賴這些方法來驗證輸入,尤其是在用戶按下返回鍵時;而應該使用類似 IME_ACTION_DONE
的操作去提示輸入法你的應用期望的反應,這樣就能用一個有意義的方式改變它的UI了。不用去猜測軟鍵盤輸入法的工作方式,只需信任它能夠爲你的應用提供格式化的文本。
註解:Android首先會調用事件處理程序,然後再調用類定義的合適的默認處理程序。正因如此,如果這些事件監聽器返回true,那麼將會中止事件向其他事件監聽器的傳播,並且也會阻止View中的默認事件處理程序的回調調用。所以當你確定要中止這個事件時才返回true。
事件處理程序
如果你要在View的基礎上構建一個自定義組件,那麼你可以定義一些回調方法用作默認的事件處理程序。在關於 Custom Components
- 當行的按鍵按下事件產生時調用。onKeyDown(int, KeyEvent)
- 當按鍵鬆開事件產生時調用。onKeyUp(int, KeyEvent)
- 當軌跡球活動事件產生時調用。onTrackballEvent(MotionEvent)
- 當觸屏活動事件產生時調用。onTouchEvent(MotionEvent)
- 當View獲取或失去焦點時調用。onFocusChanged(boolean, int, Rect)
另外還有一些其他你需要注意的方法,它們不屬於View類,但是能夠直接影響你處理事件的方式。所以,在佈局中管理更復雜的事件時,可以考慮下這些方法:
- 這個方法能夠使Activity.dispatchTouchEvent(MotionEvent)
Activity
在觸屏事件傳遞到window之前攔截它們。
- 這個方法能夠使ViewGroup.onInterceptTouchEvent(MotionEvent)
ViewGroup
查看傳遞給子元素View的觸屏事件。ViewParent.requestDisallowInterceptTouchEvent(boolean) - 在父元素View中調用這個方法來表明不應該使用 onInterceptTouchEvent(MotionEvent) 攔截觸屏事件。
觸摸模式
當用戶使用方向鍵或軌跡球導航到用戶界面上時,很有必要把焦點給可交互的控件(比如按鈕)以便用戶可以看到哪些控件可以接受輸入。然而,如果這個設備支持觸摸並且用戶通過觸摸來與界面交互,那麼就沒必要高亮這些控件或把焦點交給特定的Viewe。因此,就有個名爲“觸摸模式”的交互模式了。
對於一個支持觸摸的設備,一旦用戶觸摸了屏幕,設備就會進入觸摸模式。從這點看來,只要View的 isFocusableInTouchMode() 返回true,那它就可以獲得焦點,比如文本編輯組件。其他可觸摸的View,比如按鈕,在被觸摸時不會獲得焦點;它們僅僅是在被按下時觸發它們的on-click監聽器。
一旦用戶點擊方向鍵或滾動軌跡球時,設備就會退出觸摸模式並尋找一個View來獲取焦點。現在用戶在不觸摸屏幕的情況下繼續與用戶界面交互。
觸摸模式的狀態是由整個系統維護的(所有的window和activity)。想要查詢當前設備的觸摸模式狀態,你可以調用 isInTouchMode() 來查看設備當前是否處於觸摸模式。
處理焦點
Android框架會處理常規焦點的移動來響應用戶的輸入。包含View被刪除或隱藏,甚至新的View變得可見時的焦點變化。View通過 isFocusable() 方法來表明它們是否願意獲得焦點。調用 setFocusable() 可以設置View能否獲得焦點。在觸摸模式下,你還可以通過 isFocusableInTouchMode() 查詢View是否能獲取焦點。同樣你可以使用
改變設置。setFocusableInTouchMode()
焦點的移動是基於在給定的方向上查找最近的鄰居的算法。在某些很少見的情況下,這種默認的算法可能無法匹配開發者期望的結果。在這種情況下,你可以在佈局文件中使用下面這些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>
通常,在這樣的垂直不居中,從第一個按鈕向上導航不會到達任何地方,同樣從第二個按鈕向下導航也不會到達任何地方。現在top按鈕已經定義了bottom作爲它的nextFocusUp(反之亦然),這樣導航焦點就會在兩個按鈕之間循環了。
如果你想要在UI中聲明View是可獲得焦點的(通常不這樣做),就需要在佈局聲明文件中給這個View添加android:focusable屬性並設置爲true。你也可以使用android:focusableInTouchMode聲明View在觸摸模式下是可獲得焦點的。
使用 requestFocus() 可以讓指定的View獲得焦點。
如同上面 Event Listeners 章節中討論的,使用 onFocusChange() 來監聽焦點事件(當View獲得或失去焦點時收到通知)。