处理用户界面事件 Handling UI Events
在 Android 上,不止一个途径来侦听用户和应用程序之间交互的事件。对于用户界面里的事件,侦听方法就是从与用户交互的特定视图对象截获这些事件。视图类提供了相应的手段。
在各种用来组建布局的视图类里面,你可能会注意到一些公共的回调方法看起来对用户界面事件有用。这些方法在该对象的相关动作发生时被 Android 框架调用。比如,当一个视图(如一个按钮)被触摸时,该对象上的 onTouchEvent() 方法会被调用。不过,为了侦听这个事件,你必须扩展这个类并重写该方法。很明显,扩展每个你想使用的视图对象(只是处理一个事件)是荒唐的。这就是为什么视图类也包含了一个嵌套接口的集合,这些接口含有实现起来简单得多的回调函数。这些接口叫做事件侦听器 event listeners ,是用来截获用户和你的界面交互动作的“门票”。
当你更为普遍的使用事件侦听器来侦听用户动作时,总有那么一次你可能得为了创建一个自定义组件而扩展一个视图类。也许你想扩展按钮 Button 类来使某些事更花哨。在这种情况下,你将能够使事件处理器 event handlers 类来为你的类定义缺省事件行为。
事件侦听器是视图 View 类的接口,包含一个单独的回调方法。这些方法将在视图中注册的侦听器被用户界面操作触发时由 Android 框架调用。下面这些回调方法被包含在事件侦听器接口中:
onClick()
包含于 View.OnClickListener 。当用户触摸这个 item (在触摸模式下),或者通过浏览键或跟踪球聚焦在这个 item 上,然后按下“确认”键或者按下跟踪球时被调用。
onLongClick()
包含于 View.OnLongClickListener 。当用户触摸并控制住这个 item (在触摸模式下),或者通过浏览键或跟踪球聚焦在这个 item 上,然后保持按下“确认”键或者按下跟踪球(一秒钟)时被调用。
onFocusChange()
包含于 View.OnFocusChangeListener 。当用户使用浏览键或跟踪球浏览进入或离开这个 item 时被调用。
onKey()
包含于 View.OnKeyListener 。当用户聚焦在这个 item 上并按下或释放设备上的一个按键时被调用。
onTouch()
包含于 View.OnTouchListener 。当用户执行的动作被当做一个触摸事件时被调用,包括按下,释放,或者屏幕上任何的移动手势(在这个 item 的边界内)。
onCreateContextMenu()
包含于 View.OnCreateContextMenuListener 。当正在创建一个上下文菜单的时候被调用(作为持续的“长点击”动作的结果)。参阅创建菜单 Creating Menus 章节以获取更多信息。
这些方法是它们相应接口的唯一“住户”。要定义这些方法并处理你的事件,在你的活动中实现这个嵌套接口或定义它为一个匿名类。然后,传递你的实现的一个实例给各自的 View.set...Listener() 方法。(比如,调用 setOnClickListener() 并传递给它你的 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 作为活动的一部分来实现会便利的多。这将避免额外的类加载和对象分配。比如:
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() 回调没有返回值,但是一些其它事件侦听器必须返回一个布尔值。原因和事件相关。对于其中一些,原因如下:
· onLongClick() – 返回一个布尔值来指示你是否已经消费了这个事件而不应该再进一步处理它。也就是说,返回 true 表示你已经处理了这个事件而且到此为止;返回 false 表示你还没有处理它和 / 或这个事件应该继续交给其他 on-click 侦听器。
· onKey() – 返回一个布尔值来指示你是否已经消费了这个事件而不应该再进一步处理它。也就是说,返回 true 表示你已经处理了这个事件而且到此为止;返回 false 表示你还没有处理它和 / 或这个事件应该继续交给其他 on-key 侦听器。
· onTouch() - 返回一个布尔值来指示你的侦听器是否已经消费了这个事件。重要的是这个事件可以有多个彼此跟随的动作。因此,如果当接收到向下动作事件时你返回 false ,那表明你还没有消费这个事件而且对后续动作也不感兴趣。那么,你将不会被该事件中的其他动作调用,比如手势或最后出现向上动作事件。
记住按键事件总是递交给当前焦点所在的视图。它们从视图层次的顶层开始被分发,然后依次向下,直到到达恰当的目标。如果你的视图(或者一个子视图)当前拥有焦点,那么你可以看到事件经由 dispatchKeyEvent() 方法分发。除了从你的视图截获按键事件,还有一个可选方案,你还可以在你的活动中使用 onKeyDown() and onKeyUp() 来接收所有的事件。
注意 : Android 将首先调用事件处理器,其次是类定义中合适的缺省处理器。这样,从这些事情侦听器中返回 true 将停止事件向其它事件侦听器传播并且也会阻塞视图中的缺事件处理器的回调函数。因此当你返回 true 时确认你希望终止这个事件。
如果你从视图创建一个自定义组件,那么你将能够定义一些回调方法被用作缺省的事件处理器。在创建自定义组件Building Custom Components 的文档中,你将学习到一些用作事件处理的通用回调函数,包括:
· onKeyDown(int, KeyEvent) - 当一个新的按键事件发生时被调用。
· onKeyUp(int, KeyEvent) - 当一个向上键事件发生时被调用。
· onTrackballEvent(MotionEvent) - 当一个跟踪球运动事件发生时被调用。
· onTouchEvent(MotionEvent) - 当一个触摸屏移动事件发生时调用。
· onFocusChanged(boolean, int, Rect) - 当视图获得或者丢失焦点时被调用。
你应该知道还有一些其它方法,并不属于视图类的一部分,但可以直接影响你处理事件的方式。所以,当在一个布局里管理更复杂的事件时,考虑一下这些方法:
· Activity.dispatchTouchEvent(MotionEvent) - 这允许你的活动可以在分发给窗口之前捕获所有的触摸事件。
· ViewGroup.onInterceptTouchEvent(MotionEvent) - 这允许一个视图组ViewGroup 在分发给子视图时观察这些事件。 ViewParent.requestDisallowInterceptTouchEvent(boolean) - 在一个父视图之上调用这个方法来表示它不应该通过 onInterceptTouchEvent(MotionEvent) 来捕获触摸事件。
触摸模式Touch Mode
当 用户使用方向键或跟踪球浏览用户界面时,有必要给用户可操作的item(比如按钮)设置焦点,这样用户可以知道哪个item将接受输入。不过,如果这个设 备有触摸功能,而且用户通过触摸来和界面交互,那么就没必要高亮items,或者设定焦点到一个特定的视图。这样,就有一个交互模式 叫“触摸模式”。
对于一个具备触摸功能的设备,一旦用户触摸屏幕,设备将进入触摸模式。自此以后,只有isFocusableInTouchMode() 为 真的视图才可以被聚焦,比如文本编辑部件。其他可触摸视图,如按钮,在被触摸时将不会接受焦点;它们将只是在被按下时简单的触发on-click侦听器。 任何时候用户按下方向键或滚动跟踪球,这个设备将退出触摸模式,然后找一个视图来接受焦点,用户也许不会通过触摸屏幕的方式来恢复界面交互。
触摸模式状态的维护贯穿整个系统(所有窗口和活动)。为了查询当前状态,你可以调用isInTouchMode() 来查看这个设备当前是否处于触摸模式中。
处理焦点Handling Focus
框架将根据用户输入处理常规的焦点移动。这包含当视图删除或隐藏,或者新视图出现时改变焦点。视图通过isFocusable() 方法表明它们想获取焦点的意愿。要改变视图是否可以接受焦点,可以调用 setFocusable() 。在触摸模式中,你可以通过isFocusableInTouchMode() 查询一个视图是否允许接受焦点。你可以通过 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 (反之亦然),浏览焦点将从上到下和从下到上循环移动。
如果你希望在用户界面中声明一个可聚焦的视图(通常不是这样),可以在你的布局定义中,为这个视图增加 android:focusable XML 属性。把它的值设置成 true 。你还可以通过android:focusableInTouchMode 在触摸模式下声明一个视图为可聚焦。
想请求一个接受焦点的特定视图,调用 requestFocus() 。
要侦听焦点事件(当一个视图获得或者失去焦点时被通知到),使用 onFocusChange() ,如上面事件侦听器Event Listeners 一章所描述的那样。