Chromebook 的输入兼容性

在 Chrome 操作系统设备上,许多用户使用键盘、鼠标、触控板、触控笔或游戏手柄与应用进行交互。虽然这些输入设备也在 Android 手机上使用,但使用并不普遍,而且经常被开发者忽视。

如果开发者希望其应用在 Chrome 操作系统以及其他大屏幕 Android 设备上能够很好地处理输入内容,应考虑采取以下优化措施:

  • 添加基本的键盘操作支持并进行测试,这些操作包括通过箭头键和 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) 处理的文字输入,应用应在 Chrome 操作系统上按预期运行,而无需开发者执行额外的操作。对于框架无法预料的按键,应用需要自行处理相应的行为。对于具有自定义视图的应用来说尤其如此。

例如,聊天应用使用 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 事件。

添加键盘支持时,请按照 Android 键盘处理文档中的说明进行操作。

快捷键

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

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

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

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

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);
}

鼠标和触控板支持

Chrome 操作系统会自动处理大多数鼠标和触控板事件,因此它们的行为就像 Android 手机上的触摸事件一样。这包括双指触控板/鼠标滚轮滚动。大多数应用通常只需要处理三种以桌面为中心的事件:右键点击悬停拖放

右键点击

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

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;
});

拖放

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

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

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

有关 Chrome 操作系统的特殊注意事项

  • 如需处理 Chrome 操作系统“文件”应用中的文件,请查找 MIME 类型 application/x-arc-uri-list
  • 请务必通过 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 事件

触控笔

许多 Chromebook 都配有触控笔,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()

防止手掌误触

Chrome 操作系统会尝试识别出用户何时将手掌放到了触摸屏上。不过,系统并不总是能够做到这一点,有时可能会在操作系统识别出手掌误触之前向应用报告了触摸事件。在这种情况下,系统将通过报告 ACTION_CANCEL 事件来取消触摸。

此事件会告知应用某些触摸无效,应用应撤消由这些触摸引起的所有交互。例如,绘图应用可能会在收到新线条后临时绘制这些线条以实现最低的延迟,但只有在一系列触摸事件彻底完成后,才会将这些线条永久提交到画布。如果在此期间取消了触摸事件,很容易擦除这些临时线条。

记事应用

Chrome 操作系统具有一个特殊的 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,请使用以下方法显示记事选项:

  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 键以返回 Chrome 操作系统常规界面
  7. 退出并重新登录

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

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

游戏控制器

Chromebook 最多支持四个游戏控制器。开发者应使用标准的 Android Game Controller API 来处理这些游戏控制器。

按钮按照通用映射映射到通用值。遗憾的是,并非所有游戏控制器制造商都遵循相同的映射惯例。如果您允许用户选择不同的常见控制器映射,就能提供更好的体验。

输入转换模式

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

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

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