输入事件概览

试用 Compose 方式
Jetpack Compose 是推荐用于 Android 的界面工具包。了解如何在 Compose 中使用触控和输入功能。

在 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():此方法返回一个布尔值,指示您是否已处理完事件,以及是否应将其继续传递下去。换言之,返回 true 表示您已处理事件且事件应就此停止;如果您尚未处理事件且/或事件应继续传递给其他任何点击监听器,则返回 false。
  • onKey():此方法返回一个布尔值,指示您是否已处理完事件,以及是否应将其继续传递下去。换言之,返回 true 表示您已处理事件且事件应就此停止;如果您尚未处理事件且/或事件应继续传递给其他任何按键监听器,则返回 false。
  • onTouch():此方法返回一个布尔值,指示监听器是否处理完此事件。重要的是,此事件可以拥有多个分先后顺序的操作。因此,如果在收到 down 操作事件时返回 false,则表示您并未处理完此事件,而且对其后续操作也不感兴趣。因此,您无需执行事件内的任何其他操作,如手势或最终的 up 操作事件。

请注意,硬件按键事件始终传递给目前处于焦点的 View 对象。它们从 View 层次结构的顶层开始分派,然后向下,直至到达合适的目的地。如果您的 View 对象(或 View 对象的子项)目前处于焦点,那么您可以看到事件经由 dispatchKeyEvent() 方法的分派过程。除通过 View 对象捕获按键事件,您还可使用 onKeyDown()onKeyUp() 接收 Activity 内部的所有事件。

此外,考虑应用的文本输入时,请记住:许多设备只有软件输入法。此类方法无需基于按键;某些可能使用语音输入、手写等。尽管输入法提供类似键盘的界面,但其通常不会触发 onKeyDown() 系列的事件。除非您想将应用限制为只能在带有硬件键盘的设备上使用,否则,在设计界面时切勿要求必须通过特定按键进行控制。特别是,当用户按下返回键时,不要依赖这些方法验证输入;请改用 IME_ACTION_DONE 等操作让输入法知晓您的应用预计会作何反应,以便其通过一种有意义的方式更改其界面。不要推断软件输入法应如何工作,只要相信它能为应用提供已设置格式的文本即可。

注意:Android 会先调用事件处理程序,然后从类定义调用合适的默认处理程序。因此,如果从这些事件监听器返回 true,系统会停止将事件传播到其他事件监听器,还会阻止回调 View 对象中的默认事件处理程序。在返回 true 时请确保您需要终止事件。

事件处理程序

如果您从 View 构建自定义组件,则可定义几种回调方法,用作默认事件处理程序。在有关自定义 View 组件的文档中,您将了解一些用于事件处理的常见回调,包括:

还有一些其他方法值得您注意,尽管它们并非 View 类的一部分,但可能会直接影响所能采取的事件处理方式。因此,在管理布局内更复杂的事件时,不妨考虑使用以下其他方法:

触摸模式

当用户使用方向键或轨迹球导航界面时,须将焦点置于可操作项目上(如按钮),以便用户看到将接受输入的对象。但是,如果设备具有触摸功能且用户开始通过轻触界面与之互动,那么便不再需要突出显示项目或将焦点置于特定 View 对象上。因此,有一种互动模式称为“触摸模式”。

对于支持触摸功能的设备,当用户轻触屏幕时,设备会立即进入触摸模式。自此,只有 isFocusableInTouchMode() 为 true 的 View 对象才可聚焦,如文本编辑微件。对于其他可触摸的 View 对象(如按钮),您在轻触时不会获得焦点,按下时仅会触发点击监听器。

无论何时,只要用户点击方向键或滚动轨迹球,设备便会退出触摸模式,并找到一个 View 对象使其获得焦点。现在,用户可在不轻触屏幕的情况下继续与界面互动。

整个系统(所有窗口和 Activity)都将保持触摸模式状态。如要查询当前状态,您可以通过调用 isInTouchMode(),检查设备目前是否处于触摸模式。

处理焦点

框架会处理常规焦点移动,以响应用户输入。其中包括:在移除或隐藏 View 对象或在新 View 对象可用时更改焦点。View 对象通过 isFocusable() 方法指示是否愿意获得焦点。如要设置 View 对象能否获得焦点,请调用 setFocusable()。在触摸模式下,您可以使用 isFocusableInTouchMode() 查询 View 对象是否允许获得焦点。您可以使用 setFocusableInTouchMode() 对此进行更改。

在搭载 Android 9(API 级别 28)或更高版本的设备上,Activity 不会分配初始焦点。如有需要,您必须显式请求初始焦点。

焦点移动所使用的算法会查找指定方向上距离最近的元素。在极少数情况下,默认算法可能与开发者的期望行为不一致。在这些情况下,您可以用以下 XML 属性在布局文件中明确替换它们:nextFocusDownnextFocusLeftnextFocusRightnextFocusUp。将其中一个属性添加到失去焦点的 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()(如事件监听器部分中所述)。