Handling UI Events
在Android里, 有不只一种方式可以截获用户与你的应用程序交互的事件. 在你的界面上处理事件时,你需要捕获用户与某个View实例交互时所产生的事件.View类支持这种做法.
在你布局时需要使用的很多View类型里,你应该注意到一些对处理UI事件很有用的公有回调方法. 当特定操作发生在一些指定对象的时候,Android系统的frmaework程序就会调用这些回调方法. 比如,当我们touch某个按钮的时候,它的onTouchEvent()方法就会被调用. 但是, 为了拦截这一过程,你必须继承某个类并且重写它的onTouchEvent()方法. 显然,这样处理所有View对象上触发的事件是不切实际的.这就是为什么View类也包含了一组嵌套接口,它允许我们更加容易的定义自己想要的行为. 这些接口,叫做事件监听器,是你捕获用户与应用程序界面交互的关键所在.
在你总是使用事件监听器来监听用户行为的同时,哪一天你可能确实需要继承某一个View类以建立一个自定义组件.这种情况下,你可以使用类里所定义的事件处理器来为你的类定义默认的事件处理行为.
1.事件监听器
一个事件监听器就是View类里一个包含单个回调方法的接口. 事件监听器需要被注册到某个View上,当用户在界面上的操作触发这个事件时,Android系统的frmaework程序就会调用这个监听里的回调方法.
包含在事件监听器接口的回调方法有以下这些:
(1)onClick()----来自于 View.OnClickListener
当用户touch the item (when in touch mode), 使用导航键将焦点置于此项并按下ENTER键 ,或者使用trackball选中此项并按下trackball时,这个方法会被调用.
(2)onLongClick()----来自于 View.OnLongClickListener
This is called when the user either touches and holds the item (when in touch mode), or focuses upon the item with the navigation-keys or trackball and presses and holds the suitable "enter" key or presses and holds down on the trackball (for one second).
(3)onFocusChange()----View.OnFocusChangeListener
当用户使用navigation-key或者trackball,将焦点切换到或者离开某个选项时,这个方法被调用.
(4)onKey()----来自于 View.OnKeyListener.
当用户在焦点项上,按下或者释放设备上某个键时,这个方法会被调用.
(5)onTouch()----来自于 View.OnTouchListener
This is called when the user performs an action qualified as a touch event, including a press, a release, or any movement gesture on the screen (within the bounds of the item).
参阅android.view.MotionEvent类的ACTION_CANCEL,ACTION_DOWN,ACTION_MOVE,ACTION_UP等字段.
(6)onCreateContextMenu()----来自于 View.OnCreateContextMenuListener.
当创建一个Context Menu的时候,这个方法会被调用.参阅Creating Menus文档的context menu部分.
上述这些方法是它们各自接口的唯一内容.为了定义它们中的任何一个来处理你的事件,需要使你的Activity实现某个接口,或者在某个匿名内部类里定义接口的默认行为.然后,传递你的实现方式的一个实例到相应的View.set...Listener()方法里去.下面是创建一个匿名实现类的例子代码段:
// 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);
...
}
当然还有另一种更方便的选择,那就是把实现On...Listener作为你的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则说明你没有处理事件,或者这个事件应该继续被传递到其它的某个监听上.
(2)onKey()方法.
同上.
(3)onTouch()方法.
同样的,它的返回值指示你的listener是否处理了此事件.重要的是,这一事件可以有多个行为,互相追随(参阅android.view.MotionEvent类的ACTION_CANCEL,ACTION_DOWN,ACTION_MOVE,ACTION_UP等)。所以,如果你在down行为的处理中返回了false,表明你还没有处理事件并且对此事件里的其它行为也不感兴趣.因此,你也不会收到此事件里其它任何行为的通知,比如用户做的一个fingure手势或者最终的up行为时.
要记住,关键事件总是被分发到当前的焦点View.它们被派从顶部的View层次,然后下来,直到它们达到适当的终点。如果焦点在你的View(或者你的View的子元素)上,你可以看见被触发的事件经过dispatchKeyEvent()方法.对于捕获经过你的View的关键事件,还有一个替代方案,就是在你的Activity里使用onKeyDown() 和 onKeyUp()方法.
注意:Android会首先调用事件处理器(View.on...Listener),然后才会从类里寻找合适的事件处理定义(View.onKeyDown等等[Activity.onKeyDown?????]).同样的,从事件监听器里返回true将会打断到其它监听器的传播,并且会阻塞View里默认事件处理器的回调.所以,当你从事件监听器里返回true的时候,一定要小心,确认自己真的想要终止事件的处理和传播.
2.事件处理程序
如果你正在使用View类创建自定义组件,你可以定义几个回调方法作为默认的事件处理程序.在"Building Custom Components"的文档里,可以学习到使用一些回调方法来处理事件,包括:
* onKeyDown(int, KeyEvent) - Called when a new key event occurs.
* onKeyUp(int, KeyEvent) - Called when a key up event occurs.
* onTrackballEvent(MotionEvent) - Called when a trackball motion event occurs.
* onTouchEvent(MotionEvent) - Called when a touch screen motion event occurs.
* onFocusChanged(boolean, int, Rect) - Called when the view gains or loses focus.
还有一些我们应该注意的方法, 它们不属于View类, 却影响到我们处理事件的方式.因此,在布局内部处理复杂的事件时,请考虑到它们:
* Activity.dispatchTouchEvent(MotionEvent) - This allows your Activity to intercept all touch events before they are dispatched to the window.
* ViewGroup.onInterceptTouchEvent(MotionEvent) - This allows a ViewGroup to watch events as they are dispatched to child Views.
* ViewParent.requestDisallowInterceptTouchEvent(boolean) - Call this upon a parent View to indicate that it should not intercept touch events with onInterceptTouchEvent(MotionEvent).
3.触摸模式
当用户正通过方向键或者trackball浏览界面时,我们需要把焦点设置到那些可操作的item上面,以使用户明白哪里处理它的请求. 但是,如果某个设备具备触摸响应能力,用户会直接通过触摸来与应用程序交互,不再需要我们高亮显示某个item或者为某个view设置焦点了. 所以,我们称这种交互模式为触摸模式.
对于一个有触摸功能的设备来说,一旦用户触摸了屏幕,此设备立即进入触摸模式. 从这点开始,只有那些isFocusableInTouchMode()为true的View可以设置焦点,比如文本编辑widget.其它那些可触摸的View,比如按钮,被触摸时也不会获得焦点;它们只是在自己被pressed的时候简单地激活on-click监听器.
任何时候,一个用户点击一个方向键或用轨迹球滚动,该设备将退出触摸模式,并把焦点设置到某个View上.现在,用户可以恢复与没有触摸屏的用户界面交互。
触摸模式的状态保持在整个系统(所有窗口和活动)。要查询当前的状态,你可以调用isInTouchMode(),看看是否该设备目前在触摸模式。
4.处理焦点
该框架将处理响应用户输入过程中的例行焦点移动.包括在View被删除或者隐藏` 新建View时改变焦点.Views通过isFocusable()方法表达它们获取焦点的意愿.如果要改变某个View能否接受焦点,调用setFocusable()方法.在触摸模式下,你可以使用isFocusableInTouchMode()方法查询某个View是否被允许设置为焦点.同样地,也有办法改变这个属性,调用setFocusableInTouchMode()方法就行.
确定下一个焦点的算法是寻找指定方向上的最近相邻元素. 很少见的时候, 默认算法的结果不符合开发人员的预期行为,这种情况下,可以通过下面几个XML属性明确指定焦点移动的结果:nextFocusDown, nextFocusLeft, nextFocusRight, and nextFocusUp. 添加这些属性中的一个到焦点将要离开的View定义里.把你想要下
一个focus的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>
一般情况下,在这个布局里,在第一个按钮上按上方向键哪儿都不会去,在第二个按钮上按下方向键也一样.现在通过nextFocusUp定义了它的上方向键聚焦对象为bottom(反过来也一样),所以焦点会上下循环移动.
如果想在你的界面里声明一个可聚焦的View(当一般情况它不会被聚焦的时候), 添加android:focusable属性到这个View定义里去.把值设置为true. 你也可以使用android:focusableInTouchMode属性在触摸模式下声明一个可聚焦的View.
需要设置焦点到某个View时,调用requestFocus()方法.
为了监听聚焦事件(当一个View得到或者失去焦点的时候收到通知),使用onFocusChange()方法,就像上面"事件监听器"部分所讨论的那样.