以下材料适用于应用开发者。
为了使您的应用支持旋转,您必须
- 在相应的 Activity 布局中放置
FocusParkingView
。 - 确保视图是(或不是)可聚焦的。
- 使用
FocusArea
将所有可聚焦视图(FocusParkingView
除外)包裹起来。
在您设置好用于开发支持旋转的应用的环境后,以下详细介绍了这些任务。
设置旋转控制器
在开始开发支持旋转的应用之前,您需要旋转控制器或替代品。您有以下选项。
模拟器
source build/envsetup.sh && lunch car_x86_64-userdebug m -j emulator -wipe-data -no-snapshot -writable-system
您还可以使用 aosp_car_x86_64-userdebug
。
要访问模拟的旋转控制器
- 点击工具栏底部的三个点
图 1. 访问模拟的旋转控制器 - 在扩展控件窗口中选择 车载旋转
图 2. 选择车载旋转
USB 键盘
- 将 USB 键盘插入运行 Android Automotive OS (AAOS) 的设备。在某些情况下,这会阻止屏幕键盘出现。
- 使用
userdebug
或eng
版本。 - 启用按键事件过滤
adb shell settings put secure android.car.ROTARY_KEY_EVENT_FILTER 1
- 请参阅下表,查找每个操作对应的按键
按键 旋转操作 Q 逆时针旋转 E 顺时针旋转 A 向左轻推 D 向右轻推 W 向上轻推 S 向下轻推 F 或逗号 中心按钮 R 或 Esc 返回按钮
ADB 命令
您可以使用 car_service
命令注入旋转输入事件。这些命令可以在运行 Android Automotive OS (AAOS) 的设备或模拟器上运行。
car_service 命令 | 旋转输入 |
---|---|
adb shell cmd car_service inject-rotary |
逆时针旋转 |
adb shell cmd car_service inject-rotary -c true |
顺时针旋转 |
adb shell cmd car_service inject-rotary -dt 100 50 |
逆时针多次旋转(100 毫秒前和 50 毫秒前) |
adb shell cmd car_service inject-key 282 |
向左轻推 |
adb shell cmd car_service inject-key 283 |
向右轻推 |
adb shell cmd car_service inject-key 280 |
向上轻推 |
adb shell cmd car_service inject-key 281 |
向下轻推 |
adb shell cmd car_service inject-key 23 |
中心按钮点击 |
adb shell input keyevent inject-key 4 |
返回按钮点击 |
OEM 旋转控制器
当您的旋转控制器硬件启动并运行时,这是最现实的选择。它对于测试快速旋转特别有用。
FocusParkingView
FocusParkingView
是 Car UI 库 (car-ui-library) 中的一个透明视图。RotaryService
使用它来支持旋转控制器导航。FocusParkingView
必须是布局中第一个可聚焦的视图。它必须放置在所有 FocusArea
之外。每个窗口必须有一个 FocusParkingView
。如果您已经在使用包含 FocusParkingView
的 car-ui-library 基础布局,则无需添加另一个 FocusParkingView
。下面显示了 RotaryPlayground
中的 FocusParkingView
示例。
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.android.car.ui.FocusParkingView android:layout_width="wrap_content" android:layout_height="wrap_content"/> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent"/> </FrameLayout>
以下是您需要 FocusParkingView
的原因
- 当焦点在另一个窗口中设置时,Android 不会自动清除焦点。如果您尝试清除上一个窗口中的焦点,Android 会重新聚焦该窗口中的视图,从而导致两个窗口同时聚焦。向每个窗口添加
FocusParkingView
可以解决此问题。此视图是透明的,并且其默认焦点高亮效果已禁用,因此无论是否聚焦,用户都看不到它。它可以获取焦点,以便RotaryService
可以将焦点停放在它上面以移除焦点高亮效果。 - 如果当前窗口中只有一个
FocusArea
,则在FocusArea
中旋转控制器会导致RotaryService
将焦点从右侧的视图移动到左侧的视图(反之亦然)。向每个窗口添加此视图可以解决此问题。当RotaryService
确定焦点目标是FocusParkingView
时,它可以确定即将发生环绕,此时它会避免环绕而不移动焦点。 - 当旋转控件启动应用时,Android 会聚焦第一个可聚焦的视图,该视图始终是
FocusParkingView
。FocusParkingView
确定要聚焦的最佳视图,然后应用焦点。
可聚焦视图
RotaryService
基于 Android 框架现有的视图焦点概念构建,该概念可以追溯到手机具有物理键盘和 D-pad 的时代。现有的 android:nextFocusForward
属性被重新用于旋转(请参阅FocusArea 自定义),但 android:nextFocusLeft
、android:nextFocusRight
、android:nextFocusUp
和 android:nextFocusDown
则不是。
RotaryService
仅聚焦于可聚焦的视图。某些视图(如 Button
)通常是可聚焦的。其他视图(如 TextView
和 ViewGroup
)通常不可聚焦。可点击的视图会自动变为可聚焦,当视图具有点击监听器时,会自动变为可点击。如果此自动逻辑产生所需的聚焦能力,则无需显式设置视图的聚焦能力。如果自动逻辑未产生所需的聚焦能力,请将 android:focusable
属性设置为 true
或 false
,或者以编程方式使用 View.setFocusable(boolean)
设置视图的聚焦能力。为了使 RotaryService
聚焦于视图,视图必须满足以下要求
- 可聚焦
- 已启用
- 可见
- 宽度和高度值不为零
如果视图不满足所有这些要求(例如,可聚焦但禁用的按钮),则用户无法使用旋转控件聚焦于它。如果您希望聚焦于禁用的视图,请考虑使用自定义状态而不是 android:state_enabled
来控制视图的显示方式,而无需指示 Android 应将其视为已禁用。您的应用可以在用户点按视图时告知用户视图禁用的原因。下一部分介绍如何执行此操作。
自定义状态
要添加自定义状态
- 要向您的视图添加自定义属性。例如,要向
CustomView
视图类添加state_rotary_enabled
自定义状态,请使用<declare-styleable name="CustomView"> <attr name="state_rotary_enabled" format="boolean" /> </declare-styleable>
- 要跟踪此状态,请向您的视图添加实例变量以及访问器方法
private boolean mRotaryEnabled; public boolean getRotaryEnabled() { return mRotaryEnabled; } public void setRotaryEnabled(boolean rotaryEnabled) { mRotaryEnabled = rotaryEnabled; }
- 要在创建视图时读取属性的值
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView); mRotaryEnabled = a.getBoolean(R.styleable.CustomView_state_rotary_enabled);
- 在您的视图类中,覆盖
onCreateDrawableState()
方法,然后在适当时添加自定义状态。例如@Override protected int[] onCreateDrawableState(int extraSpace) { if (mRotaryEnabled) extraSpace++; int[] drawableState = super.onCreateDrawableState(extraSpace); if (mRotaryEnabled) { mergeDrawableStates(drawableState, { R.attr.state_rotary_enabled }); } return drawableState; }
- 使您的视图的点击处理程序根据其状态执行不同的操作。例如,当
mRotaryEnabled
为false
时,点击处理程序可能不执行任何操作,或者可能会弹出 Toast。 - 要使按钮显示为已禁用,请在视图的背景可绘制对象中使用
app:state_rotary_enabled
而不是android:state_enabled
。如果您还没有,则需要添加xmlns:app="http://schemas.android.com/apk/res-auto"
- 如果您的视图在任何布局中被禁用,请将
android:enabled="false"
替换为app:state_rotary_enabled="false"
,然后如上所述添加app
命名空间。 - 如果您的视图是以编程方式禁用的,请将对
setEnabled()
的调用替换为对setRotaryEnabled()
的调用。
FocusArea
使用 FocusArea
将可聚焦视图划分为多个块,以简化导航并与其他应用保持一致。例如,如果您的应用具有工具栏,则工具栏应与应用的其余部分位于单独的 FocusArea
中。标签栏和其他导航元素也应与应用的其余部分分开。大型列表通常应具有自己的 FocusArea
。否则,用户必须旋转浏览整个列表才能访问某些视图。
FocusArea
是 car-ui-library 中 LinearLayout
的子类。启用此功能后,当其后代之一获得焦点时,FocusArea
会绘制高亮效果。要了解更多信息,请参阅焦点高亮效果自定义。
在布局文件中创建导航块时,如果您打算使用 LinearLayout
作为该块的容器,请使用 FocusArea
代替。否则,将该块包裹在 FocusArea
中。
请勿在另一个 FocusArea
中嵌套 FocusArea
。这样做会导致未定义的导航行为。确保所有可聚焦视图都嵌套在 FocusArea
中。
RotaryPlayground
中的 FocusArea
示例如下所示
<com.android.car.ui.FocusArea android:layout_margin="16dp" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true"> </EditText> </com.android.car.ui.FocusArea>
FocusArea
的工作原理如下:
- 处理旋转和轻推操作时,
RotaryService
会在视图层次结构中查找FocusArea
的实例。 - 接收到旋转事件时,
RotaryService
会将焦点移动到同一FocusArea
中可以获取焦点的另一个视图。 - 接收到轻推事件时,
RotaryService
会将焦点移动到另一个(通常是相邻的)FocusArea
中可以获取焦点的另一个视图。
如果您的布局中未包含任何 FocusArea
,则根视图将被视为隐式焦点区域。用户无法轻推以在应用中导航。相反,他们将旋转浏览所有可聚焦视图,这对于对话框可能已足够。
FocusArea 自定义
可以使用两个标准 View 属性来自定义旋转导航
android:nextFocusForward
允许应用开发者指定焦点区域中的旋转顺序。此属性与用于控制键盘导航的 Tab 顺序的属性相同。请勿使用此属性创建循环。而是使用app:wrapAround
(见下文)创建循环。android:focusedByDefault
允许应用开发者指定窗口中的默认焦点视图。请勿在同一FocusArea
中同时使用此属性和app:defaultFocus
(见下文)。
FocusArea
还定义了一些属性来自定义旋转导航。隐式焦点区域无法使用这些属性进行自定义。
- (Android 11 QPR3、Android 11 Car、Android 12)
app:defaultFocus
可用于指定可聚焦后代视图的 ID,当用户轻推到此FocusArea
时,应聚焦于该视图。 - (Android 11 QPR3、Android 11 Car、Android 12)
app:defaultFocusOverridesHistory
可以设置为true
,以使上面指定的视图获取焦点,即使历史记录指示此FocusArea
中的另一个视图曾被聚焦。 - (Android 12)
使用app:nudgeLeftShortcut
、app:nudgeRightShortcut
、app:nudgeUpShortcut
和app:nudgeDownShortcut
指定可聚焦后代视图的 ID,当用户在给定方向上轻推时,应聚焦于该视图。要了解更多信息,请参阅下文轻推快捷方式的内容。(Android 11 QPR3、Android 11 Car,在 Android 12 中已弃用)
app:nudgeShortcut
和app:nudgeShortcutDirection
仅支持一个轻推快捷方式。 - (Android 11 QPR3、Android 11 Car、Android 12)
要在此FocusArea
中启用环绕旋转,可以将app:wrapAround
设置为true
。当视图以圆形或椭圆形排列时,最常使用此设置。 - (Android 11 QPR3、Android 11 Car、Android 12)
要调整此FocusArea
中高亮效果的内边距,请使用app:highlightPaddingStart
、app:highlightPaddingEnd
、app:highlightPaddingTop
、app:highlightPaddingBottom
、app:highlightPaddingHorizontal
和app:highlightPaddingVertical
。 - (Android 11 QPR3、Android 11 Car、Android 12)
要调整此FocusArea
的感知边界以查找轻推目标,请使用app:startBoundOffset
、app:endBoundOffset
、app:topBoundOffset
、app:bottomBoundOffset
、app:horizontalBoundOffset
和app:verticalBoundOffset
。 - (Android 11 QPR3、Android 11 Car、Android 12)
要显式指定给定方向上相邻FocusArea
(或区域)的 ID,请使用app:nudgeLeft
、app:nudgeRight
、app:nudgeUp
和app:nudgeDown
。当默认使用的几何搜索找不到所需目标时,请使用此方法。
轻推通常会在 FocusArea 之间导航。但使用轻推快捷方式时,轻推有时会先在 FocusArea
内导航,因此用户可能需要轻推两次才能导航到下一个 FocusArea
。当 FocusArea
包含一个长列表,后跟一个浮动操作按钮(如下例所示)时,轻推快捷方式非常有用

