El siguiente material está destinado a desarrolladores de apps.
Para que tu app admita la rotación, DEBES hacer lo siguiente:
- Coloca un
FocusParkingView
en el diseño de la actividad correspondiente. - Asegúrate de que las vistas puedan enfocarse (o no).
- Usa
FocusArea
para unir todas tus vistas enfocables, exceptoFocusParkingView
.
Cada una de estas tareas se detalla a continuación, después de que configures tu entorno para desarrollar apps compatibles con pantallas rotativas.
Cómo configurar un control rotativo
Antes de comenzar a desarrollar apps compatibles con controles rotativos, necesitas un control rotativo o un sustituto. Tienes las opciones que se describen a continuación.
Emulador
source build/envsetup.sh && lunch car_x86_64-userdebug m -j emulator -wipe-data -no-snapshot -writable-system
También puedes usar aosp_car_x86_64-userdebug
.
Para acceder al controlador rotativo emulado, haz lo siguiente:
- Presiona los tres puntos que aparecen en la parte inferior de la barra de herramientas:
Figura 1: Accede al control rotativo emulado - Selecciona Car rotary en la ventana de controles extendidos:
Figura 2. Selecciona Rotatorio para automóviles
Teclado USB
- Conecta un teclado USB al dispositivo que ejecuta el SO Android Automotive (AAOS). En algunos casos, esto evita que aparezca el teclado en pantalla.
- Usa una compilación
userdebug
oeng
. - Habilita el filtrado de eventos de teclas:
adb shell settings put secure android.car.ROTARY_KEY_EVENT_FILTER 1
- Consulta la siguiente tabla para encontrar la clave correspondiente a cada acción:
Clave Acción rotativa P Rotar en sentido contrario a las manecillas del reloj E Giro en sentido horario A Empujar hacia la izquierda D Empujar hacia la derecha W Empujar hacia arriba S Empujar hacia abajo F o coma Botón central R o Esc Botón Atrás
Comandos de ADB
Puedes usar los comandos car_service
para insertar eventos de entrada rotativos. Estos comandos se pueden ejecutar en dispositivos que ejecutan el SO Android Automotive (AAOS) o en un emulador.
Comandos de car_service | Entrada rotativa |
---|---|
adb shell cmd car_service inject-rotary |
Rotar en sentido contrario a las manecillas del reloj |
adb shell cmd car_service inject-rotary -c true |
Giro en sentido horario |
adb shell cmd car_service inject-rotary -dt 100 50 |
Rotar en el sentido contrario a las manecillas del reloj varias veces (hace 100 ms y hace 50 ms) |
adb shell cmd car_service inject-key 282 |
Empujar hacia la izquierda |
adb shell cmd car_service inject-key 283 |
Empujar hacia la derecha |
adb shell cmd car_service inject-key 280 |
Empujar hacia arriba |
adb shell cmd car_service inject-key 281 |
Empujar hacia abajo |
adb shell cmd car_service inject-key 23 |
Clic en el botón central |
adb shell input keyevent inject-key 4 |
Clic en el botón Atrás |
Control rotativo OEM
Cuando el hardware del controlador rotativo esté en funcionamiento, esta es la opción más realista. Es especialmente útil para probar la rotación rápida.
FocusParkingView
FocusParkingView
es una vista transparente en la biblioteca de IU del vehículo (car-ui-library).
RotaryService
lo usa para admitir la navegación del controlador rotativo.
FocusParkingView
debe ser la primera vista que se pueda enfocar en el diseño. Se debe colocar fuera de todos los FocusArea
. Cada ventana debe tener un FocusParkingView
. Si ya usas el diseño base de car-ui-library, que contiene un FocusParkingView
, no es necesario que agregues otro FocusParkingView
. A continuación, se muestra un ejemplo de FocusParkingView
en RotaryPlayground
.
<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>
Estos son los motivos por los que necesitas un FocusParkingView
:
- Android no borra el enfoque automáticamente cuando se establece en otra ventana. Si intentas borrar el enfoque en la ventana anterior, Android vuelve a enfocar una vista en esa ventana, lo que hace que se enfoquen dos ventanas de forma simultánea. Agregar un
FocusParkingView
a cada ventana puede solucionar este problema. Esta vista es transparente y su elemento destacado de enfoque predeterminado está inhabilitado, por lo que es invisible para el usuario, independientemente de si está enfocado o no. Puede tomar el enfoque para queRotaryService
pueda estacionar el enfoque en él para quitar el elemento destacado de enfoque. - Si solo hay un
FocusArea
en la ventana actual, girar el controlador enFocusArea
hace queRotaryService
mueva el enfoque de la vista de la derecha a la vista de la izquierda (y viceversa). Agregar esta vista a cada ventana puede resolver el problema. CuandoRotaryService
determina que el objetivo de enfoque es unFocusParkingView
, puede determinar que está a punto de producirse un cambio de línea, en cuyo punto evita el cambio de línea si no mueve el enfoque. - Cuando el control rotativo inicia una app, Android enfoca la primera vista enfocable, que siempre es
FocusParkingView
.FocusParkingView
determina la vista óptima en la que enfocarse y, luego, aplica el enfoque.
Vistas enfocables
RotaryService
se basa en el concepto de enfoque de vista existente del framework de Android, que se remonta a la época en que los teléfonos tenían teclados físicos y mandos de pad direccional.
El atributo android:nextFocusForward
existente se redirecciona para dispositivos rotativos (consulta la personalización de FocusArea), pero android:nextFocusLeft
, android:nextFocusRight
, android:nextFocusUp
y android:nextFocusDown
no.
RotaryService
solo se enfoca en las vistas que se pueden enfocar. Por lo general, algunas vistas, como las Button
, se pueden enfocar. Otros, como TextView
y ViewGroup
, por lo general, no lo son. Las vistas en las que se puede hacer clic se pueden enfocar automáticamente y se puede hacer clic en ellas automáticamente cuando tienen un objeto de escucha de clics. Si esta lógica automática genera la capacidad de enfoque deseada, no es necesario que establezcas explícitamente la capacidad de enfoque de la vista. Si la lógica automática no genera la capacidad de enfoque deseada, configura el atributo android:focusable
como true
o false
, o bien configura de forma programática la capacidad de enfoque de la vista con View.setFocusable(boolean)
. Para que RotaryService
se enfoque en ella, una vista DEBE cumplir con los siguientes requisitos:
- Enfocable
- Habilitado
- Visible
- Tener valores distintos de cero para el ancho y la altura
Si una vista no cumple con todos estos requisitos, por ejemplo, un botón que se puede enfocar, pero que está inhabilitado, el usuario no puede usar el control rotativo para enfocarlo. Si deseas enfocarte en vistas inhabilitadas, considera usar un estado personalizado en lugar de android:state_enabled
para controlar cómo aparece la vista sin indicar que Android debe considerarla inhabilitada. Tu app puede informar al usuario por qué se inhabilitó la vista cuando se presiona. En la siguiente sección, se explica cómo hacerlo.
Estado personalizado
Para agregar un estado personalizado, sigue estos pasos:
- Para agregar un atributo personalizado a tu vista. Por ejemplo, para agregar un estado personalizado
state_rotary_enabled
a la clase de vistaCustomView
, usa lo siguiente:<declare-styleable name="CustomView"> <attr name="state_rotary_enabled" format="boolean" /> </declare-styleable>
- Para hacer un seguimiento de este estado, agrega una variable de instancia a tu vista junto con métodos de acceso:
private boolean mRotaryEnabled; public boolean getRotaryEnabled() { return mRotaryEnabled; } public void setRotaryEnabled(boolean rotaryEnabled) { mRotaryEnabled = rotaryEnabled; }
- Para leer el valor de tu atributo cuando se crea tu vista, haz lo siguiente:
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView); mRotaryEnabled = a.getBoolean(R.styleable.CustomView_state_rotary_enabled);
- En tu clase de vista, anula el método
onCreateDrawableState()
y, luego, agrega el estado personalizado cuando corresponda. Por ejemplo:@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; }
- Haz que el controlador de clics de tu vista se comporte de manera diferente según su estado. Por ejemplo, el controlador de clics podría no hacer nada o mostrar un aviso cuando
mRotaryEnabled
seafalse
. - Para que el botón aparezca inhabilitado, en el elemento de diseño de fondo de tu vista, usa
app:state_rotary_enabled
en lugar deandroid:state_enabled
. Si aún no lo tienes, deberás agregar lo siguiente:xmlns:app="http://schemas.android.com/apk/res-auto"
- Si tu vista está inhabilitada en algún diseño, reemplaza
android:enabled="false"
porapp:state_rotary_enabled="false"
y, luego, agrega el espacio de nombresapp
, como se indicó anteriormente. - Si tu vista está inhabilitada de forma programática, reemplaza las llamadas a
setEnabled()
por llamadas asetRotaryEnabled()
.
FocusArea
Usa FocusAreas
para particionar las vistas enfocables en bloques para facilitar la navegación y mantener la coherencia con otras apps. Por ejemplo, si tu app tiene una barra de herramientas, esta debe estar en un FocusArea
independiente del resto de la app. Las barras de pestañas y otros elementos de navegación también deben estar separados del resto de la app. Por lo general, las listas grandes deben tener su propio FocusArea
. De lo contrario, los usuarios deben rotar por toda la lista para acceder a algunas vistas.
FocusArea
es una subclase de LinearLayout
en car-ui-library.
Cuando esta función está habilitada, FocusArea
dibuja un elemento destacado cuando se enfoca uno de sus descendientes. Para obtener más información, consulta Personaliza los elementos destacados de enfoque.
Cuando crees un bloque de navegación en el archivo de diseño, si deseas usar un LinearLayout
como contenedor para ese bloque, usa FocusArea
.
De lo contrario, une el bloque en un FocusArea
.
NO anides un FocusArea
en otro FocusArea
.
Si lo haces, se producirá un comportamiento de navegación indefinido. Asegúrate de que todas las vistas enfocables estén anidadas dentro de un FocusArea
.
A continuación, se muestra un ejemplo de un FocusArea
en RotaryPlayground
:
<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
funciona de la siguiente manera:
- Cuando se controlan las acciones de rotación y desplazamiento,
RotaryService
busca instancias deFocusArea
en la jerarquía de vistas. - Cuando recibe un evento de rotación,
RotaryService
mueve el enfoque a otra vista que puede enfocarse en el mismoFocusArea
. - Cuando recibes un evento de empujón,
RotaryService
mueve el enfoque a otra vista que puede enfocarse en otraFocusArea
(por lo general, adyacente).
Si no incluyes ningún FocusAreas
en tu diseño, la vista raíz se considera
como un área de enfoque implícita. El usuario no puede desplazar el dedo para navegar en la app. En su lugar, se rotarán todas las vistas enfocables, lo que podría ser adecuado para los diálogos.
Personalización de FocusArea
Se pueden usar dos atributos de View estándar para personalizar la navegación rotativa:
android:nextFocusForward
permite que los desarrolladores de apps especifiquen el orden de rotación en un área de enfoque. Este es el mismo atributo que se usa para controlar el orden de tabulación de la navegación del teclado. NO uses este atributo para crear un bucle. En su lugar, usaapp:wrapAround
(consulta a continuación) para crear un bucle.android:focusedByDefault
permite que los desarrolladores de apps especifiquen la vista de enfoque predeterminada en la ventana. NO uses este atributo niapp:defaultFocus
(consulta a continuación) en el mismoFocusArea
.
FocusArea
también define algunos atributos para personalizar la navegación rotativa.
Las áreas de enfoque implícitas no se pueden personalizar con estos atributos.
- (Android 11 QPR3, Android 11 Car y Android 12)
app:defaultFocus
se puede usar para especificar el ID de una vista descendiente que se puede enfocar, en la que se debe enfocar cuando el usuario presiona estaFocusArea
. - (Android 11 QPR3, Android 11 Car y Android 12)
app:defaultFocusOverridesHistory
se puede establecer entrue
para que la vista especificada anteriormente se enfoque, incluso si se usa el historial para indicar que se enfocó otra vista en esteFocusArea
. - (Android 12)
Usaapp:nudgeLeftShortcut
,app:nudgeRightShortcut
,app:nudgeUpShortcut
yapp:nudgeDownShortcut
para especificar el ID de una vista descendiente enfocable, en la que se debe enfocar cuando el usuario empuja en una dirección determinada. Para obtener más información, consulta el contenido sobre los atajos de sugerencias a continuación.(Android 11 QPR3, Android 11 Car, obsoleto en Android 12)
app:nudgeShortcut
yapp:nudgeShortcutDirection
solo admitían un atajo de empuje. - (Android 11 QPR3, Android 11 Car y Android 12)
Para habilitar la rotación en esteFocusArea
,app:wrapAround
se puede establecer entrue
. Por lo general, se usa cuando las vistas están organizadas en un círculo o un óvalo. - (Android 11 QPR3, Android 11 para vehículos, Android 12)
Para ajustar el padding del elemento destacado en estaFocusArea
, usaapp:highlightPaddingStart
,app:highlightPaddingEnd
,app:highlightPaddingTop
,app:highlightPaddingBottom
,app:highlightPaddingHorizontal
yapp:highlightPaddingVertical
. - (Android 11 QPR3, Android 11 para vehículos y Android 12)
Para ajustar los límites percibidos de esteFocusArea
y encontrar un objetivo de empuje, usaapp:startBoundOffset
,app:endBoundOffset
,app:topBoundOffset
,app:bottomBoundOffset
,app:horizontalBoundOffset
yapp:verticalBoundOffset
. - (Android 11 QPR3, Android 11 Car y Android 12)
Para especificar de forma explícita el ID de unFocusArea
(o áreas) adyacente en las direcciones determinadas, usaapp:nudgeLeft
,app:nudgeRight
,app:nudgeUp
yapp:nudgeDown
. Úsalo cuando la búsqueda geométrica que se usa de forma predeterminada no encuentre el objetivo deseado.
Por lo general, el empujón navega entre FocusAreas. Sin embargo, con los atajos de empuje, a veces, primero se navega dentro de un FocusArea
, por lo que es posible que el usuario deba realizar el empuje dos veces para navegar al siguiente FocusArea
. Los atajos de Nudge son útiles cuando un FocusArea
contiene una lista larga seguida de un botón de acción flotante, como en el siguiente ejemplo:

