大屏幕上的输入兼容性

在大屏设备上,用户会更频繁地使用键盘、鼠标、触控板、触控笔或游戏手柄与应用互动。如需让应用能够接受来自外部设备的输入,请执行以下操作:

  • 测试基本的键盘操作支持,例如通过 Tab 键和箭头键进行键盘导航、按 Enter 键确认文字输入以及在媒体应用中使用空格键执行播放/暂停操作
  • 在适用的情况下,添加标准的键盘快捷键;例如,使用 Ctrl + Z 执行撤消操作,使用 Ctrl + S 执行保存操作
  • 测试基本的鼠标交互,交互方式包括通过右键点击打开上下文菜单、悬停鼠标时图标的变化,以及自定义视图上的鼠标滚轮或触控板滚动事件等等
  • 测试应用专用的输入设备,如绘图应用用到的触控笔、游戏用到的游戏控制器,以及音乐应用用到的 MIDI 控制器
  • 考虑支持高级输入方式,让您的应用在桌面环境中脱颖而出;例如,支持将触控板用作 DJ 应用的唱片平滑转换器、让游戏支持鼠标捕捉,以及支持为以键盘为主要输入方式的用户设置大量键盘快捷键

键盘

您的应用响应键盘输入的方式有助于打造良好的大屏幕体验。有三种类型的键盘输入:导航按键快捷键

键盘导航很少在以触摸为主要输入方式的应用中实现,但如果用户在使用应用时将手放在键盘上,他们就希望使用键盘导航。此外,对于需要在手机、平板电脑、可折叠设备和桌面设备上使用无障碍功能的用户来说,键盘导航也可能是必不可少的。

许多应用需要的只是简单的箭头键和 Tab 键导航,这种导航主要由 Android 框架自动处理。例如,Button 的视图默认情况下可聚焦,因此键盘导航功能通常应无需添加任何代码即可实现。如需针对默认情况下不可聚焦的视图启用键盘导航,开发者应将这些视图标记为可聚焦,这可以采用编程方式或在 XML 中完成,如下所示。如需了解详情,请参阅焦点处理

Kotlin

yourView.isFocusable = true

Java

yourView.setFocusable(true);

或者,您也可以在布局文件中设置 focusable 属性:

android:focusable="true"

启用焦点后,Android 框架将根据视图的位置为所有可聚焦的视图创建导航映射。这通常可以按预期运行,无需进一步的操作。当默认映射不符合应用的需求时,可按以下方式将其替换:

Kotlin

// Arrow keys
yourView.nextFocusLeftId = R.id.view_to_left
yourView.nextFocusRightId = R.id.view_to_right
yourView.nextFocusTopId = R.id.view_above
yourView.nextFocusBottomId = R.id.view_below

// Tab key
yourView.nextFocusForwardId = R.id.next_view

Java

// Arrow keys
yourView.setNextFocusLeftId(R.id.view_to_left);
yourView.setNextFocusRightId(R.id.view_to_left);
yourView.setNextFocusTopId(R.id.view_to_left);
yourView.setNextFocusBottomId(R.id.view_to_left);

// Tab key
yourView.setNextFocusForwardId(R.id.next_view);

一种很好的做法是,在每个版本发布之前,尝试仅使用键盘来执行应用的各项功能。在没有鼠标输入或触摸输入的情况下执行最常见的操作应该很容易。

请注意,对于需要使用无障碍功能的用户来说,键盘支持可能是必不可少的。

按键

对于将由 EditText 等屏幕虚拟键盘 (IME) 处理的文字输入,应用应在大屏幕设备上按预期运行,而无需开发者执行额外的操作。对于框架无法预料的按键,应用需要自行处理相应的行为。对于具有自定义视图的应用来说尤其如此。

例如,聊天应用使用 Enter 键发送消息,媒体应用使用空格键开始和停止播放,游戏使用 W、A、S 和 D 键控制移动,等等。

大多数应用都会替换 onKeyUp() 回调,并针对收到的每个键码添加预期的行为,如下所示:

Kotlin

override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
    return when (keyCode) {
        KeyEvent.KEYCODE_ENTER -> {
            sendChatMessage()
            true
        }
        KeyEvent.KEYCODE_SPACE -> {
            playOrPauseMedia()
            true
        }
        else -> super.onKeyUp(keyCode, event)
    }
}

