输入事件概览

尝试 Compose 方式
Jetpack Compose 是 Android 推荐的 UI 工具包。了解如何在 Compose 中使用触摸和输入。

在 Android 中,有多种方式可以拦截用户与应用互动产生的事件。考虑用户界面中的事件时,方法是捕获用户与之互动的特定 View 对象的事件。View 类提供了实现此功能的方法。

在用于组合布局的各种 View 类中,您可能会注意到一些看似对 UI 事件有用的公共回调方法。当在该对象上发生相应操作时,Android 框架会调用这些方法。例如,当一个 View(如 Button)被触摸时,就会在该对象上调用 onTouchEvent() 方法。但是,为了拦截此操作,您必须扩展该类并覆盖该方法。然而,为了处理此类事件而扩展每个 View 对象是不切实际的。这就是 View 类还包含一组具有更容易定义的嵌套接口,这些接口带有回调。这些接口被称为事件监听器,是捕获用户与您的 UI 互动的关键。

虽然您更常用事件监听器来监听用户互动,但在某些时候您可能确实想要扩展一个 View 类,以便构建一个自定义组件。也许您想扩展 Button 类来创建一些更花哨的东西。在这种情况下,您可以使用类的事件处理程序为您的类定义默认事件行为。

事件监听器

事件监听器是 View 类中的一个接口,其中包含一个回调方法。当监听器已注册到的 View 被用户与 UI 中的项目互动触发时,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 实现实例)。

下面的示例展示了如何为 Button 注册点击监听器。

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() - 返回布尔值以指示您的监听器是否消耗此事件。重要的一点是,此事件可以包含多个相互跟随的操作。因此,如果在收到按下操作事件时返回 false,则表示您尚未消耗该事件,并且对该事件的后续操作也不感兴趣。因此,您将不会收到事件中任何其他操作(如手指手势或最终的抬起操作事件)的调用。

请记住,硬件按键事件始终传递给当前拥有焦点的 View。它们从 View 层次结构的顶部开始分派,然后向下传递,直到到达适当的目标。如果您的 View(或其子 View)当前拥有焦点,那么您将看到事件通过 dispatchKeyEvent() 方法传递。作为通过 View 捕获按键事件的替代方案,您还可以通过 onKeyDown()onKeyUp() 在 Activity 中接收所有事件。

此外,在考虑应用的文本输入时,请记住许多设备仅有软件输入法。此类输入法不一定是基于按键的;有些可能使用语音输入、手写等。即使输入法呈现了类似键盘的界面,通常也不会触发 onKeyDown() 系列事件。除非您想将应用限制在带有硬件键盘的设备上,否则切勿构建需要特定按键按下才能控制的 UI。特别是,当用户按下回车键时,不要依赖这些方法来验证输入;相反,请使用 IME_ACTION_DONE 等操作来向输入法发出信号,说明您的应用期望如何响应,以便输入法能够以有意义的方式更改其 UI。避免对软件输入法的工作方式进行假设,只需相信它会向您的应用提供已经格式化好的文本即可。

注意: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>

通常,在此垂直布局中,从第一个 Button 向上导航不会转到任何地方,从第二个 Button 向下导航也不会。现在,顶部 Button 已将底部 Button 定义为 nextFocusUp(反之亦然),导航焦点将从上到下和从下到上循环。

如果您想在 UI 中声明某个 View 可获取焦点(尽管其传统上不可获取),请在布局声明中向该 View 添加 android:focusable XML 属性。将其值设置为 true。您还可以使用 android:focusableInTouchMode 在触摸模式下声明 View 可获取焦点。

要请求特定 View 获得焦点,请调用 requestFocus()

要监听焦点事件(当 View 获得或失去焦点时收到通知),请使用 onFocusChange(),如事件监听器部分所述。