输入事件概述

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

在 Android 上,有多种方法可以拦截用户与应用程序交互的事件。在考虑用户界面内的事件时,方法是捕获与用户交互的特定 View 对象的事件。View 类提供了这样做的方式。

在您用来组合布局的各种 View 类中,您可能会注意到几个看起来对 UI 事件有用的公共回调方法。当 View 对象上发生相应的操作时,这些方法由 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 注册一个 onclick 监听器。

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 表示您已处理事件,应在此停止;如果未处理事件或事件应继续传递到任何其他 onclick 监听器,则返回 false
  • onKey() - 此方法返回一个布尔值以指示您是否已使用事件并应停止进一步传递。也就是说,返回 true 表示您已处理事件,应在此停止;如果未处理事件或事件应继续传递到任何其他 onkey 监听器,则返回 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 类的一部分,但可以直接影响您处理事件的方式。因此,当在布局中管理更复杂的事件时,请考虑这些其他方法。

触摸模式

当用户使用方向键或轨迹球导航用户界面时,需要将焦点赋予可操作的项目(如按钮),以便用户可以看到哪些项目可以接受输入。但是,如果设备具有触摸功能,并且用户开始通过触摸来与界面交互,那么不再需要突出显示项目或将焦点赋予特定视图。因此,存在一种名为“触摸模式”的交互模式。

对于具有触摸功能的设备,一旦用户触摸屏幕,设备将进入触摸模式。从此时起,只有 isFocusableInTouchMode() 为 true 的视图才会获得焦点,例如文本编辑小部件。其他可触摸的视图(如按钮)在被触摸时不会获得焦点;它们只会在被按下时触发其点击监听器。

用户每次按下方向键或滚动轨迹球时,设备都会退出触摸模式,并找到一个视图来获得焦点。现在,用户可以继续与用户界面交互,而无需触摸屏幕。

触摸模式状态在整个系统(所有窗口和活动)中保持一致。要查询当前状态,您可以调用 isInTouchMode() 来查看设备当前是否处于触摸模式。

处理焦点

框架将处理响应用户输入的常规焦点移动。这包括在视图被移除或隐藏,或者新的视图变为可用时更改焦点。视图通过 isFocusable() 方法指示其接收焦点的意愿。要更改视图是否可以接收焦点,请调用 setFocusable()。在触摸模式下,您可以使用 isFocusableInTouchMode() 查询视图是否允许焦点。您可以使用 setFocusableInTouchMode() 更改此设置。

在运行 Android 9(API 级别 28)或更高版本的设备上,活动不会分配初始焦点。相反,您必须在需要时明确请求初始焦点。

焦点移动基于一种算法,该算法在给定方向上找到最近的邻居。在极少数情况下,默认算法可能与开发人员的预期行为不符。在这些情况下,您可以在布局文件中使用以下 XML 属性提供明确的覆盖:nextFocusDownnextFocusLeftnextFocusRightnextFocusUp。将这些属性之一添加到焦点离开的视图中。将属性的值定义为应将焦点赋予的视图的 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(反之亦然),导航焦点将在顶部和底部之间循环。

如果您希望在 UI 中将视图声明为可获得焦点(而它通常不可获得焦点),请在布局声明中将 android:focusable XML 属性添加到视图中。将值设置为 true。您还可以使用 android:focusableInTouchMode 将视图在触摸模式下声明为可获得焦点。

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

要监听焦点事件(在视图接收或失去焦点时收到通知),请使用 onFocusChange(),如 事件监听器 部分所述。