Java

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_ENTER) {
        sendMessage();
        return true;
    } else if (KeyEvent.KEYCODE_SPACE){
        playOrPauseMedia();
        return true;
    } else {
        return super.onKeyUp(keyCode, event);
    }
}

当用户松开键时,会发生 onKeyUp 事件。使用此回调可防止在用户缓慢地按住或松开某个键时应用需要处理多个 onKeyDown 事件。如果游戏和应用想要知道用户何时按了键或预计用户会按住键盘按键,可以查找 onKeyDown() 事件并自行处理重复的 onKeyDown 事件。

如需详细了解如何提供键盘支持,请参阅处理键盘操作

快捷键

使用硬件键盘时,用户希望实现基于 Ctrl、Alt 和 Shift 的常见快捷键。如果应用不实现这些快捷键,用户可能会觉得应用使用起来不顺手。高级用户也喜欢使用快捷键来完成常用的应用专属任务。实现快捷键不仅能让应用更容易使用,而且与没有快捷键的应用相比也能彰显出自己的优势。

一些常用的快捷键包括 Ctrl + S(保存)、Ctrl + Z(撤消)和 Ctrl + Shift + Z(重做),等等。有关一些更高级的快捷键的示例,请参阅 VLC 媒体播放器快捷键列表。

您可以使用 dispatchKeyShortcutEvent() 来实现快捷键。这会截获给定键码的所有元键组合(Alt、Ctrl 和 Shift)。如需检查特定的元键,请使用 KeyEvent.isCtrlPressed()KeyEvent.isShiftPressed()KeyEvent.isAltPressed()KeyEvent.hasModifiers()

将快捷键代码与其他按键事件(如 onKeyUp()onKeyDown())分开处理可使代码维护工作更容易,还能启用默认的元键接受行为,而不必在每种情况下都手动实现元键检查。有些用户习惯使用不同的键盘布局和操作系统,允许使用所有元键组合对他们来说也会更加方便。

Kotlin

override fun dispatchKeyShortcutEvent(event: KeyEvent): Boolean {
  return when (event.keyCode) {
    KeyEvent.KEYCODE_O -> {
      openFile() // Ctrl+O, Shift+O, Alt+O
      true
    }
    KeyEvent.KEYCODE_Z-> {
      if (event.isCtrlPressed) {
        if (event.isShiftPressed) {
          redoLastAction() // Ctrl+Shift+Z pressed
          true
        } else {
          undoLastAction() // Ctrl+Z pressed
          true
        }
      }
    }
    else -> {
      return super.dispatchKeyShortcutEvent(event)
    }
  }
}

Java

@Override
public boolean dispatchKeyShortcutEvent(KeyEvent event) {
  if (event.getKeyCode() == KeyEvent.KEYCODE_O) {
      openFile(); // Ctrl+O, Shift+O, Alt+O
      return true;
  } else if(event.getKeyCode() == KeyEvent.KEYCODE_Z) {
      if (event.isCtrlPressed()) {
          if (event.isShiftPressed()) {
              redoLastAction();
              return true;
          }
          else {
              undoLastAction();
              return true;
          }
      }
  }
  return super.dispatchKeyShortcutEvent(event);
}

您也可以在 onKeyUp() 中实现快捷键,方法是检查 KeyEvent.isCtrlPressed()KeyEvent.isShiftPressed()KeyEvent.isAltPressed(),采用的方式与上述方式相同。如果元行为更多的是对应用行为而非快捷方式的修改,就会使代码更容易维护。例如,当 W 表示“向前走”、Shift + W 表示“向前跑”时。

Kotlin

override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
  return when(keyCode) {
    KeyEvent.KEYCODE_W-> {
      if (event.isShiftPressed) {
        if (event.isCtrlPressed) {
          flyForward() // Ctrl+Shift+W pressed
          true
        } else {
          runForward() // Shift+W pressed
          true
        }
      } else {
        walkForward() // W pressed
        true
      }
    }
    else -> super.onKeyUp(keyCode, event)
  }
}

Java

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_W) {
        if (event.isShiftPressed()) {
            if (event.isCtrlPressed()) {
                flyForward(); // Ctrl+Shift+W pressed
                return true;
            } else {
                runForward(); // Shift+W pressed
                return true;
            }
        } else {
            walkForward();
            return true;
        }
    }
    return super.onKeyUp(keyCode, event);
}

