Desarrolla aplicaciones

El siguiente material está destinado a desarrolladores de apps.

Para que tu app admita la rotación, DEBES hacer lo siguiente:

  1. Coloca un FocusParkingView en el diseño de la actividad correspondiente.
  2. Asegúrate de que las vistas puedan enfocarse (o no).
  3. Usa FocusArea para unir todas tus vistas enfocables, excepto FocusParkingView.

Cada una de estas tareas se detalla a continuación, después de que configures tu entorno para desarrollar apps compatibles con pantallas rotativas.

Cómo configurar un control rotativo

Antes de comenzar a desarrollar apps compatibles con controles rotativos, necesitas un control rotativo o un sustituto. Tienes las opciones que se describen a continuación.

Emulador

source build/envsetup.sh && lunch car_x86_64-userdebug
m -j
emulator -wipe-data -no-snapshot -writable-system

También puedes usar aosp_car_x86_64-userdebug.

Para acceder al controlador rotativo emulado, haz lo siguiente:

  1. Presiona los tres puntos que aparecen en la parte inferior de la barra de herramientas:

    Cómo acceder al controlador rotativo emulado
    Figura 1: Accede al control rotativo emulado
  2. Selecciona Car rotary en la ventana de controles extendidos:

    Selecciona Rotatorio de automóviles
    Figura 2. Selecciona Rotatorio para automóviles

Teclado USB

  • Conecta un teclado USB al dispositivo que ejecuta el SO Android Automotive (AAOS). En algunos casos, esto evita que aparezca el teclado en pantalla.
  • Usa una compilación userdebug o eng.
  • Habilita el filtrado de eventos de teclas:
    adb shell settings put secure android.car.ROTARY_KEY_EVENT_FILTER 1
    
  • Consulta la siguiente tabla para encontrar la clave correspondiente a cada acción:
    Clave Acción rotativa
    P Rotar en sentido contrario a las manecillas del reloj
    E Giro en sentido horario
    A Empujar hacia la izquierda
    D Empujar hacia la derecha
    W Empujar hacia arriba
    S Empujar hacia abajo
    F o coma Botón central
    R o Esc Botón Atrás

Comandos de ADB

Puedes usar los comandos car_service para insertar eventos de entrada rotativos. Estos comandos se pueden ejecutar en dispositivos que ejecutan el SO Android Automotive (AAOS) o en un emulador.

Comandos de car_service Entrada rotativa
adb shell cmd car_service inject-rotary Rotar en sentido contrario a las manecillas del reloj
adb shell cmd car_service inject-rotary -c true Giro en sentido horario
adb shell cmd car_service inject-rotary -dt 100 50 Rotar en el sentido contrario a las manecillas del reloj varias veces (hace 100 ms y hace 50 ms)
adb shell cmd car_service inject-key 282 Empujar hacia la izquierda
adb shell cmd car_service inject-key 283 Empujar hacia la derecha
adb shell cmd car_service inject-key 280 Empujar hacia arriba
adb shell cmd car_service inject-key 281 Empujar hacia abajo
adb shell cmd car_service inject-key 23 Clic en el botón central
adb shell input keyevent inject-key 4 Clic en el botón Atrás

Control rotativo OEM

Cuando el hardware del controlador rotativo esté en funcionamiento, esta es la opción más realista. Es especialmente útil para probar la rotación rápida.

FocusParkingView

FocusParkingView es una vista transparente en la biblioteca de IU del vehículo (car-ui-library). RotaryService lo usa para admitir la navegación del controlador rotativo. FocusParkingView debe ser la primera vista que se pueda enfocar en el diseño. Se debe colocar fuera de todos los FocusArea. Cada ventana debe tener un FocusParkingView. Si ya usas el diseño base de car-ui-library, que contiene un FocusParkingView, no es necesario que agregues otro FocusParkingView. A continuación, se muestra un ejemplo de FocusParkingView en RotaryPlayground.

<FrameLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent">
   <com.android.car.ui.FocusParkingView
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>
   <FrameLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent"/>
</FrameLayout>

