El siguiente material está dirigido a desarrolladores de aplicaciones.
Para que tu aplicación sea compatible con rotativa, DEBES:
- Coloque un
FocusParkingView
en el diseño de actividad respectivo. - Asegúrese de que las vistas sean (o no) enfocables.
- Utilice
FocusArea
s para abarcar todas las vistas enfocables, exceptoFocusParkingView
.
Cada una de estas tareas se detalla a continuación, una vez que haya configurado su entorno para desarrollar aplicaciones habilitadas para dispositivos rotativos.
Configurar un controlador giratorio
Antes de poder comenzar a desarrollar aplicaciones habilitadas para dispositivos rotativos, necesita un controlador 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 giratorio emulado:
- Toque los tres puntos en la parte inferior de la barra de herramientas:
- Seleccione Coche giratorio en la ventana de controles extendidos:
teclado USB
- Conecte un teclado USB a su dispositivo que ejecuta el sistema operativo Android Automotive (AAOS). En algunos casos, esto evita que aparezca el teclado en pantalla.
- Utilice una compilación
userdebug
oeng
. - Habilitar filtrado de eventos clave:
adb shell settings put secure android.car.ROTARY_KEY_EVENT_FILTER 1
- Consulte la siguiente tabla para encontrar la clave correspondiente a cada acción:
Llave Acción rotatoria q Girar en sentido antihorario mi Rotar las agujas del reloj 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 de retroceso
Comandos BAD
Puede utilizar los comandos car_service
para inyectar eventos de entrada rotativos. Estos comandos se pueden ejecutar en dispositivos que ejecutan el sistema operativo Android Automotive (AAOS) o en un emulador.
comandos car_service | Entrada rotativa |
---|---|
adb shell cmd car_service inject-rotary | Girar en sentido antihorario |
adb shell cmd car_service inject-rotary -c true | Rotar las agujas del reloj |
adb shell cmd car_service inject-rotary -dt 100 50 | Girar en sentido antihorario 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 | Haga clic en el botón Atrás |
Controlador rotativo OEM
Cuando el hardware de su controlador giratorio esté en funcionamiento, esta es la opción más realista. Es particularmente útil para probar la rotación rápida.
EnfoqueEstacionamientoVer
FocusParkingView
es una vista transparente en la biblioteca de UI del automóvil (car-ui-library) . RotaryService
lo utiliza para admitir la navegación del controlador giratorio. FocusParkingView
debe ser la primera vista enfocable en el diseño. Debe colocarse fuera de todas las FocusArea
. Cada ventana debe tener un FocusParkingView
. Si ya estás usando el diseño base car-ui-library, que contiene un FocusParkingView
, no necesitas agregar 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>
Estas son las razones por las que necesita un FocusParkingView
:
- Android no borra el foco automáticamente cuando el foco se establece en otra ventana. Si intenta borrar el foco en la ventana anterior, Android vuelve a enfocar una vista en esa ventana, lo que da como resultado que se enfoquen dos ventanas simultáneamente. Agregar un
FocusParkingView
a cada ventana puede solucionar este problema. Esta vista es transparente y su resaltado de enfoque predeterminado está deshabilitado, por lo que es invisible para el usuario sin importar si está enfocado o no. Puede tomar el foco para queRotaryService
pueda estacionar el foco en él para eliminar el resaltado del foco. - Si solo hay un
FocusArea
en la ventana actual, girar el controlador enFocusArea
hace queRotaryService
mueva el foco de la vista de la derecha a la vista de la izquierda (y viceversa). Agregar esta vista a cada ventana puede solucionar el problema. CuandoRotaryService
determina que el objetivo de enfoque es unFocusParkingView
, puede determinar que está a punto de ocurrir una envoltura y en ese momento evita la envoltura al no mover el enfoque. - Cuando el control giratorio inicia una aplicación, 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 existente de enfoque de vista del marco de Android, que se remonta a cuando los teléfonos tenían teclados físicos y D-pads. El atributo android:nextFocusForward
existente se reutiliza para rotativo (consulte Personalización de FocusArea ), pero android:nextFocusLeft
, android:nextFocusRight
, android:nextFocusUp
y android:nextFocusDown
no lo son.
RotaryService
solo se centra en vistas que son enfocables. Algunas vistas, como las de Button
, suelen ser enfocables. Otros, como TextView
y ViewGroup
, normalmente no lo son. Las vistas en las que se puede hacer clic se pueden enfocar automáticamente y se puede hacer clic en las vistas automáticamente cuando tienen un detector de clics. Si esta lógica automática da como resultado la capacidad de enfoque deseada, no es necesario establecer explícitamente la capacidad de enfoque de la vista. Si la lógica automática no da como resultado la capacidad de enfoque deseada, establezca el atributo android:focusable
en true
o false
o configure mediante programación la capacidad de enfoque de la vista con View.setFocusable(boolean)
. Para que RotaryService
se centre en ello, una vista DEBE cumplir los siguientes requisitos:
- Enfocable
- Activado
- Visible
- Tener valores distintos de cero para ancho y alto
Si una vista no cumple con todos estos requisitos, por ejemplo, un botón enfocable pero deshabilitado, el usuario no puede usar el control giratorio para enfocarlo. Si desea centrarse en las vistas deshabilitadas, considere usar un estado personalizado en lugar de android:state_enabled
para controlar cómo aparece la vista sin indicar que Android debería considerarla deshabilitada. Su aplicación puede informar al usuario por qué la vista está deshabilitada cuando la toca. La siguiente sección explica cómo hacer esto.
Estado personalizado
Para agregar un estado personalizado:
- Para agregar un atributo personalizado a su vista. Por ejemplo, para agregar un estado personalizado
state_rotary_enabled
a la clase de vistaCustomView
, use:<declare-styleable name="CustomView"> <attr name="state_rotary_enabled" format="boolean" /> </declare-styleable>
- Para realizar un seguimiento de este estado, agregue una variable de instancia a su vista junto con los métodos de acceso:
private boolean mRotaryEnabled; public boolean getRotaryEnabled() { return mRotaryEnabled; } public void setRotaryEnabled(boolean rotaryEnabled) { mRotaryEnabled = rotaryEnabled; }
- Para leer el valor de su atributo cuando se crea su vista:
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView); mRotaryEnabled = a.getBoolean(R.styleable.CustomView_state_rotary_enabled);
- En su clase de vista, anule el método
onCreateDrawableState()
y luego agregue 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; }
- Haga que el controlador de clics de su vista funcione de manera diferente según su estado. Por ejemplo, es posible que el controlador de clics no haga nada o que muestre un mensaje emergente cuando
mRotaryEnabled
esfalse
. - Para que el botón aparezca deshabilitado, en el fondo dibujable de tu vista, usa
app:state_rotary_enabled
en lugar deandroid:state_enabled
. Si aún no lo tiene, deberá agregar:xmlns:app="http://schemas.android.com/apk/res-auto"
- Si su vista está deshabilitada en algún diseño, reemplace
android:enabled="false"
conapp:state_rotary_enabled="false"
y luego agregue el espacio de nombresapp
, como se indicó anteriormente. - Si su vista está deshabilitada mediante programación, reemplace las llamadas a
setEnabled()
con llamadas asetRotaryEnabled()
.
Area de enfoque
Utilice FocusAreas
para dividir las vistas enfocables en bloques para facilitar la navegación y ser coherente con otras aplicaciones. Por ejemplo, si su aplicación tiene una barra de herramientas, ésta debe estar en un FocusArea
separada del resto de su aplicación. Las barras de pestañas y otros elementos de navegación también deben estar separados del resto de la aplicación. Las listas grandes generalmente deberían tener su propia FocusArea
. De lo contrario, los usuarios deben rotar por toda la lista para acceder a algunas vistas.
FocusArea
es una subclase de LinearLayout
en la biblioteca car-ui. Cuando esta función está habilitada, FocusArea
resalta cuando uno de sus descendientes está enfocado. Para obtener más información, consulte Personalización de aspectos destacados de Focus .
Al crear un bloque de navegación en el archivo de diseño, si desea utilizar LinearLayout
como contenedor para ese bloque, utilice FocusArea
en su lugar. De lo contrario, envuelva el bloque en un FocusArea
.
NO anide un FocusArea
en otro FocusArea
. Hacerlo conduce a un comportamiento de navegación indefinido. Asegúrese 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:
- Al manejar acciones de rotación y desplazamiento,
RotaryService
busca instancias deFocusArea
en la jerarquía de vistas. - Al recibir un evento de rotación,
RotaryService
mueve el foco a otra Vista que puede enfocarse en la mismaFocusArea
. - Al recibir un evento de empujón,
RotaryService
mueve el foco a otra vista que puede enfocarse en otraFocusArea
(normalmente adyacente).
Si no incluye ninguna FocusAreas
en su diseño, la vista raíz se trata como un área de enfoque implícita. El usuario no puede empujar para navegar en la aplicación. En cambio, rotarán por todas las vistas enfocables, lo que podría ser adecuado para los diálogos.
Personalización del área de enfoque
Se pueden utilizar dos atributos de Vista estándar para personalizar la navegación giratoria:
-
android:nextFocusForward
permite a los desarrolladores de aplicaciones especificar el orden de rotación en un área de enfoque. Este es el mismo atributo que se utiliza para controlar el orden de tabulación para la navegación con el teclado. NO utilice este atributo para crear un bucle. En su lugar, utiliceapp:wrapAround
(ver más abajo) para crear un bucle. -
android:focusedByDefault
permite a los desarrolladores de aplicaciones especificar la vista de enfoque predeterminada en la ventana. NO utilice este atributo yapp:defaultFocus
(ver más abajo) en la mismaFocusArea
.
FocusArea
también define algunos atributos para personalizar la navegación giratoria. Las áreas de enfoque implícitas no se pueden personalizar con estos atributos.
- ( Android 11 QPR3, Android 11 Coche, Android 12 )
app:defaultFocus
se puede usar para especificar el ID de una vista descendiente enfocable, en la que se debe enfocar cuando el usuario se acerca a estaFocusArea
. - ( Android 11 QPR3, Android 11 Coche, Android 12 )
app:defaultFocusOverridesHistory
se puede establecer entrue
para que la vista especificada anteriormente se enfoque incluso si tiene un historial para indicar que se ha enfocado otra vista en estaFocusArea
. - ( Androide 12 )
Utiliceapp: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, consulte el contenido de los atajos de desplazamiento a continuación.( Android 11 QPR3, Android 11 Car, obsoleto en Android 12 )
app:nudgeShortcut
yapp:nudgeShortcutDirection
solo admitían un atajo de desplazamiento. - ( Android 11 QPR3, Android 11 Coche, Android 12 )
Para permitir que la rotación se ajuste a estaFocusArea
,app:wrapAround
se puede establecer entrue
. Esto se utiliza con mayor frecuencia cuando las vistas están dispuestas en un círculo u óvalo. - ( Android 11 QPR3, Android 11 Coche, Android 12 )
Para ajustar el relleno del resaltado en estaFocusArea
, useapp:highlightPaddingStart
,app:highlightPaddingEnd
,app:highlightPaddingTop
,app:highlightPaddingBottom
,app:highlightPaddingHorizontal
yapp:highlightPaddingVertical
. - ( Android 11 QPR3, Android 11 Coche, Android 12 )
Para ajustar los límites percibidos de estaFocusArea
para encontrar un objetivo de empuje, useapp:startBoundOffset
,app:endBoundOffset
,app:topBoundOffset
,app:bottomBoundOffset
,app:horizontalBoundOffset
yapp:verticalBoundOffset
. - ( Android 11 QPR3, Android 11 Coche, Android 12 )
Para especificar explícitamente el ID de unFocusArea
(o áreas) adyacentes en las instrucciones dadas, useapp:nudgeLeft
,app:nudgeRight
,app:nudgeUp
yapp:nudgeDown
. Utilícelo cuando la búsqueda geométrica utilizada por defecto no encuentre el objetivo deseado.
Empujar normalmente navega entre áreas de enfoque. Pero con los atajos de desplazamiento, a veces el desplazamiento navega primero dentro de un FocusArea
, por lo que es posible que el usuario necesite empujar dos veces para navegar a la siguiente FocusArea
. Los atajos de desplazamiento 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 FAB.
Personalización de resaltado de enfoque
Como se señaló anteriormente, RotaryService
se basa en el concepto de enfoque de vista existente en el marco de Android. Cuando el usuario gira y empuja, RotaryService
mueve el enfoque, enfocando una vista y desenfocando otra. En Android, cuando una vista está enfocada, si la vista:
- Ha especificado su propio resaltado de enfoque, Android dibuja el resaltado de enfoque de la vista.
- No especifica un resaltado de enfoque y el resaltado de enfoque predeterminado no está deshabilitado; Android dibuja el resaltado de enfoque predeterminado para la vista.
Las aplicaciones diseñadas para el tacto generalmente no especifican los aspectos destacados de enfoque adecuados.
El resaltado de enfoque predeterminado lo proporciona el marco de trabajo de Android y el OEM puede anularlo. Los desarrolladores de aplicaciones lo reciben cuando el tema que utilizan se deriva de Theme.DeviceDefault
.
Para una experiencia de usuario consistente, confíe en el resaltado de enfoque predeterminado siempre que sea posible. Si necesita un resaltado de enfoque con una forma personalizada (por ejemplo, redonda o en forma de píldora), o si está usando un tema no derivado de Theme.DeviceDefault
, use los recursos car-ui-library para especificar su propio resaltado de enfoque para cada vista.
Para especificar un resaltado de enfoque personalizado para una vista, cambie el elemento de diseño de fondo o de primer plano de la vista a un elemento de diseño que difiera cuando se enfoca la vista. Normalmente, cambiarías el fondo. El siguiente dibujable, si se usa como fondo para una vista cuadrada, produce un resaltado 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 los anula para que sean coherentes con el resaltado de enfoque predeterminado que especifican. Esto garantiza que el color de resaltado de enfoque, el ancho del trazo, etc., no cambien cuando el usuario navega entre una vista con un resaltado de enfoque personalizado y una vista con el resaltado de enfoque predeterminado. El último elemento es una onda que se utiliza para tocar. Los valores predeterminados utilizados para los recursos en negrita aparecen de la siguiente manera:
Además, se requiere un resaltado de enfoque personalizado cuando a un botón se le asigna un color de fondo sólido para llamar la atención del usuario, como en el siguiente ejemplo. Esto puede hacer que el foco sea difícil de ver. En esta situación, especifique un resaltado de enfoque personalizado utilizando colores secundarios :
- ( Android 11 QPR3, Android 11 Coche, Android 12 )
car_ui_rotary_focus_fill_secondary_color
car_ui_rotary_focus_stroke_secondary_color
- ( Androide 12 )
car_ui_rotary_focus_pressed_fill_secondary_color
car_ui_rotary_focus_pressed_stroke_secondary_color
Por ejemplo:
Centrado, no presionado | Centrado, presionado |
Desplazamiento giratorio
Si su aplicación usa RecyclerView
s, DEBE usar CarUiRecyclerView
s en su lugar. Esto garantiza que su interfaz de usuario sea coherente con las demás porque la personalización de un OEM se aplica a todos los CarUiRecyclerView
.
Si todos los elementos de su lista son enfocables, no necesita hacer nada más. La navegación giratoria mueve el foco a través de los elementos de la lista y la lista se desplaza para hacer visible el elemento recién enfocado.
( Android 11 QPR3, Android 11 Coche, Android 12 )
Si hay una combinación de elementos enfocables y no enfocables, o si todos los elementos no son enfocables, puede habilitar el desplazamiento giratorio, lo que permite al usuario usar el controlador giratorio para desplazarse gradualmente por la lista sin omitir elementos no enfocables. Para habilitar el desplazamiento giratorio, establezca el atributo app:rotaryScrollEnabled
en true
.
( Android 11 QPR3, Android 11 Coche, Android 12 )
Puede habilitar el desplazamiento giratorio en cualquier vista desplazable, incluida av CarUiRecyclerView
, con el método setRotaryScrollEnabled()
en CarUiUtils
. Si lo hace, necesita:
- Haga que la vista desplazable sea enfocable para que pueda enfocarse cuando ninguna de sus vistas descendientes enfocables esté visible.
- Deshabilite el resaltado de enfoque predeterminado en la vista desplazable llamando
setDefaultFocusHighlightEnabled(false)
para que la vista desplazable no parezca estar enfocada. - Asegúrese de que la vista desplazable esté enfocada antes que sus descendientes llamando
setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS)
. - Escuche MotionEvents con
SOURCE_ROTARY_ENCODER
yAXIS_VSCROLL
oAXIS_HSCROLL
para indicar la distancia a desplazar y la dirección (a través del letrero).
Cuando el desplazamiento giratorio está habilitado en un CarUiRecyclerView
y el usuario gira a un área donde no hay vistas enfocables, la barra de desplazamiento cambia de gris a azul, como para indicar que la barra de desplazamiento está enfocada. Puedes implementar un efecto similar si lo deseas.
Los MotionEvents son los mismos que los generados por la rueda de desplazamiento del mouse, excepto por la fuente.
Modo de manipulación directa
Normalmente, los empujones y la rotación navegan a través de la interfaz de usuario, mientras que las pulsaciones del botón central toman acción, aunque este no es siempre el caso. Por ejemplo, si un usuario desea ajustar el volumen de la alarma, puede usar el controlador giratorio para navegar hasta el 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 regresar a la navegación. . Esto se conoce como modo de manipulación directa (DM) . En este modo, el controlador giratorio se utiliza para interactuar directamente con la vista en lugar de navegar.
Implemente DM de una de dos maneras. Si solo necesita manejar la rotación y la vista que desea manipular responde apropiadamente a ACTION_SCROLL_FORWARD
y ACTION_SCROLL_BACKWARD
AccessibilityEvent
, use el mecanismo simple . De lo contrario, utilice el mecanismo avanzado .
El mecanismo simple es la única opción en las ventanas del sistema; las aplicaciones pueden utilizar cualquiera de los dos mecanismos.
Mecanismo sencillo
( Android 11 QPR3, Android 11 Coche, Android 12 )
Su aplicación debe llamar DirectManipulationHelper.setSupportsRotateDirectly(View view, boolean enable)
. RotaryService
reconoce cuando el usuario está en modo DM e ingresa al modo DM cuando el usuario presiona el botón central mientras una vista está enfocada. Cuando está en modo DM, las rotaciones realizan ACTION_SCROLL_FORWARD
o ACTION_SCROLL_BACKWARD
y sale del modo DM cuando el usuario presiona el botón Atrás. El mecanismo simple alterna el estado seleccionado de la vista al entrar y salir del modo DM.
Para proporcionar una señal visual de que el usuario está en modo DM, haga que su vista parezca diferente cuando la seleccione. Por ejemplo, cambie el fondo cuando android:state_selected
sea true
.
Mecanismo avanzado
La aplicación determina cuándo RotaryService
entra y sale del modo DM. Para una experiencia de usuario consistente, al presionar el botón Central con una vista DM enfocada se debe ingresar al modo DM y el botón Atrás debe salir del modo DM. Si no se utilizan el botón central y/o empujar, pueden ser formas alternativas de salir del modo DM. Para aplicaciones como Maps, se puede usar un botón para representar DM para ingresar al modo DM.
Para admitir el modo DM avanzado, una vista:
- ( Android 11 QPR3, Android 11 Car, Android 12 ) DEBE escuchar un evento
KEYCODE_DPAD_CENTER
para ingresar al modo DM y escuchar un eventoKEYCODE_BACK
para salir del modo DM, llamando aDirectManipulationHelper.enableDirectManipulationMode()
en cada caso. Para escuchar estos eventos, realice una de las siguientes acciones:- Registre un
OnKeyListener
. o, - Amplíe la vista y luego anule su método
dispatchKeyEvent()
.
- Registre un
- DEBE escuchar eventos de empujón (
KEYCODE_DPAD_UP
,KEYCODE_DPAD_DOWN
,KEYCODE_DPAD_LEFT
oKEYCODE_DPAD_RIGHT
) si la vista debe manejar empujones. - DEBE escuchar
MotionEvent
sy obtener el recuento de rotación enAXIS_SCROLL
si la vista quiere manejar la rotación. Hay varias formas de hacer esto:- Registre un
OnGenericMotionListener
. - Amplíe la vista y anule su método
dispatchTouchEvent()
.
- Registre un
- Para evitar quedarse atascado en el modo DM, DEBE salir del modo DM cuando el Fragmento o Actividad a la que pertenece la vista no es interactivo.
- DEBE proporcionar una señal visual para indicar que la vista está en modo DM.
A continuación se proporciona un ejemplo de una vista personalizada que utiliza el modo DM para desplazarse y hacer zoom en 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(); }
Se pueden encontrar más ejemplos en el proyecto RotaryPlayground
.
Vista de actividad
Cuando se utiliza un ActivityView:
-
ActivityView
no debería poder enfocarse. - ( Android 11 QPR3, Android 11 Car, obsoleto 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 vistasandroid: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 puede tener una única área de enfoque que tenga contenido dentro y fuera de ActivityView
. Si no agrega ninguna FocusAreas a su ActivityView
, la raíz de la jerarquía de vistas en ActivityView
se considera un área de enfoque implícita.
Botones que funcionan cuando se mantienen presionados
La mayoría de los botones provocan 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 rebobinado normalmente funcionan cuando se mantienen presionados. Para que dichos botones sean giratorios, escuche 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 ejecutarse después de un retraso.
Modo táctil
Los usuarios pueden usar un controlador giratorio para interactuar con la unidad principal de un automóvil de dos maneras, ya sea usando el controlador giratorio o tocando la pantalla. Cuando se utiliza el controlador giratorio, se resalta una de las vistas enfocables. Al tocar la pantalla, no aparece ningún resaltado de enfoque. El usuario puede cambiar entre estos modos de entrada en cualquier momento:
- Giratorio → toque. Cuando el usuario toca la pantalla, el foco resaltado desaparece.
- Toque → giratorio. Cuando el usuario empuja, gira o presiona el botón central, aparece el resaltado de enfoque.
Los botones Atrás e Inicio no tienen ningún efecto en el modo de entrada.
"Rotary aprovecha el concepto existente de modo táctil de Android". Puede usar View.isInTouchMode()
para determinar qué modo de entrada está usando el usuario. Puede utilizar OnTouchModeChangeListener
para escuchar los cambios. Si bien esto se puede utilizar para personalizar su interfaz de usuario para el modo de entrada actual, evite realizar cambios importantes, ya que pueden resultar desconcertantes.
Solución de problemas
En una aplicación diseñada para el tacto, es común tener vistas enfocables anidadas. Por ejemplo, puede haber un FrameLayout
alrededor de un ImageButton
, los cuales se pueden enfocar. Esto no daña el tacto, pero puede resultar en una mala experiencia de usuario para el modo giratorio porque el usuario debe girar el controlador dos veces para pasar a la siguiente vista interactiva. Para una buena experiencia de usuario, Google recomienda hacer enfocable la vista exterior o la vista interior, pero no ambas.
Si un botón o interruptor pierde el foco cuando se presiona a través del controlador giratorio, podría aplicarse una de estas condiciones:
- El botón o interruptor se está desactivando (breve o indefinidamente) debido a que se presionó el botón. En cualquier caso, hay dos formas de abordar esto:
- Deje el estado
android:enabled
comotrue
y use un estado personalizado para atenuar el botón o cambiar como se describe en Estado personalizado . - Utilice un contenedor para rodear el botón o interruptor y haga que el contenedor sea enfocable en lugar del botón o interruptor. (El detector de clics debe estar en el contenedor).
- Deje el estado
- Se está reemplazando el botón o interruptor. Por ejemplo, la acción realizada cuando se presiona el botón o se activa el interruptor puede desencadenar una actualización de las acciones disponibles, lo que hace que nuevos botones reemplacen a los botones existentes. Hay dos maneras de abordar esto:
- En lugar de crear un nuevo botón o interruptor, configure el ícono y/o el texto del botón o interruptor existente.
- Como arriba, agregue un contenedor enfocable alrededor del botón o interruptor.
Patio de juegos giratorio
RotaryPlayground
es una aplicación de referencia para Rotary. Úselo para aprender cómo integrar funciones giratorias en sus aplicaciones. RotaryPlayground
se incluye en compilaciones de emuladores y en compilaciones para dispositivos que ejecutan el sistema operativo Android Automotive (AAOS).
- Repositorio
RotaryPlayground
:packages/apps/Car/tests/RotaryPlayground/
- Versiones: Android 11 QPR3, Android 11 Car y Android 12
La aplicación RotaryPlayground
muestra las siguientes pestañas a la izquierda:
- Tarjetas. Pruebe navegar por las áreas de enfoque, omitiendo elementos no enfocables y entrada de texto.
- Manipulación directa. Pruebe widgets que admitan el modo de manipulación directa simple y avanzado. Esta pestaña es específicamente para manipulación directa dentro de la ventana de la aplicación.
- Manipulación de la interfaz de usuario del sistema. Pruebe los widgets que admiten la manipulación directa en ventanas del sistema donde solo se admite el modo de manipulación directa simple.
- Red. Pruebe la navegación giratoria con patrón z con desplazamiento.
- Notificación. Pruebe cómo entrar y salir de las notificaciones de aviso.
- Desplazarse. Pruebe desplazarse por una combinación de contenido enfocable y no enfocable.
- Vista web. Pruebe navegar a través de enlaces en un
WebView
. -
FocusArea
personalizada. Pruebe la personalizaciónFocusArea
:- Envolver alrededor.
-
android:focusedByDefault
yapp:defaultFocus
. - Objetivos de empujón explícitos.
- Empujar atajos.
-
FocusArea
sin vistas enfocables.
El siguiente material está dirigido a desarrolladores de aplicaciones.
Para que tu aplicación sea compatible con rotativa, DEBES:
- Coloque un
FocusParkingView
en el diseño de actividad respectivo. - Asegúrese de que las vistas sean (o no) enfocables.
- Utilice
FocusArea
s para abarcar todas las vistas enfocables, exceptoFocusParkingView
.
Cada una de estas tareas se detalla a continuación, una vez que haya configurado su entorno para desarrollar aplicaciones habilitadas para dispositivos rotativos.
Configurar un controlador giratorio
Antes de poder comenzar a desarrollar aplicaciones habilitadas para dispositivos rotativos, necesita un controlador 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 giratorio emulado:
- Toque los tres puntos en la parte inferior de la barra de herramientas:
- Seleccione Coche giratorio en la ventana de controles extendidos:
teclado USB
- Conecte un teclado USB a su dispositivo que ejecuta el sistema operativo Android Automotive (AAOS). En algunos casos, esto evita que aparezca el teclado en pantalla.
- Utilice una compilación
userdebug
oeng
. - Habilitar filtrado de eventos clave:
adb shell settings put secure android.car.ROTARY_KEY_EVENT_FILTER 1
- Consulte la siguiente tabla para encontrar la clave correspondiente a cada acción:
Llave Acción rotatoria q Girar en sentido antihorario mi Rotar las agujas del reloj 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 de retroceso
Comandos BAD
Puede utilizar los comandos car_service
para inyectar eventos de entrada rotativos. Estos comandos se pueden ejecutar en dispositivos que ejecutan el sistema operativo Android Automotive (AAOS) o en un emulador.
comandos car_service | Entrada rotativa |
---|---|
adb shell cmd car_service inject-rotary | Girar en sentido antihorario |
adb shell cmd car_service inject-rotary -c true | Rotar las agujas del reloj |
adb shell cmd car_service inject-rotary -dt 100 50 | Girar en sentido antihorario 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 | Haga clic en el botón Atrás |
Controlador rotativo OEM
Cuando el hardware de su controlador giratorio esté en funcionamiento, esta es la opción más realista. Es particularmente útil para probar la rotación rápida.
EnfoqueEstacionamientoVer
FocusParkingView
es una vista transparente en la biblioteca de UI del automóvil (car-ui-library) . RotaryService
lo utiliza para admitir la navegación del controlador giratorio. FocusParkingView
debe ser la primera vista enfocable en el diseño. Debe colocarse fuera de todas las FocusArea
. Cada ventana debe tener un FocusParkingView
. Si ya estás usando el diseño base car-ui-library, que contiene un FocusParkingView
, no necesitas agregar 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>
Estas son las razones por las que necesita un FocusParkingView
:
- Android no borra el foco automáticamente cuando el foco se establece en otra ventana. Si intenta borrar el foco en la ventana anterior, Android vuelve a enfocar una vista en esa ventana, lo que da como resultado que se enfoquen dos ventanas simultáneamente. Agregar un
FocusParkingView
a cada ventana puede solucionar este problema. Esta vista es transparente y su resaltado de enfoque predeterminado está deshabilitado, por lo que es invisible para el usuario sin importar si está enfocado o no. Puede tomar el foco para queRotaryService
pueda estacionar el foco en él para eliminar el resaltado del foco. - Si solo hay un
FocusArea
en la ventana actual, girar el controlador enFocusArea
hace queRotaryService
mueva el foco de la vista de la derecha a la vista de la izquierda (y viceversa). Agregar esta vista a cada ventana puede solucionar el problema. CuandoRotaryService
determina que el objetivo de enfoque es unFocusParkingView
, puede determinar que está a punto de ocurrir una envoltura y en ese momento evita la envoltura al no mover el enfoque. - Cuando el control giratorio inicia una aplicación, 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 existente de enfoque de vista del marco de Android, que se remonta a cuando los teléfonos tenían teclados físicos y D-pads. El atributo android:nextFocusForward
existente se reutiliza para rotativo (consulte Personalización de FocusArea ), pero android:nextFocusLeft
, android:nextFocusRight
, android:nextFocusUp
y android:nextFocusDown
no lo son.
RotaryService
solo se centra en vistas que son enfocables. Algunas vistas, como las de Button
, suelen ser enfocables. Otros, como TextView
y ViewGroup
, normalmente no lo son. Las vistas en las que se puede hacer clic se pueden enfocar automáticamente y se puede hacer clic en las vistas automáticamente cuando tienen un detector de clics. Si esta lógica automática da como resultado la capacidad de enfoque deseada, no es necesario establecer explícitamente la capacidad de enfoque de la vista. Si la lógica automática no da como resultado la capacidad de enfoque deseada, establezca el atributo android:focusable
en true
o false
o configure mediante programación la capacidad de enfoque de la vista con View.setFocusable(boolean)
. Para que RotaryService
se centre en ello, una vista DEBE cumplir los siguientes requisitos:
- Enfocable
- Activado
- Visible
- Tener valores distintos de cero para ancho y alto
Si una vista no cumple con todos estos requisitos, por ejemplo, un botón enfocable pero deshabilitado, el usuario no puede usar el control giratorio para enfocarlo. Si desea centrarse en las vistas deshabilitadas, considere usar un estado personalizado en lugar de android:state_enabled
para controlar cómo aparece la vista sin indicar que Android debería considerarla deshabilitada. Su aplicación puede informar al usuario por qué la vista está deshabilitada cuando la toca. La siguiente sección explica cómo hacer esto.
Estado personalizado
Para agregar un estado personalizado:
- Para agregar un atributo personalizado a su vista. Por ejemplo, para agregar un estado personalizado
state_rotary_enabled
a la clase de vistaCustomView
, use:<declare-styleable name="CustomView"> <attr name="state_rotary_enabled" format="boolean" /> </declare-styleable>
- Para realizar un seguimiento de este estado, agregue una variable de instancia a su vista junto con los métodos de acceso:
private boolean mRotaryEnabled; public boolean getRotaryEnabled() { return mRotaryEnabled; } public void setRotaryEnabled(boolean rotaryEnabled) { mRotaryEnabled = rotaryEnabled; }
- Para leer el valor de su atributo cuando se crea su vista:
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView); mRotaryEnabled = a.getBoolean(R.styleable.CustomView_state_rotary_enabled);
- En su clase de vista, anule el método
onCreateDrawableState()
y luego agregue 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; }
- Haga que el controlador de clics de su vista funcione de manera diferente según su estado. Por ejemplo, es posible que el controlador de clics no haga nada o que muestre un mensaje emergente cuando
mRotaryEnabled
esfalse
. - Para que el botón aparezca deshabilitado, en el fondo dibujable de tu vista, usa
app:state_rotary_enabled
en lugar deandroid:state_enabled
. Si aún no lo tiene, deberá agregar:xmlns:app="http://schemas.android.com/apk/res-auto"
- Si su vista está deshabilitada en algún diseño, reemplace
android:enabled="false"
conapp:state_rotary_enabled="false"
y luego agregue el espacio de nombresapp
, como se indicó anteriormente. - Si su vista está deshabilitada mediante programación, reemplace las llamadas a
setEnabled()
con llamadas asetRotaryEnabled()
.
Area de enfoque
Utilice FocusAreas
para dividir las vistas enfocables en bloques para facilitar la navegación y ser coherente con otras aplicaciones. Por ejemplo, si su aplicación tiene una barra de herramientas, ésta debe estar en un FocusArea
separada del resto de su aplicación. Las barras de pestañas y otros elementos de navegación también deben estar separados del resto de la aplicación. Las listas grandes generalmente deberían tener su propia FocusArea
. De lo contrario, los usuarios deben rotar por toda la lista para acceder a algunas vistas.
FocusArea
es una subclase de LinearLayout
en la biblioteca car-ui. Cuando esta función está habilitada, FocusArea
resalta cuando uno de sus descendientes está enfocado. Para obtener más información, consulte Personalización de aspectos destacados de Focus .
Al crear un bloque de navegación en el archivo de diseño, si desea utilizar LinearLayout
como contenedor para ese bloque, utilice FocusArea
en su lugar. De lo contrario, envuelva el bloque en un FocusArea
.
NO anide un FocusArea
en otro FocusArea
. Hacerlo conduce a un comportamiento de navegación indefinido. Asegúrese 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:
- Al manejar acciones de rotación y desplazamiento,
RotaryService
busca instancias deFocusArea
en la jerarquía de vistas. - Al recibir un evento de rotación,
RotaryService
mueve el foco a otra Vista que puede enfocarse en la mismaFocusArea
. - Al recibir un evento de empujón,
RotaryService
mueve el foco a otra vista que puede enfocarse en otraFocusArea
(normalmente adyacente).
Si no incluye ninguna FocusAreas
en su diseño, la vista raíz se trata como un área de enfoque implícita. El usuario no puede empujar para navegar en la aplicación. En cambio, rotarán por todas las vistas enfocables, lo que podría ser adecuado para los diálogos.
Personalización del área de enfoque
Se pueden utilizar dos atributos de Vista estándar para personalizar la navegación giratoria:
-
android:nextFocusForward
permite a los desarrolladores de aplicaciones especificar el orden de rotación en un área de enfoque. Este es el mismo atributo que se utiliza para controlar el orden de tabulación para la navegación con el teclado. NO utilice este atributo para crear un bucle. En su lugar, utiliceapp:wrapAround
(ver más abajo) para crear un bucle. -
android:focusedByDefault
permite a los desarrolladores de aplicaciones especificar la vista de enfoque predeterminada en la ventana. NO utilice este atributo yapp:defaultFocus
(ver más abajo) en la mismaFocusArea
.
FocusArea
también define algunos atributos para personalizar la navegación giratoria. Las áreas de enfoque implícitas no se pueden personalizar con estos atributos.
- ( Android 11 QPR3, Android 11 Coche, Android 12 )
app:defaultFocus
se puede usar para especificar el ID de una vista descendiente enfocable, en la que se debe enfocar cuando el usuario se acerca a estaFocusArea
. - ( Android 11 QPR3, Android 11 Coche, Android 12 )
app:defaultFocusOverridesHistory
se puede establecer entrue
para que la vista especificada anteriormente se enfoque incluso si tiene un historial para indicar que se ha enfocado otra vista en estaFocusArea
. - ( Androide 12 )
Utiliceapp: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, consulte el contenido de los atajos de desplazamiento a continuación.( Android 11 QPR3, Android 11 Car, obsoleto en Android 12 )
app:nudgeShortcut
yapp:nudgeShortcutDirection
solo admitían un atajo de desplazamiento. - ( Android 11 QPR3, Android 11 Coche, Android 12 )
Para permitir que la rotación se ajuste a estaFocusArea
,app:wrapAround
se puede establecer entrue
. Esto se utiliza con mayor frecuencia cuando las vistas están dispuestas en un círculo u óvalo. - ( Android 11 QPR3, Android 11 Coche, Android 12 )
Para ajustar el relleno del resaltado en estaFocusArea
, useapp:highlightPaddingStart
,app:highlightPaddingEnd
,app:highlightPaddingTop
,app:highlightPaddingBottom
,app:highlightPaddingHorizontal
yapp:highlightPaddingVertical
. - ( Android 11 QPR3, Android 11 Coche, Android 12 )
Para ajustar los límites percibidos de estaFocusArea
para encontrar un objetivo de empuje, useapp:startBoundOffset
,app:endBoundOffset
,app:topBoundOffset
,app:bottomBoundOffset
,app:horizontalBoundOffset
yapp:verticalBoundOffset
. - ( Android 11 QPR3, Android 11 Coche, Android 12 )
Para especificar explícitamente el ID de unFocusArea
(o áreas) adyacentes en las instrucciones dadas, useapp:nudgeLeft
,app:nudgeRight
,app:nudgeUp
yapp:nudgeDown
. Utilícelo cuando la búsqueda geométrica utilizada por defecto no encuentre el objetivo deseado.
Empujar normalmente navega entre áreas de enfoque. Pero con los atajos de desplazamiento, a veces el desplazamiento navega primero dentro de un FocusArea
, por lo que es posible que el usuario necesite empujar dos veces para navegar a la siguiente FocusArea
. Los atajos de desplazamiento 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 FAB.
Personalización de resaltado de enfoque
Como se señaló anteriormente, RotaryService
se basa en el concepto de enfoque de vista existente en el marco de Android. Cuando el usuario gira y empuja, RotaryService
mueve el enfoque, enfocando una vista y desenfocando otra. En Android, cuando una vista está enfocada, si la vista:
- Ha especificado su propio resaltado de enfoque, Android dibuja el resaltado de enfoque de la vista.
- No especifica un resaltado de enfoque y el resaltado de enfoque predeterminado no está deshabilitado; Android dibuja el resaltado de enfoque predeterminado para la vista.
Las aplicaciones diseñadas para el tacto generalmente no especifican los aspectos destacados de enfoque adecuados.
El resaltado de enfoque predeterminado lo proporciona el marco de trabajo de Android y el OEM puede anularlo. Los desarrolladores de aplicaciones lo reciben cuando el tema que utilizan se deriva de Theme.DeviceDefault
.
Para una experiencia de usuario consistente, confíe en el resaltado de enfoque predeterminado siempre que sea posible. Si necesita un resaltado de enfoque con una forma personalizada (por ejemplo, redonda o en forma de píldora), o si está usando un tema no derivado de Theme.DeviceDefault
, use los recursos car-ui-library para especificar su propio resaltado de enfoque para cada vista.
Para especificar un resaltado de enfoque personalizado para una vista, cambie el elemento de diseño de fondo o de primer plano de la vista a un elemento de diseño que difiera cuando se enfoca la vista. Normalmente, cambiarías el fondo. El siguiente dibujable, si se usa como fondo para una vista cuadrada, produce un resaltado 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 los anula para que sean coherentes con el resaltado de enfoque predeterminado que especifican. Esto garantiza que el color de resaltado de enfoque, el ancho del trazo, etc., no cambien cuando el usuario navega entre una vista con un resaltado de enfoque personalizado y una vista con el resaltado de enfoque predeterminado. El último elemento es una onda que se utiliza para tocar. Los valores predeterminados utilizados para los recursos en negrita aparecen de la siguiente manera:
Además, se requiere un resaltado de enfoque personalizado cuando a un botón se le asigna un color de fondo sólido para llamar la atención del usuario, como en el siguiente ejemplo. Esto puede hacer que el foco sea difícil de ver. En esta situación, especifique un resaltado de enfoque personalizado utilizando colores secundarios :
- ( Android 11 QPR3, Android 11 Coche, Android 12 )
car_ui_rotary_focus_fill_secondary_color
car_ui_rotary_focus_stroke_secondary_color
- ( Androide 12 )
car_ui_rotary_focus_pressed_fill_secondary_color
car_ui_rotary_focus_pressed_stroke_secondary_color
Por ejemplo:
Centrado, no presionado | Centrado, presionado |
Desplazamiento giratorio
Si su aplicación usa RecyclerView
s, DEBE usar CarUiRecyclerView
s en su lugar. Esto garantiza que su interfaz de usuario sea coherente con las demás porque la personalización de un OEM se aplica a todos los CarUiRecyclerView
.
Si todos los elementos de su lista son enfocables, no necesita hacer nada más. La navegación giratoria mueve el foco a través de los elementos de la lista y la lista se desplaza para hacer visible el elemento recién enfocado.
( Android 11 QPR3, Android 11 Coche, Android 12 )
Si hay una combinación de elementos enfocables y no enfocables, o si todos los elementos no son enfocables, puede habilitar el desplazamiento giratorio, lo que permite al usuario usar el controlador giratorio para desplazarse gradualmente por la lista sin omitir elementos no enfocables. Para habilitar el desplazamiento giratorio, establezca el atributo app:rotaryScrollEnabled
en true
.
( Android 11 QPR3, Android 11 Coche, Android 12 )
Puede habilitar el desplazamiento giratorio en cualquier vista desplazable, incluida av CarUiRecyclerView
, con el método setRotaryScrollEnabled()
en CarUiUtils
. Si lo hace, necesita:
- Haga que la vista desplazable sea enfocable para que pueda enfocarse cuando ninguna de sus vistas descendientes enfocables esté visible.
- Deshabilite el resaltado de enfoque predeterminado en la vista desplazable llamando
setDefaultFocusHighlightEnabled(false)
para que la vista desplazable no parezca estar enfocada. - Asegúrese de que la vista desplazable esté enfocada antes que sus descendientes llamando
setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS)
. - Escuche MotionEvents con
SOURCE_ROTARY_ENCODER
yAXIS_VSCROLL
oAXIS_HSCROLL
para indicar la distancia a desplazar y la dirección (a través del letrero).
Cuando el desplazamiento giratorio está habilitado en un CarUiRecyclerView
y el usuario gira a un área donde no hay vistas enfocables, la barra de desplazamiento cambia de gris a azul, como para indicar que la barra de desplazamiento está enfocada. Puedes implementar un efecto similar si lo deseas.
Los MotionEvents son los mismos que los generados por la rueda de desplazamiento del mouse, excepto por la fuente.
Modo de manipulación directa
Normalmente, los empujones y la rotación navegan a través de la interfaz de usuario, mientras que las pulsaciones del botón central toman acción, aunque este no es siempre el caso. Por ejemplo, si un usuario desea ajustar el volumen de la alarma, puede usar el controlador giratorio para navegar hasta el 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 regresar a la navegación. . Esto se conoce como modo de manipulación directa (DM) . En este modo, el controlador giratorio se utiliza para interactuar directamente con la vista en lugar de navegar.
Implemente DM de una de dos maneras. Si solo necesita manejar la rotación y la vista que desea manipular responde apropiadamente a ACTION_SCROLL_FORWARD
y ACTION_SCROLL_BACKWARD
AccessibilityEvent
, use el mecanismo simple . De lo contrario, utilice el mecanismo avanzado .
El mecanismo simple es la única opción en las ventanas del sistema; las aplicaciones pueden utilizar cualquiera de los dos mecanismos.
Mecanismo sencillo
( Android 11 QPR3, Android 11 Coche, Android 12 )
Su aplicación debe llamar DirectManipulationHelper.setSupportsRotateDirectly(View view, boolean enable)
. RotaryService
reconoce cuando el usuario está en modo DM e ingresa al modo DM cuando el usuario presiona el botón central mientras una vista está enfocada. Cuando está en modo DM, las rotaciones realizan ACTION_SCROLL_FORWARD
o ACTION_SCROLL_BACKWARD
y sale del modo DM cuando el usuario presiona el botón Atrás. El mecanismo simple alterna el estado seleccionado de la vista al entrar y salir del modo DM.
Para proporcionar una señal visual de que el usuario está en modo DM, haga que su vista parezca diferente cuando la seleccione. Por ejemplo, cambie el fondo cuando android:state_selected
sea true
.
Mecanismo avanzado
La aplicación determina cuándo RotaryService
entra y sale del modo DM. Para una experiencia de usuario consistente, al presionar el botón Central con una vista DM enfocada se debe ingresar al modo DM y el botón Atrás debe salir del modo DM. Si no se utilizan el botón central y/o empujar, pueden ser formas alternativas de salir del modo DM. Para aplicaciones como Maps, se puede usar un botón para representar DM para ingresar al modo DM.
Para admitir el modo DM avanzado, una vista:
- ( Android 11 QPR3, Android 11 Car, Android 12 ) DEBE escuchar un evento
KEYCODE_DPAD_CENTER
para ingresar al modo DM y escuchar un eventoKEYCODE_BACK
para salir del modo DM, llamando aDirectManipulationHelper.enableDirectManipulationMode()
en cada caso. Para escuchar estos eventos, realice una de las siguientes acciones:- Registre un
OnKeyListener
. o, - Amplíe la vista y luego anule su método
dispatchKeyEvent()
.
- Registre un
- DEBE escuchar eventos de empujón (
KEYCODE_DPAD_UP
,KEYCODE_DPAD_DOWN
,KEYCODE_DPAD_LEFT
oKEYCODE_DPAD_RIGHT
) si la vista debe manejar empujones. - DEBE escuchar
MotionEvent
sy obtener el recuento de rotación enAXIS_SCROLL
si la vista quiere manejar la rotación. Hay varias formas de hacer esto:- Registre un
OnGenericMotionListener
. - Amplíe la vista y anule su método
dispatchTouchEvent()
.
- Registre un
- Para evitar quedarse atascado en el modo DM, DEBE salir del modo DM cuando el Fragmento o Actividad a la que pertenece la vista no es interactivo.
- DEBE proporcionar una señal visual para indicar que la vista está en modo DM.
A continuación se proporciona un ejemplo de una vista personalizada que utiliza el modo DM para desplazarse y hacer zoom en 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(); }
Se pueden encontrar más ejemplos en el proyecto RotaryPlayground
.
Vista de actividad
Cuando se utiliza un ActivityView:
-
ActivityView
no debería poder enfocarse. - ( Android 11 QPR3, Android 11 Car, obsoleto 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 vistasandroid: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 puede tener una única área de enfoque que tenga contenido dentro y fuera de ActivityView
. Si no agrega ninguna FocusAreas a su ActivityView
, la raíz de la jerarquía de vistas en ActivityView
se considera un área de enfoque implícita.
Botones que funcionan cuando se mantienen presionados
La mayoría de los botones provocan 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 rebobinado normalmente funcionan cuando se mantienen presionados. Para que dichos botones sean giratorios, escuche 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 ejecutarse después de un retraso.
Modo táctil
Los usuarios pueden usar un controlador giratorio para interactuar con la unidad principal de un automóvil de dos maneras, ya sea usando el controlador giratorio o tocando la pantalla. Cuando se utiliza el controlador giratorio, se resalta una de las vistas enfocables. Al tocar la pantalla, no aparece ningún resaltado de enfoque. El usuario puede cambiar entre estos modos de entrada en cualquier momento:
- Giratorio → toque. Cuando el usuario toca la pantalla, el foco resaltado desaparece.
- Toque → giratorio. Cuando el usuario empuja, gira o presiona el botón central, aparece el resaltado de enfoque.
Los botones Atrás e Inicio no tienen ningún efecto en el modo de entrada.
"Rotary aprovecha el concepto existente de modo táctil de Android". Puede usar View.isInTouchMode()
para determinar qué modo de entrada está usando el usuario. Puede utilizar OnTouchModeChangeListener
para escuchar los cambios. Si bien esto se puede utilizar para personalizar su interfaz de usuario para el modo de entrada actual, evite realizar cambios importantes, ya que pueden resultar desconcertantes.
Solución de problemas
En una aplicación diseñada para el tacto, es común tener vistas enfocables anidadas. Por ejemplo, puede haber un FrameLayout
alrededor de un ImageButton
, los cuales se pueden enfocar. Esto no daña el tacto, pero puede resultar en una mala experiencia de usuario para el modo giratorio porque el usuario debe girar el controlador dos veces para pasar a la siguiente vista interactiva. Para una buena experiencia de usuario, Google recomienda hacer enfocable la vista exterior o la vista interior, pero no ambas.
Si un botón o interruptor pierde el foco cuando se presiona a través del controlador giratorio, podría aplicarse una de estas condiciones:
- El botón o interruptor se está desactivando (breve o indefinidamente) debido a que se presionó el botón. En cualquier caso, hay dos formas de abordar esto:
- Deje el estado
android:enabled
comotrue
y use un estado personalizado para atenuar el botón o cambiar como se describe en Estado personalizado . - Utilice un contenedor para rodear el botón o interruptor y haga que el contenedor sea enfocable en lugar del botón o interruptor. (El detector de clics debe estar en el contenedor).
- Deje el estado
- Se está reemplazando el botón o interruptor. Por ejemplo, la acción realizada cuando se presiona el botón o se activa el interruptor puede desencadenar una actualización de las acciones disponibles, lo que hace que nuevos botones reemplacen a los botones existentes. Hay dos maneras de abordar esto:
- En lugar de crear un nuevo botón o interruptor, configure el ícono y/o el texto del botón o interruptor existente.
- Como arriba, agregue un contenedor enfocable alrededor del botón o interruptor.
Patio de juegos giratorio
RotaryPlayground
es una aplicación de referencia para Rotary. Úselo para aprender cómo integrar funciones giratorias en sus aplicaciones. RotaryPlayground
se incluye en compilaciones de emuladores y en compilaciones para dispositivos que ejecutan el sistema operativo Android Automotive (AAOS).
- Repositorio
RotaryPlayground
:packages/apps/Car/tests/RotaryPlayground/
- Versiones: Android 11 QPR3, Android 11 Car y Android 12
La aplicación RotaryPlayground
muestra las siguientes pestañas a la izquierda:
- Tarjetas. Pruebe navegar por las áreas de enfoque, omitiendo elementos no enfocables y entrada de texto.
- Manipulación directa. Pruebe widgets que admitan el modo de manipulación directa simple y avanzado. Esta pestaña es específicamente para manipulación directa dentro de la ventana de la aplicación.
- Manipulación de la interfaz de usuario del sistema. Pruebe los widgets que admiten la manipulación directa en ventanas del sistema donde solo se admite el modo de manipulación directa simple.
- Red. Pruebe la navegación giratoria con patrón z con desplazamiento.
- Notificación. Pruebe cómo entrar y salir de las notificaciones de aviso.
- Desplazarse. Pruebe desplazarse por una combinación de contenido enfocable y no enfocable.
- Vista web. Pruebe navegar a través de enlaces en un
WebView
. -
FocusArea
personalizada. Pruebe la personalizaciónFocusArea
:- Envolver alrededor.
-
android:focusedByDefault
yapp:defaultFocus
. - Objetivos de empujón explícitos.
- Empujar atajos.
-
FocusArea
sin vistas enfocables.