触控笔

许多大屏幕设备都配有触控笔,Android 应用将触控笔输入作为触摸屏输入进行处理。某些设备可能还配备 USB 或蓝牙绘图板,如 Wacom Intuos。Android 应用可以接收蓝牙输入,但不支持 USB 输入。

触控笔事件通过 View.onTouchEvent()View.onGenericMotionEvent() 被报告为触摸屏事件,并且包含返回类型为 SOURCE_STYLUSMotionEvent.getSource()

MotionEvent 还包含其他数据:

历史点

Android 会对输入事件进行批处理,并且每帧传送一次。触控笔可以按比显示屏高得多的频率来报告事件。创建绘图应用时,请务必使用 getHistorical API 检查最近可能发生的事件:

  • MotionEvent.getHistoricalX()
  • MotionEvent.getHistoricalY()
  • MotionEvent.getHistoricalPressure()
  • MotionEvent.getHistoricalAxisValue()

防止手掌误触

使用触控笔绘图、手写或与您的应用互动时,用户有时会用手掌触摸屏幕。系统可能会在将其识别为手掌误触并予以忽略之前便将相应触摸事件(设置为 ACTION_DOWNACTION_POINTER_DOWN)报告给您的应用。

Android 通过分派 MotionEvent 来取消手掌触摸事件。如果您的应用收到 ACTION_CANCEL,则应取消相应手势。如果您的应用收到 ACTION_POINTER_UP,则应检查 FLAG_CANCELED 是否已设置。如果已设置,则应取消相应手势。

请勿仅检查 FLAG_CANCELED。为方便起见,从 Android 13 开始,系统会为 ACTION_CANCEL 事件设置 FLAG_CANCELED,但以前的版本并未做此设置。

Android 12

在 Android 12(API 级别 32)及更低版本上,只能针对单指针触摸事件检测手掌误触。如果手掌触摸是唯一的指针,系统会通过在动作事件对象上设置 ACTION_CANCEL 来取消相应事件。如果还有其他指针,系统会设置 ACTION_POINTER_UP,这不足以检测手掌误触。

Android 13

在 Android 13(API 级别 33)及更高版本中,如果手掌触摸是唯一的指针,系统会对动作事件对象设置 ACTION_CANCELFLAG_CANCELED 以取消相应事件。如果还有其他指针,系统会设置 ACTION_POINTER_UPFLAG_CANCELED

每当您的应用收到带有 ACTION_POINTER_UP 的动作事件时,请检查 FLAG_CANCELED 以确定相应事件是否指示防止手掌误触(或其他事件取消)。

记事应用

ChromeOS 具有一个特殊的 intent,用于将已注册的记事应用呈现给用户。如需将应用注册为记事应用,请将以下内容添加到 Android 清单:

<intent-filter>
    <action android:name="org.chromium.arc.intent.action.CREATE_NOTE" />
    <category android:name="android.intent.category.DEFAULT" />
</intent-filter>