Estos son los motivos por los que necesitas un FocusParkingView:

  1. Android no borra el enfoque automáticamente cuando se establece en otra ventana. Si intentas borrar el enfoque en la ventana anterior, Android vuelve a enfocar una vista en esa ventana, lo que hace que se enfoquen dos ventanas de forma simultánea. Agregar un FocusParkingView a cada ventana puede solucionar este problema. Esta vista es transparente y su elemento destacado de enfoque predeterminado está inhabilitado, por lo que es invisible para el usuario, independientemente de si está enfocado o no. Puede tomar el enfoque para que RotaryService pueda estacionar el enfoque en él para quitar el elemento destacado de enfoque.
  2. Si solo hay un FocusArea en la ventana actual, girar el controlador en FocusArea hace que RotaryService mueva el enfoque de la vista de la derecha a la vista de la izquierda (y viceversa). Agregar esta vista a cada ventana puede resolver el problema. Cuando RotaryService determina que el objetivo de enfoque es un FocusParkingView, puede determinar que está a punto de producirse un cambio de línea, en cuyo punto evita el cambio de línea si no mueve el enfoque.
  3. Cuando el control rotativo inicia una app, Android enfoca la primera vista enfocable, que siempre es FocusParkingView. FocusParkingView determina la vista óptima en la que enfocarse y, luego, aplica el enfoque.

Vistas enfocables

RotaryService se basa en el concepto de enfoque de vista existente del framework de Android, que se remonta a la época en que los teléfonos tenían teclados físicos y mandos de pad direccional. El atributo android:nextFocusForward existente se redirecciona para dispositivos rotativos (consulta la personalización de FocusArea), pero android:nextFocusLeft, android:nextFocusRight, android:nextFocusUp y android:nextFocusDown no.

RotaryService solo se enfoca en las vistas que se pueden enfocar. Por lo general, algunas vistas, como las Button, se pueden enfocar. Otros, como TextView y ViewGroup, por lo general, no lo son. Las vistas en las que se puede hacer clic se pueden enfocar automáticamente y se puede hacer clic en ellas automáticamente cuando tienen un objeto de escucha de clics. Si esta lógica automática genera la capacidad de enfoque deseada, no es necesario que establezcas explícitamente la capacidad de enfoque de la vista. Si la lógica automática no genera la capacidad de enfoque deseada, configura el atributo android:focusable como true o false, o bien configura de forma programática la capacidad de enfoque de la vista con View.setFocusable(boolean). Para que RotaryService se enfoque en ella, una vista DEBE cumplir con los siguientes requisitos:

  • Enfocable
  • Habilitado
  • Visible
  • Tener valores distintos de cero para el ancho y la altura

Si una vista no cumple con todos estos requisitos, por ejemplo, un botón que se puede enfocar, pero que está inhabilitado, el usuario no puede usar el control rotativo para enfocarlo. Si deseas enfocarte en vistas inhabilitadas, considera usar un estado personalizado en lugar de android:state_enabled para controlar cómo aparece la vista sin indicar que Android debe considerarla inhabilitada. Tu app puede informar al usuario por qué se inhabilitó la vista cuando se presiona. En la siguiente sección, se explica cómo hacerlo.

Estado personalizado

Para agregar un estado personalizado, sigue estos pasos:

  1. Para agregar un atributo personalizado a tu vista. Por ejemplo, para agregar un estado personalizado state_rotary_enabled a la clase de vista CustomView, usa lo siguiente:
    <declare-styleable name="CustomView">
        <attr name="state_rotary_enabled" format="boolean" />
    </declare-styleable>
    
  2. Para hacer un seguimiento de este estado, agrega una variable de instancia a tu vista junto con métodos de acceso:
    private boolean mRotaryEnabled;
    public boolean getRotaryEnabled() { return mRotaryEnabled; }
    public void setRotaryEnabled(boolean rotaryEnabled) {
        mRotaryEnabled = rotaryEnabled;
    }
    
  3. Para leer el valor de tu atributo cuando se crea tu vista, haz lo siguiente:
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
    mRotaryEnabled = a.getBoolean(R.styleable.CustomView_state_rotary_enabled);
    
  4. En tu clase de vista, anula el método onCreateDrawableState() y, luego, agrega el estado personalizado cuando corresponda. Por ejemplo:
    @Override
    protected int[] onCreateDrawableState(int extraSpace) {
        if (mRotaryEnabled) extraSpace++;
        int[] drawableState = super.onCreateDrawableState(extraSpace);
        if (mRotaryEnabled) {
            mergeDrawableStates(drawableState, { R.attr.state_rotary_enabled });
        }
        return drawableState;
    }
    
  5. Haz que el controlador de clics de tu vista se comporte de manera diferente según su estado. Por ejemplo, el controlador de clics podría no hacer nada o mostrar un aviso cuando mRotaryEnabled sea false.
  6. Para que el botón aparezca inhabilitado, en el elemento de diseño de fondo de tu vista, usa app:state_rotary_enabled en lugar de android:state_enabled. Si aún no lo tienes, deberás agregar lo siguiente:
    xmlns:app="http://schemas.android.com/apk/res-auto"
    
  7. Si tu vista está inhabilitada en algún diseño, reemplaza android:enabled="false" por app:state_rotary_enabled="false" y, luego, agrega el espacio de nombres app, como se indicó anteriormente.
  8. Si tu vista está inhabilitada de forma programática, reemplaza las llamadas a setEnabled() por llamadas a setRotaryEnabled().

