在用于组成布局的的各种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对象接受或失去焦点时会发出通知)。