大屏幕上的输入兼容性

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

  • 测试基本的键盘支持,例如 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);

最佳实践是在每次发布之前,仅使用键盘访问应用的每个功能。无需鼠标或触摸输入即可轻松访问最常见的操作。

请记住,键盘支持对于有辅助功能需求的用户至关重要。

按键

对于由屏幕虚拟键盘 (IME) 处理的文本输入(例如 EditText),应用在大型屏幕设备上应按预期行为工作,开发人员无需额外操作。对于框架无法预期的按键,应用需要自行处理行为。对于具有自定义视图的应用尤其如此。

一些示例包括使用 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);
}

您还可以通过检查 KeyEvent.isCtrlPressed()KeyEvent.isShiftPressed()KeyEvent.isAltPressed()(与上述方式相同)在 onKeyUp() 中实现快捷键。如果元行为更像是对应用行为的修改而不是快捷键,这可能更容易维护。例如,当 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 有一种特殊的意图,可以向用户显示已注册的笔记应用。要将应用注册为笔记应用,请将以下内容添加到 Android 清单中

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

注册应用后,用户可以选择将其设置为默认笔记应用。当请求新笔记时,应用应创建一个空白笔记,准备接收触控笔输入。当用户希望注释图像(例如屏幕截图或下载的图像)时,应用会启动,其中包含一个或多个带有 content:// URI 的 ClipData 项目。应用应创建一个使用第一个附加图像作为背景图像的笔记,并进入用户可以使用触控笔在屏幕上绘制的模式。

在没有触控笔的情况下测试笔记意图

要测试应用是否对没有活动触控笔的笔记意图做出正确响应,请使用以下方法在 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 UI
  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 标志中

高级指针支持

对鼠标和触控板输入进行高级处理的应用程序应遵循 Android 文档中有关 View.onGenericMotionEvent() 的说明,并使用 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" />

其他资源