FocusArea

Usa FocusAreas para particionar las vistas enfocables en bloques para facilitar la navegación y mantener la coherencia con otras apps. Por ejemplo, si tu app tiene una barra de herramientas, esta debe estar en un FocusArea independiente del resto de la app. Las barras de pestañas y otros elementos de navegación también deben estar separados del resto de la app. Por lo general, las listas grandes deben tener su propio FocusArea. De lo contrario, los usuarios deben rotar por toda la lista para acceder a algunas vistas.

FocusArea es una subclase de LinearLayout en car-ui-library. Cuando esta función está habilitada, FocusArea dibuja un elemento destacado cuando se enfoca uno de sus descendientes. Para obtener más información, consulta Personaliza los elementos destacados de enfoque.

Cuando crees un bloque de navegación en el archivo de diseño, si deseas usar un LinearLayout como contenedor para ese bloque, usa FocusArea. De lo contrario, une el bloque en un FocusArea.

NO anides un FocusArea en otro FocusArea. Si lo haces, se producirá un comportamiento de navegación indefinido. Asegúrate de que todas las vistas enfocables estén anidadas dentro de un FocusArea.

A continuación, se muestra un ejemplo de un FocusArea en RotaryPlayground:

<com.android.car.ui.FocusArea
       android:layout_margin="16dp"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:orientation="vertical">
       <EditText
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:singleLine="true">
       </EditText>
   </com.android.car.ui.FocusArea>

FocusArea funciona de la siguiente manera:

  1. Cuando se controlan las acciones de rotación y desplazamiento, RotaryService busca instancias de FocusArea en la jerarquía de vistas.
  2. Cuando recibe un evento de rotación, RotaryService mueve el enfoque a otra vista que puede enfocarse en el mismo FocusArea.
  3. Cuando recibes un evento de empujón, RotaryService mueve el enfoque a otra vista que puede enfocarse en otra FocusArea (por lo general, adyacente).

Si no incluyes ningún FocusAreas en tu diseño, la vista raíz se considera como un área de enfoque implícita. El usuario no puede desplazar el dedo para navegar en la app. En su lugar, se rotarán todas las vistas enfocables, lo que podría ser adecuado para los diálogos.

Personalización de FocusArea

Se pueden usar dos atributos de View estándar para personalizar la navegación rotativa:

  • android:nextFocusForward permite que los desarrolladores de apps especifiquen el orden de rotación en un área de enfoque. Este es el mismo atributo que se usa para controlar el orden de tabulación de la navegación del teclado. NO uses este atributo para crear un bucle. En su lugar, usa app:wrapAround (consulta a continuación) para crear un bucle.
  • android:focusedByDefault permite que los desarrolladores de apps especifiquen la vista de enfoque predeterminada en la ventana. NO uses este atributo ni app:defaultFocus (consulta a continuación) en el mismo FocusArea.

