在大屏设备上,用户通常使用键盘、鼠标、触控板、触控笔或游戏手柄与应用进行交互。要让您的应用能够接受来自外部设备的输入,请执行以下操作:
- 测试基本的键盘支持,例如用于撤消的 Ctrl+Z、用于复制的 Ctrl+C 以及用于保存的 Ctrl+S。如需查看默认键盘快捷键列表,请参阅处理键盘操作。
- 测试高级键盘支持,例如 Tab 键和方向键键盘导航、Enter 键文本输入确认,以及媒体应用中的 空格键 播放和暂停。
- 测试基本的鼠标交互,包括右键点击以显示上下文菜单、悬停时图标变化,以及自定义组件上的鼠标滚轮或触控板滚动事件。
- 测试应用专用输入设备,例如触控笔、游戏控制器和音乐应用 MIDI 控制器。
- 考虑高级输入支持,这可以在桌面环境中让应用脱颖而出,例如触控板作为 DJ 应用的交叉渐变器、游戏的鼠标捕获以及以键盘为中心用户的键盘快捷键。
键盘
您的应用响应键盘输入的方式影响着大屏用户体验。键盘输入分为三种类型:导航、按键操作和快捷键。
导航
以触摸为中心的应用很少实现键盘导航,但用户在使用应用时,如果双手放在键盘上,则会期待这种导航。键盘导航对于有无障碍功能需求的用户来说,在手机、平板电脑、折叠屏设备和桌面设备上可能至关重要。
对于许多应用来说,Android 框架会自动处理方向键和 Tab 键导航。例如,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 和 Meta 键的常见键盘快捷键。如果应用未实现快捷键,用户体验可能会令人沮丧。高级用户也喜欢用于常用应用专用任务的快捷键。快捷键使应用更易于使用,并区别于没有快捷键的应用。
一些常见快捷键包括 Ctrl+S(保存)、Ctrl+Z(撤消)和 Ctrl+Shift+Z(重做)。如需查看默认快捷键列表,请参阅处理键盘操作。
通过实现 dispatchKeyShortcutEvent()
来拦截给定键码的所有组合键(Alt、Ctrl、Shift 和 Meta),可以启用快捷键。要检查特定的修饰键,请使用:
KeyEvent.isCtrlPressed()
,KeyEvent.isShiftPressed()
,KeyEvent.isAltPressed()
,KeyEvent.isMetaPressed()
,或
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()
和 onKeyDown()
)的代码分开,可以默认接受修饰键,而无需在每种情况下手动实现修饰键检查。允许所有修饰键组合对于习惯不同键盘布局和操作系统的用户来说也可能更方便。
但是,您也可以通过检查 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_STYLUS
的 MotionEvent#getSource()
。
MotionEvent
对象包含有关事件的信息:
MotionEvent#getToolType()
根据与显示屏接触的工具返回TOOL_TYPE_FINGER
、TOOL_TYPE_STYLUS
或TOOL_TYPE_ERASER
:MotionEvent#getPressure()
报告施加到触控笔上的物理压力(如果支持):- 使用
MotionEvent#getAxisValue()
以及MotionEvent.AXIS_TILT
和MotionEvent.AXIS_ORIENTATION
可提供触控笔的物理倾斜度和方向(如果支持):
历史点
Android 会批量处理输入事件,并每帧传递一次。触控笔报告事件的频率远高于显示屏。创建绘图应用时,可以使用 getHistorical
API 检查可能发生在不久前的事件:
MotionEvent#getHistoricalX()
MotionEvent#getHistoricalY()
MotionEvent#getHistoricalPressure()
MotionEvent#getHistoricalAxisValue()
手掌误触识别
当用户使用触控笔在应用中绘制、书写或交互时,有时会用手掌触碰屏幕。触摸事件(设置为 ACTION_DOWN
或 ACTION_POINTER_DOWN
)可能会在系统识别并忽略意外的手掌触碰之前报告给您的应用。
Android 通过分派 MotionEvent
来取消手掌触碰事件。如果您的应用收到 ACTION_CANCEL
,则取消手势。如果您的应用收到 ACTION_POINTER_UP
,请检查是否设置了 FLAG_CANCELED
。如果是,则取消手势。
不要仅检查 FLAG_CANCELED
。在 Android 13(API 级别 33)及更高版本上,系统会为 ACTION_CANCEL
事件设置 FLAG_CANCELED
,但在较低的 Android 版本上,系统不会设置此标志。
Android 12
在 Android 12(API 级别 32)及更低版本上,手掌误触识别仅适用于单指触控事件。如果手掌触碰是唯一的指针,系统会通过在动作事件对象上设置 ACTION_CANCEL
来取消事件。如果有其他指针按下,系统会设置 ACTION_POINTER_UP
,这不足以检测手掌误触。
Android 13
在 Android 13(API 级别 33)及更高版本上,如果手掌触碰是唯一的指针,系统会通过在动作事件对象上设置 ACTION_CANCEL
和 FLAG_CANCELED
来取消事件。如果有其他指针按下,系统会设置 ACTION_POINTER_UP
和 FLAG_CANCELED
。
无论何时,只要您的应用收到带有 ACTION_POINTER_UP
的动作事件,请检查是否存在 FLAG_CANCELED
,以确定事件是否指示手掌误触(或其他事件取消)。
笔记应用
ChromeOS 有一个特殊的 Intent,可以将已注册的笔记应用呈现给用户。要将应用注册为笔记应用,请在应用清单中添加以下内容:
<intent-filter>
<action android:name="org.chromium.arc.intent.action.CREATE_NOTE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
应用注册到系统后,用户可以选择它作为默认笔记应用。当请求创建新笔记时,应用应创建一个空笔记,准备接受触控笔输入。当用户希望对图像(例如屏幕截图或下载的图像)进行注释时,应用会使用包含一个或多个带有 content://
URI 的项的 ClipData
启动。应用应创建一个笔记,该笔记使用第一个附件图像作为背景图像,并进入用户可以使用触控笔在屏幕上绘图的模式。
在没有触控笔的情况下测试笔记 Intent
[TBD remove section.]
要在没有主动触控笔的情况下测试应用是否正确响应笔记 Intent,请使用以下方法在 ChromeOS 上显示笔记选项:
- 切换到开发者模式并使设备可写
- 按 Ctrl+Alt+F2 打开终端
- 运行命令
sudo vi /etc/chrome_dev.conf
- 按
i
进行编辑,并在文件末尾新起一行添加--ash-enable-palette
- 按 Esc 保存,然后依次输入 :、w、q 并按 Enter
- 按 Ctrl+Alt+F1 返回常规 ChromeOS 界面
- 退出登录,然后重新登录
货架上现在应该有一个触控笔菜单
- 点按货架上的触控笔按钮,然后选择新建笔记。这应该会打开一个空白绘图笔记。
- 截取屏幕截图。从货架上选择触控笔按钮 > 截取屏幕或下载图像。通知中应该有注释图像的选项。这应该会启动应用,并准备好要注释的图像。
鼠标和触控板支持
大多数应用通常只需要处理三个以大屏为中心的事件:右键点击、悬停和拖放。
右键点击
任何导致应用显示上下文菜单的操作,例如长按列表项,也应响应右键点击事件。
要处理右键点击事件,应用应注册 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) true // Listener consumes the event. }
Java
// Change the icon to a "hand" pointer on hover. // Highlight the view by changing the background. yourView.setOnHoverListener((view, event) -> { addVisualHighlighting(true); view.setPointerIcon( PointerIcon.getSystemIcon(view.getContext(), PointerIcon.TYPE_HAND) ); return true; // Listener consumes the event. });
其中两个最常见的示例是:
- 通过改变鼠标指针图标,向用户指示某个元素是否具有交互行为,例如是否可点击或可编辑
- 在大型列表或网格中,当指针悬停在其上时,向项目添加视觉反馈
拖放
在多窗口环境中,用户期望能够在应用之间拖放项目。这适用于桌面设备,也适用于分屏模式下的平板电脑、手机和折叠屏设备。
考虑用户是否可能将项目拖到您的应用中。例如,照片编辑器应该期望接收照片,音频播放器应该期望接收音频文件,绘图程序应该期望接收照片。
要添加拖放支持,请参阅启用拖放,并查看Android on ChromeOS — Implementing Drag & Drop 博客文章。
ChromeOS 的特殊注意事项
- 请记住使用
requestDragAndDropPermissions()
请求权限,以访问从应用外部拖入的项目: 项目必须具有
View.DRAG_FLAG_GLOBAL
标志,才能被拖到其他应用中。
高级指针支持
对鼠标和触控板输入进行高级处理的应用应实现 View#onGenericMotionEvent()
,并使用 [MotionEvent.getSource()
][] 来区分 SOURCE_MOUSE
和 SOURCE_TOUCHSCREEN
。
检查 MotionEvent
对象以实现所需行为:
- 移动会生成
ACTION_HOVER_MOVE
事件。 - 按钮会生成
ACTION_BUTTON_PRESS
和ACTION_BUTTON_RELEASE
事件。您还可以使用getButtonState()
检查所有鼠标和触控板按钮的当前状态。 - 鼠标滚轮滚动会生成
ACTION_SCROLL
事件。
游戏控制器
某些大屏 Android 设备最多支持四个游戏控制器。使用标准的 Android 游戏控制器 API 处理游戏控制器(请参阅支持游戏控制器)。
游戏控制器按钮按照通用映射关系映射到常用值。但并非所有游戏控制器制造商都遵循相同的映射约定。如果允许用户选择不同的常用控制器映射,则可以提供更好的体验。如需了解详情,请参阅处理游戏手柄按钮按下事件。
输入转换模式
ChromeOS 默认启用输入转换模式。对于大多数 Android 应用,此模式有助于应用在桌面环境中按预期工作。一些示例包括自动启用触控板上的双指滚动、鼠标滚轮滚动以及将原始显示坐标映射到窗口坐标。通常,应用开发者无需自行实现任何这些行为。
如果应用实现了自定义输入行为(例如定义自定义双指触控板捏合操作),或者这些输入转换未提供应用预期的输入事件,您可以通过在 Android 清单中添加以下标签来禁用输入转换模式:
<uses-feature
android:name="android.hardware.type.pc"
android:required="false" />