El siguiente material es para desarrolladores de aplicaciones.
Para que su aplicación sea compatible con la rotación, DEBE:
- Coloque un
FocusParkingView
en el diseño de actividad respectivo. - Asegúrese de que las vistas sean (o no) enfocables.
- Use
FocusArea
s para envolver todas sus 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 rotación.
Configurar un controlador rotatorio
Antes de que pueda comenzar a desarrollar aplicaciones habilitadas para rotación, necesita un controlador rotatorio o un suplente. Tiene 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 puede usar aosp_car_x86_64-userdebug
.
Para acceder al controlador rotatorio emulado:
- Toca los tres puntos en la parte inferior de la barra de herramientas:
- Seleccione Coche giratorio en la ventana de controles ampliados:
Teclado USB
- Conecte un teclado USB a su dispositivo que ejecute Android Automotive OS (AAOS). En algunos casos, esto puede evitar que aparezca el teclado en pantalla.
- Use 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 tecla correspondiente a cada acción:
Llave Acción rotatoria q Girar en sentido antihorario mi Rotar las agujas del reloj A Empujar a la izquierda D Empujar a la derecha W empujar hacia arriba S empujar hacia abajo Para coma Botón central R o ESC Botón de retroceso
Comandos BAD
Puede usar los comandos car_service
para inyectar eventos de entrada rotativos. Estos comandos se pueden ejecutar en dispositivos que ejecutan Android Automotive OS (AAOS) o en un emulador.
comandos de servicio de coche | 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 | Gire en sentido contrario a las agujas del reloj varias veces (hace 100 ms y hace 50 ms) |
adb shell cmd car_service inject-key 282 | Empujar a la izquierda |
adb shell cmd car_service inject-key 283 | Empujar a 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á funcionando, esta es la opción más realista. Es particularmente útil para probar la rotación rápida.
EnfoqueEstacionamientoVista
FocusParkingView
es una vista transparente en Car UI Library (car-ui-library) . RotaryService
lo usa para admitir la navegación del controlador rotatorio. 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á usando el diseño base car-ui-library, que contiene un FocusParkingView
, no necesita 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 enfoque automáticamente cuando el enfoque se establece en otra ventana. Si intenta borrar el enfoque en la ventana anterior, Android vuelve a enfocar una vista en esa ventana, lo que da como resultado que se enfocan 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 del foco es unFocusParkingView
, puede determinar que está a punto de ocurrir una vuelta y en ese momento evita la vuelta al no mover el foco. - Cuando el control giratorio inicia una aplicación, Android enfoca la primera vista enfocable, que siempre es
FocusParkingView
.FocusParkingView
determina la vista óptima para enfocar y luego aplica el enfoque.
Vistas enfocables
RotaryService
se basa en el concepto existente de enfoque de vista del marco de trabajo de Android, que se remonta a cuando los teléfonos tenían teclados físicos y D-pads. El atributo android:nextFocusForward
existente se reutilizó para rotativo (consulte Personalización de FocusArea ), pero android:nextFocusLeft
, android:nextFocusRight
, android:nextFocusUp
y android:nextFocusDown
no lo son.
RotaryService
solo se enfoca en vistas que son enfocables. Algunas vistas, como Button
s, suelen ser enfocables. 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 las vistas se pueden hacer clic automáticamente cuando tienen un detector de clics. Si esta lógica automática da como resultado la capacidad de enfoque deseada, no necesita 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 establezca mediante programación la capacidad de enfoque de la vista con View.setFocusable(boolean)
. Para que RotaryService
se centre en ella, una vista DEBE cumplir con 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 se 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 rastrear 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
seafalse
. - Para que el botón aparezca deshabilitado, en el fondo dibujable de su vista, use
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 cualquier 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 mantener la coherencia con otras aplicaciones. Por ejemplo, si su aplicación tiene una barra de herramientas, la barra de herramientas debe estar en un FocusArea
separado 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 deben 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 car-ui-library. Cuando esta característica está habilitada, un FocusArea
resaltará cuando uno de sus descendientes esté enfocado. Para obtener más información, consulte Personalización de resaltado de enfoque .
Al crear un bloque de navegación en el archivo de diseño, si pretende usar un LinearLayout
como contenedor para ese bloque, use un FocusArea
en su lugar. De lo contrario, envuelva el bloque en un FocusArea
.
NO anide un FocusArea
en otro FocusArea
. Si lo hace, dará lugar 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 empuje,
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 a través de todas las vistas enfocables, lo que puede ser adecuado para los diálogos.
Personalización del área de enfoque
Se pueden usar 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 usa para controlar el orden de tabulación para la navegación con el teclado. NO use este atributo para crear un bucle. En su lugar, useapp: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 use este atributo yapp:defaultFocus
(vea a continuación) en la mismaFocusArea
.
FocusArea
también define algunos atributos para personalizar la navegación rotatoria. 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 empuja a esteFocusArea
. - ( Android 11 QPR3, Android 11 Coche, Android 12 )
app:defaultFocusOverridesHistory
se puede establecer entrue
para que la vista especificada anteriormente tome el foco, incluso si con el historial para indicar que se ha enfocado otra vista en estaFocusArea
. - ( 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, consulte el contenido de los atajos de empuje a continuación.( Android 11 QPR3, Android 11 Car, en desuso en Android 12 )
app:nudgeShortcut
yapp:nudgeShortcutDirection
admitían solo un atajo de empuje. - ( Android 11 QPR3, Android 11 Coche, Android 12 )
Para permitir que la rotación se ajuste en estaFocusArea
,app:wrapAround
se puede establecer entrue
. Esto se suele utilizar cuando las vistas se organizan en un círculo o un ó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) adyacente en las direcciones dadas, useapp:nudgeLeft
,app:nudgeRight
,app:nudgeUp
yapp:nudgeDown
. Utilícelo cuando la búsqueda geométrica utilizada de forma predeterminada no encuentre el objetivo deseado.
Empujar generalmente navega entre FocusAreas. Pero con los atajos de toque, a veces el toque primero navega dentro de un FocusArea
, por lo que es posible que el usuario necesite empujar dos veces para navegar al 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 ejemplo a continuación:
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 existente de enfoque de vista del marco de trabajo de Android. Cuando el usuario gira y empuja, RotaryService
mueve el foco, enfocando una vista y desenfocando otra. En Android, cuando se enfoca una vista, 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 resaltados de enfoque apropiados.
El marco de Android proporciona el resaltado de enfoque predeterminado y el OEM puede anularlo. Los desarrolladores de aplicaciones lo reciben cuando el tema que usan 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 personalizado (por ejemplo, redondo o en forma de pastilla), o si está usando un tema que no se deriva de Theme.DeviceDefault
, use los recursos de 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. Por lo general, cambiaría el fondo. El siguiente elemento de diseño, 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 utilizada para el tacto. 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 da un color de fondo sólido para llamar la atención del usuario, como en el ejemplo a continuación. Esto puede hacer que el punto culminante del enfoque sea difícil de ver. En esta situación, especifique un resaltado de enfoque personalizado usando colores secundarios :
- ( Android 11 QPR3, Android 11 Coche, 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 | Enfocado, presionado |
Desplazamiento rotatorio
Si su aplicación usa RecyclerView
s, DEBERÍA 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
s.
Si todos los elementos de su lista son enfocables, no necesita hacer nada más. La navegación giratoria mueve el enfoque 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, que permite al usuario usar el controlador giratorio para desplazarse gradualmente por la lista sin omitir los 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, incluido av CarUiRecyclerView
, con el método setRotaryScrollEnabled()
en CarUiUtils
. Si lo hace, necesita:
- Haga que la vista desplazable sea enfocable para que se pueda enfocar 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 de desplazamiento y la dirección (a través del signo).
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. Puede implementar un efecto similar si lo desea.
Los MotionEvents son los mismos que los generados por una rueda de desplazamiento en un 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 actúan, aunque no siempre es así. 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 volver a la navegación. . Esto se conoce como modo de manipulación directa (DM) . En este modo, el controlador giratorio se usa para interactuar con la vista directamente 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 a ACTION_SCROLL_FORWARD
y ACTION_SCROLL_BACKWARD
AccessibilityEvent
s de manera adecuada, 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 usar cualquier mecanismo.
Mecanismo sencillo
( Android 11 QPR3, Android 11 Coche, Android 12 )
Tu 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 se seleccione. Por ejemplo, cambia el fondo cuando android:state_selected
es true
.
Mecanismo avanzado
La aplicación determina cuándo RotaryService
ingresa y sale del modo DM. Para una experiencia de usuario consistente, al presionar el botón central con una vista de DM enfocada, 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 el empujón, 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, - Extienda la vista y luego anule su método
dispatchKeyEvent()
.
- Registre un
- DEBERÍA escuchar eventos de empujón (
KEYCODE_DPAD_UP
,KEYCODE_DPAD_DOWN
,KEYCODE_DPAD_LEFT
oKEYCODE_DPAD_RIGHT
) si la vista debe manejar empujones. - DEBERÍA escuchar
MotionEvent
s y 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
. - Extienda 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 la Actividad a la que pertenece la vista no es interactivo.
- DEBERÍA 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 desplazar 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
Al usar un ActivityView:
- El
ActivityView
no debe ser enfocable. - ( Android 11 QPR3, Android 11 Car, obsoleto en Android 11 )
El contenido deActivityView
DEBE contenerFocusParkingView
como la primera vista enfocable, y su atributoapp:shouldRestoreFocus
DEBE serfalse
. - El contenido de
ActivityView
no debe tener vistasandroid:focusByDefault
.
Para el usuario, las vistas de actividad no deberían tener ningún efecto en la navegación, excepto que las áreas de enfoque no pueden abarcar vistas de actividad. En otras palabras, no puede tener un área de enfoque única que tenga contenido dentro y fuera de una 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 compatibles con la rotación, 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 en un automóvil de dos maneras, ya sea usando el controlador giratorio o tocando la pantalla. Al usar el controlador giratorio, se resaltará 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 → táctil. Cuando el usuario toca la pantalla, el resaltado de enfoque desaparece.
- Toque → giratorio. Cuando el usuario empuja, gira o presiona el botón central, aparece el resaltado de enfoque.
Los botones Atrás y Inicio no tienen 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 usar OnTouchModeChangeListener
para escuchar los cambios. Si bien esto se puede usar para personalizar su interfaz de usuario para el modo de entrada actual, evite cambios importantes, ya que pueden ser desconcertantes.
Solución de problemas
En una aplicación diseñada para tocar, no es raro tener vistas enfocables anidadas. Por ejemplo, puede haber un FrameLayout
alrededor de un ImageButton
, ambos enfocables. Esto no hace daño al tacto, pero puede resultar en una experiencia de usuario deficiente para el rotativo porque el usuario debe rotar el controlador dos veces para pasar a la siguiente vista interactiva. Para una buena experiencia de usuario, Google recomienda que se pueda enfocar la vista exterior o la interior, pero no ambas.
Si un botón o interruptor pierde el foco cuando se presiona a través del controlador giratorio, se puede aplicar 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 el interruptor y hacer que el contenedor se pueda enfocar en lugar del botón o el interruptor. (El detector de clics debe estar en el contenedor).
- Deje el estado
- El botón o interruptor está siendo reemplazado. Por ejemplo, la acción realizada cuando se presiona el botón o se alterna el interruptor puede desencadenar una actualización de las acciones disponibles, lo que hace que los nuevos botones reemplacen a los existentes. Hay dos formas de abordar esto:
- En lugar de crear un nuevo botón o interruptor, configure el icono y/o el texto del botón o interruptor existente.
- Como arriba, agregue un contenedor enfocable alrededor del botón o interruptor.
Patio de recreo giratorio
RotaryPlayground
es una aplicación de referencia para rotativo. Úselo para aprender a integrar funciones rotativas en sus aplicaciones. RotaryPlayground
se incluye en compilaciones de emuladores y en compilaciones para dispositivos que ejecutan Android Automotive OS (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 la navegación por las áreas de enfoque, omitiendo los elementos no enfocables y la entrada de texto.
- Manipulación directa. Pruebe widgets que admitan el modo de manipulación directa simple y avanzada. Esta pestaña es específicamente para la 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 las ventanas del sistema donde solo se admite el modo de manipulación directa simple.
- Red. Pruebe la navegación rotatoria de patrón z con desplazamiento.
- Notificación. Pruebe la entrada y salida de notificaciones emergentes.
- Desplazarse. Pruebe el desplazamiento a través de una combinación de contenido enfocable y no enfocable.
- WebView. Pruebe la navegación a través de enlaces en un
WebView
. -
FocusArea
personalizada. Pruebe la personalizaciónFocusArea
:- Envolver alrededor.
-
android:focusedByDefault
yapp:defaultFocus
. - Empuje de objetivos explícitos.
- Empujar atajos.
-
FocusArea
sin vistas enfocables.