FocusArea también define algunos atributos para personalizar la navegación rotativa. Las áreas de enfoque implícitas no se pueden personalizar con estos atributos.

  1. (Android 11 QPR3, Android 11 Car y Android 12)
    app:defaultFocus se puede usar para especificar el ID de una vista descendiente que se puede enfocar, en la que se debe enfocar cuando el usuario presiona esta FocusArea.
  2. (Android 11 QPR3, Android 11 Car y Android 12)
    app:defaultFocusOverridesHistory se puede establecer en true para que la vista especificada anteriormente se enfoque, incluso si se usa el historial para indicar que se enfocó otra vista en este FocusArea.
  3. (Android 12)
    Usa app:nudgeLeftShortcut, app:nudgeRightShortcut, app:nudgeUpShortcut y app:nudgeDownShortcut para especificar el ID de una vista descendiente enfocable, en la que se debe enfocar cuando el usuario empuja en una dirección determinada. Para obtener más información, consulta el contenido sobre los atajos de sugerencias a continuación.

    (Android 11 QPR3, Android 11 Car, obsoleto en Android 12) app:nudgeShortcut y app:nudgeShortcutDirection solo admitían un atajo de empuje.

  4. (Android 11 QPR3, Android 11 Car y Android 12)
    Para habilitar la rotación en este FocusArea, app:wrapAround se puede establecer en true. Por lo general, se usa cuando las vistas están organizadas en un círculo o un óvalo.
  5. (Android 11 QPR3, Android 11 para vehículos, Android 12)
    Para ajustar el padding del elemento destacado en esta FocusArea, usa app:highlightPaddingStart, app:highlightPaddingEnd, app:highlightPaddingTop, app:highlightPaddingBottom, app:highlightPaddingHorizontal y app:highlightPaddingVertical.
  6. (Android 11 QPR3, Android 11 para vehículos y Android 12)
    Para ajustar los límites percibidos de este FocusArea y encontrar un objetivo de empuje, usa app:startBoundOffset, app:endBoundOffset, app:topBoundOffset, app:bottomBoundOffset, app:horizontalBoundOffset y app:verticalBoundOffset.
  7. (Android 11 QPR3, Android 11 Car y Android 12)
    Para especificar de forma explícita el ID de un FocusArea (o áreas) adyacente en las direcciones determinadas, usa app:nudgeLeft, app:nudgeRight, app:nudgeUp y app:nudgeDown. Úsalo cuando la búsqueda geométrica que se usa de forma predeterminada no encuentre el objetivo deseado.

Por lo general, el empujón navega entre FocusAreas. Sin embargo, con los atajos de empuje, a veces, primero se navega dentro de un FocusArea, por lo que es posible que el usuario deba realizar el empuje dos veces para navegar al siguiente FocusArea. Los atajos de Nudge son útiles cuando un FocusArea contiene una lista larga seguida de un botón de acción flotante, como en el siguiente ejemplo:

Acceso directo a la función Empujar
Figura 3: Acceso directo para enviar un aviso

Sin el atajo de empuje, el usuario tendría que rotar por toda la lista para llegar al BAF.

Personalización de los elementos destacados de enfoque

Como se señaló anteriormente, RotaryService se basa en el concepto existente de enfoque de vista del framework de Android. Cuando el usuario rota y mueve el dispositivo, RotaryService mueve el enfoque, centrando una vista y desenfocando otra. En Android, cuando se enfoca una vista, sucede lo siguiente:

  • Especificó su propio elemento destacado de enfoque. Android dibuja el elemento destacado de enfoque de la vista.
  • Si no se especifica un elemento destacado de enfoque y el elemento destacado de enfoque predeterminado no está inhabilitado, Android dibuja el elemento destacado de enfoque predeterminado para la vista.

Por lo general, las apps diseñadas para la interacción táctil no especifican los elementos destacados de enfoque adecuados.

El framework de Android proporciona el elemento destacado de enfoque predeterminado, que el OEM puede anular. Los desarrolladores de apps lo reciben cuando el tema que usan se deriva de Theme.DeviceDefault.

Para lograr una experiencia del usuario coherente, usa el elemento destacado de enfoque predeterminado siempre que sea posible. Si necesitas un elemento destacado de enfoque con forma personalizada (por ejemplo, redondo o con forma de píldora) o si usas un tema que no se deriva de Theme.DeviceDefault, usa los recursos de car-ui-library para especificar tu propio elemento destacado de enfoque para cada vista.