注册某个应用后,用户可以选择它作为默认的记事应用。当收到新记事请求时,该应用应创建一条空记事,以备处理触控笔输入。当用户希望标注图片(如屏幕截图或下载的图片)时,该应用会使用 ClipData(包含一个或多个带有 content:// URI 的项目)启动。该应用应创建一条记事,这条记事将附加的第一张图片用作背景图片,该应用还应进入一种模式,用户可以在这种模式下使用触控笔在屏幕上进行绘制。

在没有触控笔的情况下测试记事 intent

如需测试在没有主动式触控笔的情况下应用是否可以正确响应记事 intent,请使用以下方法显示 ChromeOS 上的记事选项:

  1. 切换到开发模式并使设备可写入
  2. 按 Ctrl + Alt + F2 键以打开终端
  3. 运行命令 sudo vi /etc/chrome_dev.conf
  4. i 进行修改,并将 --ash-enable-palette 添加到文件末尾且要另起一行
  5. 按 Esc 键进行保存,然后输入 :、w 和 q 并按 Enter 键
  6. 按 Ctrl + Alt + F1 键以返回 ChromeOS 常规界面
  7. 退出并重新登录

任务栏中现在应该有一个触控笔菜单:

  • 点按任务栏中的触控笔按钮,然后选择新建记事。这样应该会打开一条空白绘图记事。
  • 截取屏幕截图。在任务栏中依次选择触控笔按钮 > 截取屏幕,或者下载图片。通知中应该会有“标注图片”的选项。这样应该会启动应用,并且图片已做好标注的准备。

鼠标和触控板支持

大多数应用通常只需要处理三种以大屏幕为中心的事件:右键点击悬停拖放

右键点击

会使应用显示上下文菜单的所有操作(如轻触并按住列表项)也应该对右键点击事件作出反应。为了处理右键点击事件,应用应注册 View.OnContextClickListener。如需详细了解如何构造上下文菜单,请参阅创建上下文菜单

Kotlin

yourView.setOnContextClickListener {
  showContextMenu()
  true
}

Java

yourView.setOnContextClickListener(v -> {
    showContextMenu();
    return true;
});

悬停

开发者可以通过处理悬停事件,使其应用布局更美观且更易于使用。对于自定义视图来说尤其如此。这方面最常见的两个示例如下:

  • 通过改变鼠标指针图标,向用户表明某个元素是否具有交互行为,如可点击或可修改
  • 当指针悬停在大型列表或网格中的项目上时,向这些项目添加视觉反馈

Kotlin

// Change the icon to a "hand" pointer on hover,
// Highlight the view by changing the background.
yourView.setOnHoverListener { view, _ ->
  addVisualHighlighting(true)
  view.pointerIcon =
    PointerIcon.getSystemIcon(view.context,
    PointerIcon.TYPE_HAND)
  false // listener did not consume the event.
}

Java

yourView.setOnHoverListener((view, event) -> {
    addVisualHighlighting(true);
    view.setPointerIcon(PointerIcon
            .getSystemIcon(view.getContext(), PointerIcon.TYPE_HAND));
    return true;
});

拖放

在多窗口环境中,用户希望能够在应用之间拖放项目。桌面设备以及分屏模式下的平板电脑、手机和可折叠设备就是如此。

开发者应考虑用户是否有可能将项目拖入他们的应用。一些常见的示例包括:照片编辑器应该能接收照片、音频播放器应该能接收音频文件、绘图程序应该能接收照片。

如需添加拖放支持,请按照 Android 拖放文档中的说明进行操作,并参阅这篇 ChromeOS 博文

有关 ChromeOS 的特殊注意事项

  • 请务必通过 requestDragAndDropPermissions 请求权限,然后才能访问从应用外部拖入的项目
  • 项目必须带有 View.DRAG_FLAG_GLOBAL 标记才能被拖出到其他应用

高级指针支持

对鼠标和触控板输入进行高级处理的应用应遵循有关 View.onGenericMotionEvent() 的 Android 文档中的说明,并使用 MotionEvent.getSource() 来区分 SOURCE_MOUSESOURCE_TOUCHSCREEN

检查 MotionEvent 以确定是否实现了所需的行为:

  • 移动生成 ACTION_HOVER_MOVE 事件。
  • 按钮生成 ACTION_BUTTON_PRESSACTION_BUTTON_RELEASE 事件。您也可以使用 getButtonState() 检查所有鼠标/触控板按钮的当前状态。
  • 鼠标滚轮滚动生成 ACTION_SCROLL 事件。

游戏控制器

某些大屏幕 Android 设备最多支持四个游戏控制器。开发者应使用标准的 Android 游戏控制器 API 来处理这些游戏控制器(请参阅支持游戏控制器)。

按钮按照通用映射映射到通用值。遗憾的是,并非所有游戏控制器制造商都遵循相同的映射惯例。如果您允许用户选择不同的常见控制器映射,就能提供更好的体验。如需了解详情,请参阅处理游戏手柄按钮按下操作

输入转换模式

默认情况下,ChromeOS 会启用输入转换模式。对于大多数 Android 应用来说,此模式有助于应用在桌面环境中按预期运行。例如,在触控板上自动启用双指滚动、鼠标滚轮滚动,以及将原始显示坐标映射到窗口坐标,等等。通常,应用开发者不需要自行实现上述任何行为。

如果某个应用实现了自定义输入行为(例如,定义一种自定义触控板双指张合操作),或者这些输入转换未提供该应用预期的输入事件,您可以停用输入转换模式,方法是将以下代码添加到 Android 清单:

<uses-feature
    android:name="android.hardware.type.pc"
    android:required="false" />

其他资源