Sin el atajo de empuje, el usuario tendría que rotar por toda la lista para llegar al BAF.
Personalización de los elementos destacados de enfoque
Como se señaló anteriormente, RotaryService
se basa en el concepto existente de enfoque de vista del framework de Android. Cuando el usuario rota y mueve el dispositivo, RotaryService
mueve el enfoque, centrando una vista y desenfocando otra. En Android, cuando se enfoca una vista, sucede lo siguiente:
- Especificó su propio elemento destacado de enfoque. Android dibuja el elemento destacado de enfoque de la vista.
- Si no se especifica un elemento destacado de enfoque y el elemento destacado de enfoque predeterminado no está inhabilitado, Android dibuja el elemento destacado de enfoque predeterminado para la vista.
Por lo general, las apps diseñadas para la interacción táctil no especifican los elementos destacados de enfoque adecuados.
El framework de Android proporciona el elemento destacado de enfoque predeterminado, que el OEM puede anular. Los desarrolladores de apps lo reciben cuando el tema que usan se deriva de Theme.DeviceDefault
.
Para lograr una experiencia del usuario coherente, usa el elemento destacado de enfoque predeterminado siempre que sea posible.
Si necesitas un elemento destacado de enfoque con forma personalizada (por ejemplo, redondo o con forma de píldora) o si usas un tema que no se deriva de Theme.DeviceDefault
, usa los recursos de car-ui-library para especificar tu propio elemento destacado de enfoque para cada vista.
Para especificar un elemento destacado de enfoque personalizado para una vista, cambia el elemento de diseño de fondo o primer plano de la vista a un elemento de diseño que difiera cuando se enfoque la vista. Por lo general, cambiarías el fondo. Si se usa como fondo de una vista cuadrada, el siguiente elemento de diseño produce un elemento destacado de enfoque redondo:
<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) Las referencias de recursos en negrita en el ejemplo anterior identifican los recursos definidos por car-ui-library. El OEM anula estos elementos para que sean coherentes con el elemento destacado de enfoque predeterminado que especifican. Esto garantiza que el color de la selección de enfoque, el ancho del trazo, etcétera, no cambien cuando el usuario navegue entre una vista con una selección de enfoque personalizada y una vista con la selección de enfoque predeterminada. El último elemento es una onda que se usa para el tacto. Los valores predeterminados que se usan para los recursos en negrita aparecen de la siguiente manera:

Además, se llama a un elemento destacado de enfoque personalizado cuando se le asigna un color de fondo sólido a un botón para llamar la atención del usuario, como en el siguiente ejemplo. Esto puede hacer que sea difícil ver el foco destacado. En esta situación, especifica un elemento destacado de enfoque personalizado con colores secundarios:
![]() |
- (Android 11 QPR3, Android 11 Car y 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
Por ejemplo:
![]() |
![]() |
|
Enfocado, no presionado | Enfoque, presión |
Desplazamiento rotativo
Si tu app usa RecyclerView
, DEBES usar CarUiRecyclerView
. Esto garantiza que tu IU sea coherente con las demás, ya que la personalización de un OEM se aplica a todos los CarUiRecyclerView
.
Si todos los elementos de la lista son enfocables, no necesitas hacer nada más. La navegación rotativa mueve el enfoque a través de los elementos de la lista y la lista se desplaza para que el elemento enfocado recientemente sea visible.
(Android 11 QPR3, Android 11 para vehículos, Android 12)
Si hay una combinación de elementos que se pueden enfocar y que no se pueden enfocar, o si todos los elementos no se pueden enfocar, puedes habilitar el desplazamiento rotativo, que permite al usuario usar el controlador rotativo para desplazarse gradualmente por la lista sin omitir los elementos que no se pueden enfocar. Para habilitar el desplazamiento rotativo, establece el atributo app:rotaryScrollEnabled
en true
.
(Android 11 QPR3, Android 11 Car y Android 12)
Puedes habilitar el desplazamiento rotativo en cualquier vista desplazable, incluida avCarUiRecyclerView
, con el método setRotaryScrollEnabled()
en CarUiUtils
. Si lo haces, debes hacer lo siguiente:
- Haz que la vista desplazable sea enfocable para que se pueda enfocar cuando no se vea ninguna de sus vistas descendientes enfocables.
- Inhabilita el enfoque de la vista desplazable predeterminado llamando a
setDefaultFocusHighlightEnabled(false)
para que la vista desplazable no parezca estar enfocada. - Para asegurarte de que la vista desplazable se enfoque antes que sus elementos secundarios, llama a
setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS)
. - Escucha MotionEvents con
SOURCE_ROTARY_ENCODER
yAXIS_VSCROLL
oAXIS_HSCROLL
para indicar la distancia de desplazamiento y la dirección (a través del signo).
Cuando el desplazamiento rotativo está habilitado en un CarUiRecyclerView
y el usuario rota a un área donde no hay vistas enfocables, la barra de desplazamiento cambia de gris a azul, como si indicara que la barra de desplazamiento está enfocada. Si lo deseas, puedes implementar un efecto similar.
Los MotionEvents son los mismos que los que genera una rueda de desplazamiento en un mouse, excepto la fuente.
Modo de manipulación directa
Por lo general, los empujones y la rotación navegan por la interfaz de usuario, mientras que las presiones del botón central realizan acciones, aunque no siempre es así. Por ejemplo, si un usuario quiere ajustar el volumen de la alarma, puede usar el controlador rotativo para navegar al control deslizante de volumen, presionar el botón central, girar el controlador para ajustar el volumen de la alarma y, luego, presionar el botón Atrás para volver a la navegación. Esto se conoce como modo de manipulación directa (DM). En este modo, el controlador rotativo se usa para interactuar con la vista directamente en lugar de navegar.
Implementa DM de una de estas dos maneras. Si solo necesitas controlar la rotación y la vista que deseas manipular responde a ACTION_SCROLL_FORWARD
y ACTION_SCROLL_BACKWARD
AccessibilityEvent
de forma adecuada, usa el mecanismo simple. De lo contrario, usa el mecanismo avanzado.
El mecanismo simple es la única opción en las ventanas del sistema. Las apps pueden usar cualquiera de los mecanismos.
Mecanismo simple
(Android 11 QPR3, Android 11 Car y Android 12)
Tu app debe llamar a DirectManipulationHelper.setSupportsRotateDirectly(View view, boolean enable)
.
RotaryService
reconoce cuando el usuario está en el modo DM y entra en ese modo cuando el usuario presiona el botón Centro mientras se enfoca una vista. Cuando se está en el modo DM, las rotaciones realizan ACTION_SCROLL_FORWARD
o ACTION_SCROLL_BACKWARD
y salen del modo DM cuando el usuario presiona el botón Atrás. El mecanismo simple activa o desactiva el estado seleccionado de la vista cuando se ingresa y se sale del modo DM.
Para proporcionar una indicación visual de que el usuario está en el modo DM, haz que tu vista se vea diferente cuando se seleccione. Por ejemplo, cambia el fondo cuando android:state_selected
sea true
.
Mecanismo avanzado
La app determina cuándo RotaryService
entra y sale del modo DM. Para que la experiencia del usuario sea coherente, presionar el botón central con una vista de MD enfocada debería ingresar al modo de MD, y el botón Atrás debería salir del modo de MD. Si no se usan el botón central ni el mensaje de notificación, pueden ser formas alternativas de salir del modo DM. En el caso de apps como Maps, se puede usar un botón para representar el modo DM y entrar en él.
Para admitir el modo de DM avanzado, una vista debe cumplir con los siguientes requisitos:
- (Android 11 QPR3, Android 11 Car y Android 12) DEBEN detectar un evento
KEYCODE_DPAD_CENTER
para ingresar al modo DM y detectar un eventoKEYCODE_BACK
para salir del modo DM, y llamar aDirectManipulationHelper.enableDirectManipulationMode()
en cada caso. Para escuchar estos eventos, realiza una de las siguientes acciones:- Registra un
OnKeyListener
.
o
- Extiende la vista y, luego, anula su método
dispatchKeyEvent()
.
- Registra un
- DEBE detectar eventos de sugerencia (
KEYCODE_DPAD_UP
,KEYCODE_DPAD_DOWN
,KEYCODE_DPAD_LEFT
oKEYCODE_DPAD_RIGHT
) si la vista debe controlar las sugerencias. - DEBE escuchar
MotionEvent
y obtener el recuento de rotación enAXIS_SCROLL
si la vista desea controlar la rotación. Hay varias maneras de hacerlo:- Registra un
OnGenericMotionListener
. - Extiende la vista y anula su método
dispatchTouchEvent()
.
- Registra un
- Para evitar que se bloquee en el modo DM, DEBE salir del modo DM cuando el fragmento o la actividad a la que pertenece la vista no sea interactiva.
- DEBE proporcionar una indicación visual para indicar que la vista está en modo DM.
A continuación, se muestra un ejemplo de una vista personalizada que usa el modo DM para desplazar y acercar un mapa:
/** 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(); }
Puedes encontrar más ejemplos en el proyecto RotaryPlayground
.
ActivityView
Cuando usas un ActivityView, haz lo siguiente:
- El
ActivityView
no debe poder enfocarse. - (Android 11 QPR3, Android 11 Car, se dio de baja en Android 11)
El contenido deActivityView
DEBE contener unFocusParkingView
como la primera vista enfocable, y su atributoapp:shouldRestoreFocus
DEBE serfalse
. - El contenido de
ActivityView
no debe tener vistas deandroid:focusByDefault
.
Para el usuario, ActivityViews no debería tener ningún efecto en la navegación, excepto que las áreas de enfoque no pueden abarcar ActivityViews. En otras palabras, no puedes tener un solo área de enfoque que tenga contenido dentro y fuera de un ActivityView
. Si no agregas ningún FocusArea a tu ActivityView
, la raíz de la jerarquía de vistas en el ActivityView
se considera un área de enfoque implícita.
Botones que funcionan cuando se mantienen presionados
La mayoría de los botones realizan alguna acción cuando se hace clic en ellos. Algunos botones funcionan cuando se mantienen presionados.
Por ejemplo, los botones de avance rápido y retroceso suelen funcionar cuando se mantienen presionados. Para que estos botones admitan la rotación, escucha KEYCODE_DPAD_CENTER
KeyEvents
de la siguiente manera:
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; });
En el que mRunnable
realiza una acción (como rebobinar) y se programa para que se ejecute después de una demora.
Modo táctil
Los usuarios pueden usar un control rotativo para interactuar con la unidad principal de un automóvil de dos maneras: con el control rotativo o con la pantalla táctil. Cuando se usa el controlador rotativo, se destaca una de las vistas enfocables. Cuando se toca la pantalla, no aparece ningún elemento destacado de enfoque. El usuario puede cambiar entre estos modos de entrada en cualquier momento:
- Rotatorio → táctil. Cuando el usuario toca la pantalla, desaparece el resaltado de enfoque.
- Táctil → rotativo. Cuando el usuario presiona, rota o presiona el botón Central, aparece el elemento destacado de enfoque.
Los botones Atrás y Página principal no tienen efecto en el modo de entrada.
El control rotativo se basa en el concepto existente de modo táctil de Android.
Puedes usar View.isInTouchMode()
para determinar qué modo de entrada está usando el usuario. Puedes usar OnTouchModeChangeListener
para detectar cambios. Si bien esto se puede usar para personalizar la interfaz de usuario para el modo de entrada actual, evita hacer cambios importantes, ya que pueden ser desconcertantes.
Solución de problemas
En una app diseñada para la interacción táctil, es común tener vistas anidadas que se pueden enfocar.
Por ejemplo, puede haber un FrameLayout
alrededor de un ImageButton
, ambos enfocados. Esto no afecta la función táctil, pero puede generar una mala experiencia del usuario para el control rotativo, ya que el usuario debe rotar el controlador dos veces para pasar a la siguiente vista interactiva. Para brindar una buena experiencia del usuario, Google recomienda que hagas que la vista exterior o la interior sean enfocables, pero no ambas.
Si un botón o interruptor pierde el enfoque cuando se presiona a través del controlador rotativo, es posible que se aplique una de estas condiciones:
- El botón o interruptor se inhabilita (de forma breve o indefinida) debido a que se presiona. En cualquier caso, existen dos maneras de abordar este problema:
- Deja el estado
android:enabled
comotrue
y usa un estado personalizado para inhabilitar el botón o el interruptor, como se describe en Estado personalizado. - Usa un contenedor para rodear el botón o el interruptor y haz que el contenedor sea enfocable en lugar del botón o el interruptor. (El objeto de escucha de clics debe estar en el contenedor).
- Deja el estado
- Se reemplazará el botón o interruptor. Por ejemplo, la acción que se realiza cuando se presiona el botón o se activa el interruptor puede activar una actualización de las acciones disponibles, lo que hace que los botones nuevos reemplacen a los existentes. Existen dos maneras de abordar este problema:
- En lugar de crear un botón o interruptor nuevo, configura el ícono o el texto del botón o interruptor existente.
- Al igual que antes, agrega un contenedor enfocado alrededor del botón o interruptor.
RotaryPlayground
RotaryPlayground
es una app de referencia para dispositivos rotativos. Úsalo para aprender a integrar funciones rotativas en tus apps. RotaryPlayground
se incluye en las compilaciones del emulador y en las compilaciones para dispositivos que ejecutan el SO Android Automotive (AAOS).
- Repositorio
RotaryPlayground
:packages/apps/Car/tests/RotaryPlayground/
- Versiones: Android 11 QPR3, Android 11 para vehículos y Android 12
La app de RotaryPlayground
muestra las siguientes pestañas a la izquierda:
- Tarjetas. Prueba la navegación por las áreas de enfoque, omitiendo los elementos que no se pueden enfocar y la entrada de texto.
- Manipulación directa: Prueba widgets que admitan el modo de manipulación directa simple y avanzado. Esta pestaña es específicamente para la manipulación directa dentro de la ventana de la app.
- Manipulación de la IU de Sys. Prueba widgets que admitan la manipulación directa en ventanas del sistema en las que solo se admite el modo de manipulación directa simple.
- Cuadrícula. Prueba la navegación rotativa en forma de Z con desplazamiento.
- Notificación. Prueba la acción de deslizar hacia dentro y hacia fuera las notificaciones emergentes.
- Desplázate. Prueba el desplazamiento a través de una combinación de contenido que se puede enfocar y que no se puede enfocar.
- WebView. Prueba la navegación a través de vínculos en un
WebView
. - Personalizado
FocusArea
. Prueba la personalización deFocusArea
:- Wrap-around.
android:focusedByDefault
yapp:defaultFocus
.
- Objetivos de los mensajes directos explícitos
- Combinaciones de teclas de empuje
FocusArea
sin vistas enfocables.