Para especificar un elemento destacado de enfoque personalizado para una vista, cambia el elemento de diseño de fondo o primer plano de la vista a un elemento de diseño que difiera cuando se enfoque la vista. Por lo general, cambiarías el fondo. Si se usa como fondo de una vista cuadrada, el siguiente elemento de diseño produce un elemento destacado de enfoque redondo:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
   <item android:state_focused="true" android:state_pressed="true">
      <shape android:shape="oval">
         <solid android:color="@color/car_ui_rotary_focus_pressed_fill_color"/>
         <stroke
            android:width="@dimen/car_ui_rotary_focus_pressed_stroke_width"
            android:color="@color/car_ui_rotary_focus_pressed_stroke_color"/>
      </shape>
   </item>
   <item android:state_focused="true">
      <shape android:shape="oval">
         <solid android:color="@color/car_ui_rotary_focus_fill_color"/>
         <stroke
            android:width="@dimen/car_ui_rotary_focus_stroke_width"
            android:color="@color/car_ui_rotary_focus_stroke_color"/>
      </shape>
   </item>
   <item>
      <ripple...>
         ...
      </ripple>
   </item>
</selector>

(Android 11 QPR3, Android 11 Car, Android 12) Las referencias de recursos en negrita en el ejemplo anterior identifican los recursos definidos por car-ui-library. El OEM anula estos elementos para que sean coherentes con el elemento destacado de enfoque predeterminado que especifican. Esto garantiza que el color de la selección de enfoque, el ancho del trazo, etcétera, no cambien cuando el usuario navegue entre una vista con una selección de enfoque personalizada y una vista con la selección de enfoque predeterminada. El último elemento es una onda que se usa para el tacto. Los valores predeterminados que se usan para los recursos en negrita aparecen de la siguiente manera:

Valores predeterminados para los recursos en negrita
Figura 4: Valores predeterminados para recursos en negrita

Además, se llama a un elemento destacado de enfoque personalizado cuando se le asigna un color de fondo sólido a un botón para llamar la atención del usuario, como en el siguiente ejemplo. Esto puede hacer que sea difícil ver el foco destacado. En esta situación, especifica un elemento destacado de enfoque personalizado con colores secundarios:

Color de fondo sólido
  • (Android 11 QPR3, Android 11 Car y Android 12)
    car_ui_rotary_focus_fill_secondary_color
    car_ui_rotary_focus_stroke_secondary_color
  • (Android 12)
    car_ui_rotary_focus_pressed_fill_secondary_color
    car_ui_rotary_focus_pressed_stroke_secondary_color

Por ejemplo:

Enfocado, no presionado Enfocado, presionado
Enfocado, no presionado Enfoque, presión

Desplazamiento rotativo

Si tu app usa RecyclerView, DEBES usar CarUiRecyclerView. Esto garantiza que tu IU sea coherente con las demás, ya que la personalización de un OEM se aplica a todos los CarUiRecyclerView.

Si todos los elementos de la lista son enfocables, no necesitas hacer nada más. La navegación rotativa mueve el enfoque a través de los elementos de la lista y la lista se desplaza para que el elemento enfocado recientemente sea visible.

(Android 11 QPR3, Android 11 para vehículos, Android 12)
Si hay una combinación de elementos que se pueden enfocar y que no se pueden enfocar, o si todos los elementos no se pueden enfocar, puedes habilitar el desplazamiento rotativo, que permite al usuario usar el controlador rotativo para desplazarse gradualmente por la lista sin omitir los elementos que no se pueden enfocar. Para habilitar el desplazamiento rotativo, establece el atributo app:rotaryScrollEnabled en true.

(Android 11 QPR3, Android 11 Car y Android 12)
Puedes habilitar el desplazamiento rotativo en cualquier vista desplazable, incluida avCarUiRecyclerView, con el método setRotaryScrollEnabled() en CarUiUtils. Si lo haces, debes hacer lo siguiente:

  • Haz que la vista desplazable sea enfocable para que se pueda enfocar cuando no se vea ninguna de sus vistas descendientes enfocables.
  • Inhabilita el enfoque de la vista desplazable predeterminado llamando a setDefaultFocusHighlightEnabled(false) para que la vista desplazable no parezca estar enfocada.
  • Para asegurarte de que la vista desplazable se enfoque antes que sus elementos secundarios, llama a setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS).
  • Escucha MotionEvents con SOURCE_ROTARY_ENCODER y AXIS_VSCROLL o AXIS_HSCROLL para indicar la distancia de desplazamiento y la dirección (a través del signo).