如果没有轻推快捷方式,用户将必须旋转浏览整个列表才能到达 FAB。
焦点高亮效果自定义
如上所述,RotaryService
基于 Android 框架现有的视图焦点概念构建。当用户旋转和轻推时,RotaryService
会移动焦点,聚焦一个视图并取消聚焦另一个视图。在 Android 中,当视图获得焦点时,如果视图
- 已指定其自身的焦点高亮效果,则 Android 会绘制视图的焦点高亮效果。
- 未指定焦点高亮效果,并且默认焦点高亮效果未禁用,则 Android 会绘制视图的默认焦点高亮效果。
为触摸屏设计的应用通常不指定适当的焦点高亮效果。
默认焦点高亮效果由 Android 框架提供,OEM 可以覆盖它。当应用开发者使用的主题派生自 Theme.DeviceDefault
时,他们会收到此效果。
为了获得一致的用户体验,请尽可能依赖默认焦点高亮效果。如果您需要自定义形状(例如,圆形或胶囊形)的焦点高亮效果,或者如果您使用的主题并非派生自 Theme.DeviceDefault
,请使用 car-ui-library 资源为每个视图指定您自己的焦点高亮效果。
要为视图指定自定义焦点高亮效果,请将视图的背景或前景可绘制对象更改为在视图获得焦点时不同的可绘制对象。通常,您会更改背景。以下可绘制对象(如果用作方形视图的背景)会产生圆形焦点高亮效果
<selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_focused="true" android:state_pressed="true"> <shape android:shape="oval"> <solid android:color="@color/car_ui_rotary_focus_pressed_fill_color"/> <stroke android:width="@dimen/car_ui_rotary_focus_pressed_stroke_width" android:color="@color/car_ui_rotary_focus_pressed_stroke_color"/> </shape> </item> <item android:state_focused="true"> <shape android:shape="oval"> <solid android:color="@color/car_ui_rotary_focus_fill_color"/> <stroke android:width="@dimen/car_ui_rotary_focus_stroke_width" android:color="@color/car_ui_rotary_focus_stroke_color"/> </shape> </item> <item> <ripple...> ... </ripple> </item> </selector>
(Android 11 QPR3、Android 11 Car、Android 12)上面示例中的粗体资源引用标识了 car-ui-library 定义的资源。OEM 会覆盖这些资源,以使其与他们指定的默认焦点高亮效果保持一致。这可确保当用户在具有自定义焦点高亮效果的视图和具有默认焦点高亮效果的视图之间导航时,焦点高亮效果颜色、笔划宽度等不会改变。最后一项是用于触摸的波纹效果。用于粗体资源的默认值如下所示

