The Android Developer Challenge is back! Submit your idea before December 2.

输入事件概览

在 Android 中,从用户与应用的交互中截获事件的方法不止一种。如考虑截获界面内的事件,则可从用户与之交互的特定 View 对象中捕获事件。为此,View 类提供了多种方法。

在用于构建布局的各种 View 类中,您可能会注意到几种看起来适用于界面事件的公开回调方法。当该对象上发生相应的操作时,Android 框架会调用这些方法。例如,在轻触一个 View 对象(例如“按钮”)时,系统对该对象调用 onTouchEvent() 方法。不过,为截获此事件,您必须扩展 View 类并重写该方法。然而,为处理此类事件而扩展每个 View 对象并不现实。正因如此,View 类还包含一系列嵌套接口以及您可以更轻松定义的回调。这些接口称为事件侦听器,是您捕获用户与界面之间交互的票证。

尽管您通常会使用事件侦听器来侦听用户交互,但有时您确实需通过扩展 View 类来构建自定义组件。也许,您想通过扩展 Button 类来丰富某些内容的样式。在此情况下,您将能够使用该类的事件处理程序为类定义默认事件行为。

事件侦听器

事件侦听器是 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():此方法返回一个布尔值,表示侦听器是否处理完此事件。重要的是,此事件可以拥有多个分先后顺序的操作。因此,如果在收到关闭操作事件时返回 false,则表示您并未处理完此事件,而且对其后续操作也不感兴趣。因此,您无需执行事件内的任何其他操作,如手势或最终操作事件。

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

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

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

事件处理程序

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

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

轻触模式

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

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

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

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

处理焦点

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

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

焦点移动所使用的算法会查找指定方向上距离最近的元素。在极少数情况下,默认算法可能与开发者的期望行为不一致。在这些情况下,您可以在布局文件中显式重写以下 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(反之亦然),因而导航焦点将通过自上而下和自下而上循环往复。

若要将视图声明为在界面中可聚焦(通常并非如此),请在布局声明中将 android:focusable XML 属性添加到该视图。将值设置为 true。此外,您还可使用 android:focusableInTouchMode,将视图声明为在轻触模式下可聚焦。

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

如要侦听焦点事件(视图获得或失去焦点时会收到通知),请使用 onFocusChange()(如事件侦听器部分中所述)。