Cuando el desplazamiento rotativo está habilitado en un CarUiRecyclerView y el usuario rota a un área donde no hay vistas enfocables, la barra de desplazamiento cambia de gris a azul, como si indicara que la barra de desplazamiento está enfocada. Si lo deseas, puedes implementar un efecto similar.

Los MotionEvents son los mismos que los que genera una rueda de desplazamiento en un mouse, excepto la fuente.

Modo de manipulación directa

Por lo general, los empujones y la rotación navegan por la interfaz de usuario, mientras que las presiones del botón central realizan acciones, aunque no siempre es así. Por ejemplo, si un usuario quiere ajustar el volumen de la alarma, puede usar el controlador rotativo para navegar al control deslizante de volumen, presionar el botón central, girar el controlador para ajustar el volumen de la alarma y, luego, presionar el botón Atrás para volver a la navegación. Esto se conoce como modo de manipulación directa (DM). En este modo, el controlador rotativo se usa para interactuar con la vista directamente en lugar de navegar.

Implementa DM de una de estas dos maneras. Si solo necesitas controlar la rotación y la vista que deseas manipular responde a ACTION_SCROLL_FORWARD y ACTION_SCROLL_BACKWARD AccessibilityEvent de forma adecuada, usa el mecanismo simple. De lo contrario, usa el mecanismo avanzado.

El mecanismo simple es la única opción en las ventanas del sistema. Las apps pueden usar cualquiera de los mecanismos.

Mecanismo simple

(Android 11 QPR3, Android 11 Car y Android 12)
Tu app debe llamar a DirectManipulationHelper.setSupportsRotateDirectly(View view, boolean enable). RotaryService reconoce cuando el usuario está en el modo DM y entra en ese modo cuando el usuario presiona el botón Centro mientras se enfoca una vista. Cuando se está en el modo DM, las rotaciones realizan ACTION_SCROLL_FORWARD o ACTION_SCROLL_BACKWARD y salen del modo DM cuando el usuario presiona el botón Atrás. El mecanismo simple activa o desactiva el estado seleccionado de la vista cuando se ingresa y se sale del modo DM.

Para proporcionar una indicación visual de que el usuario está en el modo DM, haz que tu vista se vea diferente cuando se seleccione. Por ejemplo, cambia el fondo cuando android:state_selected sea true.

Mecanismo avanzado

La app determina cuándo RotaryService entra y sale del modo DM. Para que la experiencia del usuario sea coherente, presionar el botón central con una vista de MD enfocada debería ingresar al modo de MD, y el botón Atrás debería salir del modo de MD. Si no se usan el botón central ni el mensaje de notificación, pueden ser formas alternativas de salir del modo DM. En el caso de apps como Maps, se puede usar un botón para representar el modo DM y entrar en él.

Para admitir el modo de DM avanzado, una vista debe cumplir con los siguientes requisitos:

  1. (Android 11 QPR3, Android 11 Car y Android 12) DEBEN detectar un evento KEYCODE_DPAD_CENTER para ingresar al modo DM y detectar un evento KEYCODE_BACK para salir del modo DM, y llamar a DirectManipulationHelper.enableDirectManipulationMode() en cada caso. Para escuchar estos eventos, realiza una de las siguientes acciones:
    • Registra un OnKeyListener.
    • o
    • Extiende la vista y, luego, anula su método dispatchKeyEvent().
  2. DEBE detectar eventos de sugerencia (KEYCODE_DPAD_UP, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_LEFT o KEYCODE_DPAD_RIGHT) si la vista debe controlar las sugerencias.
  3. DEBE escuchar MotionEvent y obtener el recuento de rotación en AXIS_SCROLL si la vista desea controlar la rotación. Hay varias maneras de hacerlo:
    1. Registra un OnGenericMotionListener.
    2. Extiende la vista y anula su método dispatchTouchEvent().
  4. Para evitar que se bloquee en el modo DM, DEBE salir del modo DM cuando el fragmento o la actividad a la que pertenece la vista no sea interactiva.
  5. DEBE proporcionar una indicación visual para indicar que la vista está en modo DM.