此外,当按钮被赋予纯色背景颜色以引起用户的注意时,也需要自定义焦点高亮效果,如下例所示。这可能会使焦点高亮效果难以看清。在这种情况下,请使用辅助颜色指定自定义焦点高亮效果
![]() |
- (Android 11 QPR3、Android 11 Car、Android 12)
car_ui_rotary_focus_fill_secondary_color
car_ui_rotary_focus_stroke_secondary_color
- (Android 12)
car_ui_rotary_focus_pressed_fill_secondary_color
car_ui_rotary_focus_pressed_stroke_secondary_color
例如
![]() |
![]() |
|
已聚焦,未按下 | 已聚焦,已按下 |
旋转滚动
如果您的应用使用 RecyclerView
,您应该改用 CarUiRecyclerView
。这可确保您的 UI 与其他 UI 保持一致,因为 OEM 的自定义设置适用于所有 CarUiRecyclerView
。
如果列表中的元素都是可聚焦的,则您无需执行任何其他操作。旋转导航会在列表中移动焦点,列表会滚动以使新聚焦的元素可见。
(Android 11 QPR3、Android 11 Car、Android 12)
如果存在可聚焦和不可聚焦元素的混合,或者如果所有元素都不可聚焦,则可以启用旋转滚动,这允许用户使用旋转控制器逐步滚动浏览列表,而无需跳过不可聚焦的项目。要启用旋转滚动,请将 app:rotaryScrollEnabled
属性设置为 true
。
(Android 11 QPR3、Android 11 Car、Android 12)
您可以在任何可滚动视图中启用旋转滚动,包括 CarUiRecyclerView
,方法是使用 CarUiUtils
中的 setRotaryScrollEnabled()
方法。如果您这样做,您需要:
- 使可滚动视图可聚焦,以便在其可聚焦的后代视图均不可见时可以聚焦它;
- 通过调用
setDefaultFocusHighlightEnabled(false)
停用可滚动视图上的默认焦点突出显示,以使可滚动视图看起来未聚焦; - 通过调用
setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS)
确保可滚动视图在其后代之前获得焦点。 - 监听带有
SOURCE_ROTARY_ENCODER
以及AXIS_VSCROLL
或AXIS_HSCROLL
的 MotionEvent,以指示要滚动的距离和方向(通过符号)。
当在 CarUiRecyclerView
上启用旋转滚动并且用户旋转到不存在可聚焦视图的区域时,滚动条会从灰色变为蓝色,就像指示滚动条已聚焦一样。如果您愿意,可以实现类似的效果。
MotionEvent 与鼠标上的滚轮生成的事件相同,只是来源不同。
直接操控模式
通常,轻推和旋转会在用户界面中导航,而中心按钮按下会执行操作,尽管并非总是如此。例如,如果用户想要调整闹钟音量,他们可以使用旋转控制器导航到音量滑块,按下中心按钮,旋转控制器以调整闹钟音量,然后按下返回按钮以返回导航。这被称为直接操控 (DM) 模式。在此模式下,旋转控制器用于直接与视图交互,而不是用于导航。
通过两种方式之一实现 DM。如果您只需要处理旋转,并且您要操控的视图对 ACTION_SCROLL_FORWARD
和 ACTION_SCROLL_BACKWARD
AccessibilityEvent
做出适当响应,请使用简单机制。否则,请使用高级机制。
简单机制是系统窗口中唯一的选项;应用可以使用任一机制。
简单机制
(Android 11 QPR3、Android 11 Car、Android 12)
您的应用应调用 DirectManipulationHelper.setSupportsRotateDirectly(View view, boolean enable)
。RotaryService
识别用户何时处于 DM 模式,并在用户在视图聚焦时按下中心按钮时进入 DM 模式。在 DM 模式下,旋转会执行 ACTION_SCROLL_FORWARD
或 ACTION_SCROLL_BACKWARD
,并在用户按下返回按钮时退出 DM 模式。简单机制会在进入和退出 DM 模式时切换视图的选定状态。
为了提供用户处于 DM 模式的可视化提示,请使您的视图在选定时显示不同。例如,当 android:state_selected
为 true
时更改背景。
高级机制
应用确定 RotaryService
何时进入和退出 DM 模式。为了获得一致的用户体验,当 DM 视图聚焦时按下中心按钮应进入 DM 模式,而返回按钮应退出 DM 模式。如果未使用中心按钮和/或轻推,则它们可能是退出 DM 模式的替代方法。对于地图等应用,可以使用表示 DM 的按钮进入 DM 模式。
为了支持高级 DM 模式,视图:
- (Android 11 QPR3、Android 11 Car、Android 12)必须监听
KEYCODE_DPAD_CENTER
事件以进入 DM 模式,并监听KEYCODE_BACK
事件以退出 DM 模式,并在每种情况下调用DirectManipulationHelper.enableDirectManipulationMode()
。要监听这些事件,请执行以下操作之一:- 注册
OnKeyListener
。
或者,
- 扩展视图,然后替换其
dispatchKeyEvent()
方法。
- 注册
- 如果视图应处理轻推,则应监听轻推事件(
KEYCODE_DPAD_UP
、KEYCODE_DPAD_DOWN
、KEYCODE_DPAD_LEFT
或KEYCODE_DPAD_RIGHT
)。 - 如果视图想要处理旋转,则应监听
MotionEvent
,并在AXIS_SCROLL
中获取旋转计数。有几种方法可以做到这一点:- 注册
OnGenericMotionListener
。 - 扩展视图并替换其
dispatchTouchEvent()
方法。
- 注册
- 为了避免卡在 DM 模式下,当视图所属的 Fragment 或 Activity 不可交互时,必须退出 DM 模式。
- 应提供可视化提示,以指示视图处于 DM 模式。
下面提供了使用 DM 模式平移和缩放地图的自定义视图示例:
/** Whether this view is in DM mode. */ private boolean mInDirectManipulationMode;
/** Initializes the view. Called by the constructors. */ private void init() { setOnKeyListener((view, keyCode, keyEvent) -> { boolean isActionUp = keyEvent.getAction() == KeyEvent.ACTION_UP; switch (keyCode) { // Always consume KEYCODE_DPAD_CENTER and KEYCODE_BACK events. case KeyEvent.KEYCODE_DPAD_CENTER: if (!mInDirectManipulationMode && isActionUp) { mInDirectManipulationMode = true; DirectManipulationHelper.enableDirectManipulationMode(this, true); setSelected(true); // visually indicate DM mode } return true; case KeyEvent.KEYCODE_BACK: if (mInDirectManipulationMode && isActionUp) { mInDirectManipulationMode = false; DirectManipulationHelper.enableDirectManipulationMode(this, false); setSelected(false); } return true; // Consume controller nudge events only when in DM mode. // When in DM mode, nudges pan the map. case KeyEvent.KEYCODE_DPAD_UP: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(0f, -10f); return true; case KeyEvent.KEYCODE_DPAD_DOWN: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(0f, 10f); return true; case KeyEvent.KEYCODE_DPAD_LEFT: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(-10f, 0f); return true; case KeyEvent.KEYCODE_DPAD_RIGHT: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(10f, 0f); return true; // Don't consume other key events. default: return false; } });
// When in DM mode, rotation zooms the map. setOnGenericMotionListener(((view, motionEvent) -> { if (!mInDirectManipulationMode) return false; float scroll = motionEvent.getAxisValue(MotionEvent.AXIS_SCROLL); zoom(10 * scroll); return true; })); }
@Override public void onPause() { if (mInDirectManipulationMode) { // To ensure that the user doesn't get stuck in DM mode, disable DM mode // when the fragment is not interactive (e.g., a dialog shows up). mInDirectManipulationMode = false; DirectManipulationHelper.enableDirectManipulationMode(this, false); } super.onPause(); }
更多示例可以在 RotaryPlayground
项目中找到。
ActivityView
当使用 ActivityView 时:
ActivityView
不应可聚焦。- (Android 11 QPR3、Android 11 Car,在 Android 11 中已弃用)
ActivityView
的内容必须包含一个FocusParkingView
作为第一个可聚焦视图,并且其app:shouldRestoreFocus
属性必须为false
。 ActivityView
的内容应没有android:focusByDefault
视图。
对于用户而言,ActivityView 对导航应该没有影响,除非焦点区域不能跨越 ActivityView。换句话说,您不能拥有一个同时包含 ActivityView
内部和外部内容的焦点区域。如果您没有向 ActivityView
添加任何 FocusArea,则 ActivityView
中的视图层次结构的根将被视为隐式焦点区域。
按住时操作的按钮
大多数按钮在单击时都会导致某些操作。有些按钮在按住时操作。例如,快进和倒退按钮通常在按住时操作。为了使此类按钮支持旋转,请按如下方式监听 KEYCODE_DPAD_CENTER
KeyEvent
:
mButton.setOnKeyListener((v, keyCode, event) -> { if (keyCode != KEYCODE_DPAD_CENTER) { return false; } if (event.getAction() == ACTION_DOWN) { mButton.setPressed(true); mHandler.post(mRunnable); } else { mButton.setPressed(false); mHandler.removeCallbacks(mRunnable); } return true; });
其中 mRunnable
执行操作(例如倒退),并安排自身在延迟后运行。
触摸模式
用户可以通过两种方式使用旋转控制器与车载信息娱乐系统进行交互:使用旋转控制器或触摸屏幕。当使用旋转控制器时,其中一个可聚焦视图会被突出显示。当触摸屏幕时,不会显示焦点突出显示。用户可以随时在这些输入模式之间切换:
- 旋转 → 触摸。当用户触摸屏幕时,焦点突出显示消失。
- 触摸 → 旋转。当用户轻推、旋转或按下中心按钮时,焦点突出显示出现。
返回和主页按钮对输入模式没有影响。
旋转功能基于 Android 现有的 触摸模式 概念。您可以使用 View.isInTouchMode()
来确定用户正在使用的输入模式。您可以使用 OnTouchModeChangeListener
来监听更改。虽然这可以用于为当前输入模式自定义用户界面,但请避免任何重大更改,因为它们可能会令人不安。
问题排查
在为触摸设计的应用中,嵌套可聚焦视图很常见。例如,ImageButton
周围可能有一个 FrameLayout
,两者都是可聚焦的。这对触摸没有危害,但对于旋转来说,可能会导致糟糕的用户体验,因为用户必须旋转控制器两次才能移动到下一个交互式视图。为了获得良好的用户体验,Google 建议您使外部视图或内部视图可聚焦,但不要同时都可聚焦。
如果按钮或开关在通过旋转控制器按下时失去焦点,则可能适用以下条件之一:
- 由于按钮被按下,按钮或开关被禁用(短暂或无限期)。在任一情况下,都有两种方法可以解决此问题:
- 将
android:enabled
状态保留为true
,并使用自定义状态来灰显按钮或开关,如自定义状态中所述。 - 使用容器包围按钮或开关,并使容器可聚焦,而不是按钮或开关。(点击监听器必须在容器上。)
- 将
- 按钮或开关正在被替换。例如,按下按钮或切换开关时采取的操作可能会触发可用操作的刷新,从而导致新按钮替换现有按钮。有两种方法可以解决此问题:
- 不要创建新的按钮或开关,而是设置现有按钮或开关的图标和/或文本。
- 如上所述,在按钮或开关周围添加一个可聚焦的容器。
RotaryPlayground
RotaryPlayground
是旋转功能的参考应用。使用它可以了解如何将旋转功能集成到您的应用中。RotaryPlayground
包含在模拟器版本和运行 Android Automotive OS (AAOS) 的设备的版本中。
RotaryPlayground
代码库:packages/apps/Car/tests/RotaryPlayground/
- 版本:Android 11 QPR3、Android 11 Car 和 Android 12
RotaryPlayground
应用在左侧显示以下标签:
- 卡片。测试在焦点区域周围导航、跳过不可聚焦的元素和文本输入。
- 直接操控。测试支持简单和高级直接操控模式的小部件。此标签专门用于应用窗口内的直接操控。
- 系统界面操控。测试支持系统窗口中直接操控的小部件,其中仅支持简单直接操控模式。
- 网格。测试带有滚动的 Z 字形旋转导航。
- 通知。测试在浮动通知中和浮动通知外轻推。
- 滚动。测试滚动浏览可聚焦和不可聚焦内容混合的内容。
- WebView。测试在
WebView
中导航链接。 - 自定义
FocusArea
。测试FocusArea
自定义:- 环绕。
android:focusedByDefault
和app:defaultFocus
.
- 显式轻推目标。
- 轻推快捷方式。
- 没有可聚焦视图的
FocusArea
。