page.title=输入事件 parent.title=用户界面 parent.link=index.html @jd:body
在 Android 系统中,从用户与应用的交互中截获事件的方法不止一种。如考虑截获用户界面内的事件,则可从用户与之交互的特定视图对象中捕获事件。 为此,View 类提供了多种方法。
在您将用于构建布局的各种 View 类中,您可能会注意到几种看起来适用于
UI 事件的公共回调方法。当该对象上发生相应的操作时,Android
框架会调用这些方法。例如,在触摸一个视图对象(例如“按钮”)时,对该对象调用
onTouchEvent()
方法。不过,为了截获此事件,您必须扩展 View 类并重写该方法。
然而,为了处理此类事件而扩展每个视图对象并不现实。
正因如此,View 类还包含一系列嵌套接口以及您可以更加轻松定义的回调。
这些接口称为事件侦听器,是您捕获用户与
UI 之间交互的票证。
尽管您通常会使用事件侦听器来侦听用户交互,但有时您确实需要扩展 View 类以构建自定义组件。 也许,您想扩展 {@link android.widget.Button} 类来丰富某些内容的样式。在这种情况下,您将能够使用该类的事件处理程序为类定义默认事件行为。
事件侦听器是 {@link android.view.View} 类中包含一个回调方法的接口。当用户与 UI 项目之间的交互触发已注册此视图的侦听器时,Android 框架将调用这些方法。
各事件侦听器接口包含的回调方法如下:
onClick()
onLongClick()
onFocusChange()
onKey()
onTouch()
onCreateContextMenu()
这些方法是其相应接口的唯一成员。要定义其中一个方法并处理事件,请在 Activity 中实现嵌套接口或将其定义为匿名类。然后,将实现的实例传递给相应的
View.set...Listener()
方法。
(例如,调用
{@link android.view.View#setOnClickListener(View.OnClickListener) setOnClickListener()}
并向其传递 {@link android.view.View.OnClickListener OnClickListener} 实现。)
以下示例显示了如何为按钮注册点击侦听器。
// 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()
回调没有返回值,但是其他某些事件侦听器方法必须返回布尔值。具体原因取决于事件。
对于这几个事件侦听器,必须返回布尔值的原因如下:
{@link android.view.View.OnLongClickListener#onLongClick(View) onLongClick()}
:
此方法返回一个布尔值,表示您是否已处理完事件,以及是否应该将它继续传下去。
也就是说,返回“true”表示您已经处理事件且事件应就此停止;如果您尚未处理事件和/或事件应该继续传递给其他任何点击侦听器,则返回“false”。
{@link android.view.View.OnKeyListener#onKey(View,int,KeyEvent) onKey()}
:
此方法返回一个布尔值,表示您是否已处理完事件,以及是否应该将它继续传下去。
也就是说,返回“true”表示您已经处理事件且事件应就此停止;如果您尚未处理事件和/或事件应该继续传递给其他任何按键侦听器,则返回“false”。
{@link android.view.View.OnTouchListener#onTouch(View,MotionEvent) onTouch()}
:
此方法返回一个布尔值,表示侦听器是否处理完此事件。重要的是,此事件可以拥有多个分先后顺序的操作。
因此,如果在收到关闭操作事件时返回“false”,则表示您并未处理完此事件,而且对其后续操作也不感兴趣。
因此,您无需执行事件内的任何其他操作,如手势或最终操作事件。
请记住,硬按键事件总是传递给目前处于焦点的视图对象。它们从视图
层次结构的顶部开始分派,然后向下,直至到达合适的目的地。如果您的视图对象(或视图对象的子项)目前具有焦点,那么您可以看到事件经由
{@link android.view.View#dispatchKeyEvent(KeyEvent)
dispatchKeyEvent()}
方法的分派过程。除了通过视图捕获按键事件,您还可以使用
{@link android.app.Activity#onKeyDown(int,KeyEvent) onKeyDown()}
和 {@link android.app.Activity#onKeyUp(int,KeyEvent) onKeyUp()}
接收 Activity 内部的所有事件。
此外,考虑应用的文本输入时,请记住:许多设备只有软件输入法。
此类方法无需基于按键;某些可能使用语音输入、手写等。尽管输入法提供了类似键盘的界面,但它通常不会触发
{@link android.app.Activity#onKeyDown(int,KeyEvent) onKeyDown()}
系列的事件。除非您要将应用限制为带有硬键盘的设备,否则,在设计 UI 时切勿要求必须通过特定按键进行控制。
特别是,当用户按下返回键时,不要依赖这些方法验证输入;请改用
{@link android.view.inputmethod.EditorInfo#IME_ACTION_DONE}
等操作让输入法知晓您的应用预计会作何反应,这样,可以通过一种有意义的方式更改其 UI。不要推断软件输入法应如何工作,只要相信它能够为应用提供已设置格式的文本即可。
注:Android 会先调用事件处理程序,然后从类定义调用合适的默认处理程序。 因此,从这些事件侦听器返回“true”会停止将事件传播到其他事件侦听器,还会阻止回调视图对象中的默认事件处理程序。 因此,在返回“true”时请确保您要终止事件。
如果您从视图构建自定义组件,则将能够定义几种用作默认事件处理程序的回调方法。在有关自定义组件的文档中,您将了解某些用于事件处理的常见回调,其中包括:
{@link android.view.View#onKeyDown}
:在发生新的按键事件时调用{@link android.view.View#onKeyUp}
:在发生按键弹起事件时调用{@link android.view.View#onTrackballEvent}
:在发生轨迹球运动事件时调用{@link android.view.View#onTouchEvent}
:在发生触摸屏运动事件时调用{@link android.view.View#onFocusChanged}
:在视图获得或失去焦点时调用还有一些其他方法值得您注意,尽管它们并非 View 类的一部分,但可能会直接影响所能采取的事件处理方式。 因此,在管理布局内更复杂的事件时,请考虑使用以下其他方法:
{@link android.app.Activity#dispatchTouchEvent(MotionEvent)
Activity.dispatchTouchEvent(MotionEvent)}
:此方法允许 {@link
android.app.Activity} 在分派给窗口之前截获所有触摸事件。{@link android.view.ViewGroup#onInterceptTouchEvent(MotionEvent)
ViewGroup.onInterceptTouchEvent(MotionEvent)}
:此方法允许 {@link
android.view.ViewGroup} 监视分派给子视图的事件。{@link android.view.ViewParent#requestDisallowInterceptTouchEvent(boolean)
ViewParent.requestDisallowInterceptTouchEvent(boolean)}
:
对父视图调用此方法表明不应使用 {@link
android.view.ViewGroup#onInterceptTouchEvent(MotionEvent)}
截获触摸事件。当用户使用方向键或轨迹球导航用户界面时,必须聚焦到可操作项目上(如按钮),以便用户看到将接受输入的对象。 但是,如果设备具有触摸功能且用户开始通过触摸界面与之交互,则不再需要突出显示项目或聚焦到特定视图对象上。 因此,有一种交互模式称为“触摸模式”。
对于支持触摸功能的设备,当用户触摸屏幕时,设备会立即进入触摸模式。 自此以后,只有 {@link android.view.View#isFocusableInTouchMode} 为“true”的视图才可聚焦,如文本编辑小工具。其他可触摸的视图(如按钮)在用户触摸时不会获得焦点;按下时它们只是触发点击侦听器。
无论何时,只要用户点击方向键或滚动轨迹球,设备就会退出触摸模式并找到一个视图使其获得焦点。 现在,用户可在不触摸屏幕的情况下继续与用户界面交互。
整个系统(所有窗口和 Activity)都将保持触摸模式状态。要查询当前状态,您可以调用 {@link android.view.View#isInTouchMode} 来检查设备目前是否处于触摸模式。
该框架将处理例行焦点移动来响应用户输入。其中包括在视图被删除或隐藏时或在新视图变得可用时更改焦点。
视图对象表示愿意通过
{@link android.view.View#isFocusable()}
方法获得焦点。要设置视图能否获得焦点,请调用
{@link android.view.View#setFocusable(boolean) setFocusable()}
。在触摸模式中,您可以使用
{@link android.view.View#isFocusableInTouchMode()}
查询视图是否允许聚焦,而且可以使用
{@link android.view.View#setFocusableInTouchMode(boolean) setFocusableInTouchMode()}
对此进行更改。
焦点移动所使用的算法会查找指定方向上距离最近的元素。 在极少数情况下,默认算法可能与开发者的期望行为不一致。 在这些情况下,您可以在布局文件中显式重写以下 XML 属性: nextFocusDown、 nextFocusLeft、 nextFocusRight和 nextFocusUp。将其中一个属性添加到失去焦点的视图。 将属性的值定义为应该聚焦的视图的 ID。例如:
<LinearLayout android:orientation="vertical" ... > <Button android:id="@+id/top" android:nextFocusUp="@+id/bottom" ... /> <Button android:id="@+id/bottom" android:nextFocusDown="@+id/top" ... /> </LinearLayout>
一般来说,在此垂直布局中,从第一个按钮向上导航或从第二个按钮向下导航,焦点都不会移到任何其他位置。 现在,顶部按钮已将底部按钮定义为 nextFocusUp (反之亦然),因而导航焦点将自上而下和自下而上循环往复。
若要将一个视图声明为在 UI 中可聚焦(传统上并非如此),请在布局声明中将 android:focusable
XML 属性添加到该视图。将值设置为
true。此外,您还可以使用
android:focusableInTouchMode
将 Vew 声明为在触摸模式下可聚焦。
要请求要获得焦点的特定视图,请调用 {@link android.view.View#requestFocus()}
。
要侦听焦点事件(视图获得或失去焦点时会收到通知),请使用
{@link android.view.View.OnFocusChangeListener#onFocusChange(View,boolean) onFocusChange()}
,
如上文的事件侦听器部分中所述。