A continuación, se muestra un ejemplo de una vista personalizada que usa el modo DM para desplazar y acercar un mapa:

/** Whether this view is in DM mode. */
private boolean mInDirectManipulationMode;

/** Initializes the view. Called by the constructors. */ private void init() { setOnKeyListener((view, keyCode, keyEvent) -> { boolean isActionUp = keyEvent.getAction() == KeyEvent.ACTION_UP; switch (keyCode) { // Always consume KEYCODE_DPAD_CENTER and KEYCODE_BACK events. case KeyEvent.KEYCODE_DPAD_CENTER: if (!mInDirectManipulationMode && isActionUp) { mInDirectManipulationMode = true; DirectManipulationHelper.enableDirectManipulationMode(this, true); setSelected(true); // visually indicate DM mode } return true; case KeyEvent.KEYCODE_BACK: if (mInDirectManipulationMode && isActionUp) { mInDirectManipulationMode = false; DirectManipulationHelper.enableDirectManipulationMode(this, false); setSelected(false); } return true; // Consume controller nudge events only when in DM mode. // When in DM mode, nudges pan the map. case KeyEvent.KEYCODE_DPAD_UP: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(0f, -10f); return true; case KeyEvent.KEYCODE_DPAD_DOWN: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(0f, 10f); return true; case KeyEvent.KEYCODE_DPAD_LEFT: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(-10f, 0f); return true; case KeyEvent.KEYCODE_DPAD_RIGHT: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(10f, 0f); return true; // Don't consume other key events. default: return false; } });
// When in DM mode, rotation zooms the map. setOnGenericMotionListener(((view, motionEvent) -> { if (!mInDirectManipulationMode) return false; float scroll = motionEvent.getAxisValue(MotionEvent.AXIS_SCROLL); zoom(10 * scroll); return true; })); }
@Override public void onPause() { if (mInDirectManipulationMode) { // To ensure that the user doesn't get stuck in DM mode, disable DM mode // when the fragment is not interactive (e.g., a dialog shows up). mInDirectManipulationMode = false; DirectManipulationHelper.enableDirectManipulationMode(this, false); } super.onPause(); }

Puedes encontrar más ejemplos en el proyecto RotaryPlayground.

ActivityView

Cuando usas un ActivityView, haz lo siguiente:

  • El ActivityView no debe poder enfocarse.
  • (Android 11 QPR3, Android 11 Car, se dio de baja en Android 11)
    El contenido de ActivityView DEBE contener un FocusParkingView como la primera vista enfocable, y su atributo app:shouldRestoreFocus DEBE ser false.
  • El contenido de ActivityView no debe tener vistas de android:focusByDefault.

Para el usuario, ActivityViews no debería tener ningún efecto en la navegación, excepto que las áreas de enfoque no pueden abarcar ActivityViews. En otras palabras, no puedes tener un solo área de enfoque que tenga contenido dentro y fuera de un ActivityView. Si no agregas ningún FocusArea a tu ActivityView, la raíz de la jerarquía de vistas en el ActivityView se considera un área de enfoque implícita.

Botones que funcionan cuando se mantienen presionados

La mayoría de los botones realizan alguna acción cuando se hace clic en ellos. Algunos botones funcionan cuando se mantienen presionados. Por ejemplo, los botones de avance rápido y retroceso suelen funcionar cuando se mantienen presionados. Para que estos botones admitan la rotación, escucha KEYCODE_DPAD_CENTER KeyEvents de la siguiente manera:

mButton.setOnKeyListener((v, keyCode, event) ->
{
    if (keyCode != KEYCODE_DPAD_CENTER) {
        return false;
    }
    if (event.getAction() == ACTION_DOWN) {
        mButton.setPressed(true);
        mHandler.post(mRunnable);
    } else {
        mButton.setPressed(false);
        mHandler.removeCallbacks(mRunnable);
    }
    return true;
});

En el que mRunnable realiza una acción (como rebobinar) y se programa para que se ejecute después de una demora.

Modo táctil

Los usuarios pueden usar un control rotativo para interactuar con la unidad principal de un automóvil de dos maneras: con el control rotativo o con la pantalla táctil. Cuando se usa el controlador rotativo, se destaca una de las vistas enfocables. Cuando se toca la pantalla, no aparece ningún elemento destacado de enfoque. El usuario puede cambiar entre estos modos de entrada en cualquier momento:

  • Rotatorio → táctil. Cuando el usuario toca la pantalla, desaparece el resaltado de enfoque.
  • Táctil → rotativo. Cuando el usuario presiona, rota o presiona el botón Central, aparece el elemento destacado de enfoque.

Los botones Atrás y Página principal no tienen efecto en el modo de entrada.

El control rotativo se basa en el concepto existente de modo táctil de Android. Puedes usar View.isInTouchMode() para determinar qué modo de entrada está usando el usuario. Puedes usar OnTouchModeChangeListener para detectar cambios. Si bien esto se puede usar para personalizar la interfaz de usuario para el modo de entrada actual, evita hacer cambios importantes, ya que pueden ser desconcertantes.

Solución de problemas

En una app diseñada para la interacción táctil, es común tener vistas anidadas que se pueden enfocar. Por ejemplo, puede haber un FrameLayout alrededor de un ImageButton, ambos enfocados. Esto no afecta la función táctil, pero puede generar una mala experiencia del usuario para el control rotativo, ya que el usuario debe rotar el controlador dos veces para pasar a la siguiente vista interactiva. Para brindar una buena experiencia del usuario, Google recomienda que hagas que la vista exterior o la interior sean enfocables, pero no ambas.

Si un botón o interruptor pierde el enfoque cuando se presiona a través del controlador rotativo, es posible que se aplique una de estas condiciones:

  • El botón o interruptor se inhabilita (de forma breve o indefinida) debido a que se presiona. En cualquier caso, existen dos maneras de abordar este problema:
    • Deja el estado android:enabled como true y usa un estado personalizado para inhabilitar el botón o el interruptor, como se describe en Estado personalizado.
    • Usa un contenedor para rodear el botón o el interruptor y haz que el contenedor sea enfocable en lugar del botón o el interruptor. (El objeto de escucha de clics debe estar en el contenedor).
  • Se reemplazará el botón o interruptor. Por ejemplo, la acción que se realiza cuando se presiona el botón o se activa el interruptor puede activar una actualización de las acciones disponibles, lo que hace que los botones nuevos reemplacen a los existentes. Existen dos maneras de abordar este problema:
    • En lugar de crear un botón o interruptor nuevo, configura el ícono o el texto del botón o interruptor existente.
    • Al igual que antes, agrega un contenedor enfocado alrededor del botón o interruptor.

RotaryPlayground

RotaryPlayground es una app de referencia para dispositivos rotativos. Úsalo para aprender a integrar funciones rotativas en tus apps. RotaryPlayground se incluye en las compilaciones del emulador y en las compilaciones para dispositivos que ejecutan el SO Android Automotive (AAOS).

  • Repositorio RotaryPlayground: packages/apps/Car/tests/RotaryPlayground/
  • Versiones: Android 11 QPR3, Android 11 para vehículos y Android 12

La app de RotaryPlayground muestra las siguientes pestañas a la izquierda:

  • Tarjetas. Prueba la navegación por las áreas de enfoque, omitiendo los elementos que no se pueden enfocar y la entrada de texto.
  • Manipulación directa: Prueba widgets que admitan el modo de manipulación directa simple y avanzado. Esta pestaña es específicamente para la manipulación directa dentro de la ventana de la app.
  • Manipulación de la IU de Sys. Prueba widgets que admitan la manipulación directa en ventanas del sistema en las que solo se admite el modo de manipulación directa simple.
  • Cuadrícula. Prueba la navegación rotativa en forma de Z con desplazamiento.
  • Notificación. Prueba la acción de deslizar hacia dentro y hacia fuera las notificaciones emergentes.
  • Desplázate. Prueba el desplazamiento a través de una combinación de contenido que se puede enfocar y que no se puede enfocar.
  • WebView. Prueba la navegación a través de vínculos en un WebView.
  • Personalizado FocusArea. Prueba la personalización de FocusArea:
    • Wrap-around.
    • android:focusedByDefault y app:defaultFocus
    • .
    • Objetivos de los mensajes directos explícitos
    • Combinaciones de teclas de empuje
    • FocusArea sin vistas enfocables.