在 Android 中,从用户与应用的互动中截获事件的方法不止一种。对于界面内的事件,可以从用户与之互动的特定 View 对象中捕获事件。为此,View 类提供了多种方法。
在用于构建布局的各种 View 类中,您可能会注意到几种看起来适用于界面事件的公开回调方法。当该对象上发生相应的操作时,Android 框架会调用这些方法。例如,在用户轻触一个 View 对象(例如,按钮)时,系统对该对象调用 onTouchEvent() 方法。不过,为截获此事件,您必须扩展 View 类并替换该方法。然而,为处理此类事件而扩展每个 View 对象并不现实。正因如此,View 类还包含一系列嵌套接口以及您可以更轻松定义的回调。这些接口称为事件监听器,是您捕获用户与界面之间互动的票证。
尽管您使用事件监听器监听用户互动的频率会更高,但有时您确实需要通过扩展 View 类来构建自定义组件。也许,您想通过扩展 Button 类来满足更复杂的需求。在此情况下,您将能够使用该类的事件处理程序为类定义默认事件行为。
事件监听器
事件监听器是 View 类中包含一个回调方法的接口。当用户与界面项目之间的互动触发已注册监听器的 View 对象时,Android 框架将调用这些方法。
事件监听器接口中包含以下回调方法:
- onClick()
- 在 View.OnClickListener中。当用户轻触项目(在触摸模式下),或者使用导航键或轨迹球聚焦于项目,然后按适用的“Enter”键或按下轨迹球时,系统会调用此方法。
- onLongClick()
- 在 View.OnLongClickListener中。当用户轻触并按住项目(在触摸模式下)时,或者使用导航键或轨迹球聚焦于项目,然后按住适用的“Enter”键或按住轨迹球(持续一秒钟)时,系统会调用此方法。
- onFocusChange()
- 在 View.OnFocusChangeListener中。当用户使用导航键或轨迹球转到或离开项目时,系统会调用此方法。
- onKey()
- 在 View.OnKeyListener中。当用户聚焦于项目并按下或释放设备上的硬件按键时,系统会调用此方法。
- onTouch()
- 在 View.OnTouchListener中。当用户执行可视为触摸事件的操作时,包括按下、释放或屏幕上的任何移动手势(在项目边界内),系统会调用此方法。
- onCreateContextMenu()
- 在 View.OnCreateContextMenuListener中。当(因用户持续“长按”而)生成上下文菜单时,系统会调用此方法。请参阅菜单开发者指南中有关上下文菜单的介绍。
这些方法是其相应接口的唯一成员。如要定义其中一个方法并处理事件,请在 Activity 中实现嵌套接口或将其定义为匿名类。然后,将实现的实例传递给相应的 View.set...Listener() 方法。(例如,调用 setOnClickListener()OnClickListener 的实现。)
以下示例展示了如何为按钮注册点击监听器。
Kotlin
protected void onCreate(savedValues: Bundle) { ... val button: Button = findViewById(R.id.corky) // Register the onClick listener with the implementation above button.setOnClickListener { view -> // do something when the button is clicked } ... }
Java
// Create an anonymous implementation of OnClickListener private OnClickListener corkyListener = 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(corkyListener); ... }
您可能还会发现,将 OnClickListener 作为 Activity 的一部分来实现更为方便。这样可避免加载额外的类和分配对象。例如:
Kotlin
class ExampleActivity : Activity(), OnClickListener { protected fun onCreate(savedValues: Bundle) { val button: Button = findViewById(R.id.corky) button.setOnClickListener(this) } // Implement the OnClickListener callback fun onClick(v: View) { // do something when the button is clicked } }
Java
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()
- onKey()
- onTouch()
请注意,硬件按键事件始终传递给目前处于焦点的 View 对象。它们从 View 层次结构的顶层开始分派,然后向下,直至到达合适的目的地。如果您的 View 对象(或 View 对象的子项)目前处于焦点,那么您可以看到事件经由 dispatchKeyEvent()onKeyDown()onKeyUp()
此外,考虑应用的文本输入时,请记住:许多设备只有软件输入法。此类方法无需基于按键;某些可能使用语音输入、手写等。尽管输入法提供类似键盘的界面,但其通常不会触发 onKeyDown()IME_ACTION_DONE 等操作让输入法知晓您的应用预计会作何反应,以便其通过一种有意义的方式更改其界面。不要推断软件输入法应如何工作,只要相信它能为应用提供已设置格式的文本即可。
注意:Android 会先调用事件处理程序,然后从类定义调用合适的默认处理程序。因此,如果从这些事件监听器返回 true,系统会停止将事件传播到其他事件监听器,还会阻止回调 View 对象中的默认事件处理程序。在返回 true 时请确保您需要终止事件。
事件处理程序
如果您从 View 构建自定义组件,则可定义几种回调方法,用作默认事件处理程序。在有关自定义 View 组件的文档中,您将了解一些用于事件处理的常见回调,包括:
- onKeyDown(int, KeyEvent)
- onKeyUp(int, KeyEvent)
- onTrackballEvent(MotionEvent)
- onTouchEvent(MotionEvent)
- onFocusChanged(boolean, int, Rect)
还有一些其他方法值得您注意,尽管它们并非 View 类的一部分,但可能会直接影响所能采取的事件处理方式。因此,在管理布局内更复杂的事件时,不妨考虑使用以下其他方法:
- Activity.dispatchTouchEvent(MotionEvent)- Activity在所有触摸事件分派给窗口之前截获它们。
- ViewGroup.onInterceptTouchEvent(MotionEvent)- ViewGroup监视分派给子级 View 的事件。
- ViewParent.requestDisallowInterceptTouchEvent(boolean)- onInterceptTouchEvent(MotionEvent)
触摸模式
当用户使用方向键或轨迹球导航界面时,须将焦点置于可操作项目上(如按钮),以便用户看到将接受输入的对象。但是,如果设备具有触摸功能且用户开始通过轻触界面与之互动,那么便不再需要突出显示项目或将焦点置于特定 View 对象上。因此,有一种互动模式称为“触摸模式”。
对于支持触摸功能的设备,当用户轻触屏幕时,设备会立即进入触摸模式。自此,只有 isFocusableInTouchMode() 为 true 的 View 对象才可聚焦,如文本编辑微件。对于其他可触摸的 View 对象(如按钮),您在轻触时不会获得焦点,按下时仅会触发点击监听器。
无论何时,只要用户点击方向键或滚动轨迹球,设备便会退出触摸模式,并找到一个 View 对象使其获得焦点。现在,用户可在不轻触屏幕的情况下继续与界面互动。
整个系统(所有窗口和 Activity)都将保持触摸模式状态。如要查询当前状态,您可以通过调用 isInTouchMode(),检查设备目前是否处于触摸模式。
处理焦点
框架会处理常规焦点移动,以响应用户输入。其中包括:在移除或隐藏 View 对象或在新 View 对象可用时更改焦点。View 对象通过 isFocusable()setFocusable()isFocusableInTouchMode()setFocusableInTouchMode()
在搭载 Android 9(API 级别 28)或更高版本的设备上,Activity 不会分配初始焦点。如有需要,您必须显式请求初始焦点。
焦点移动所使用的算法会查找指定方向上距离最近的元素。在极少数情况下,默认算法可能与开发者的期望行为不一致。在这些情况下,您可以用以下 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>
一般来说,在此垂直布局中,无论是从第一个按钮向上导航还是从第二个按钮向下导航,焦点都不会移到任何其他位置。现在,顶部按钮已将底部按钮定义为 nextFocusUp(反之亦然),因而导航焦点将按自上而下和自下而上的顺序循环往复。
若要将某个 View 对象声明为界面中的可聚焦对象(通常情况下不是),请在布局声明中将 android:focusable XML 属性添加到该 View 对象。将值设为 true。此外,您还可以使用 android:focusableInTouchMode,声明 View 对象在触摸模式下可聚焦。
如需请求让特定 View 对象获得焦点,请调用 requestFocus()
如需监听焦点事件(在 View 对象获得或失去焦点时收到通知),请使用 onFocusChange()
 
  