Desarrollo de aplicaciones

El siguiente material es para desarrolladores de aplicaciones.

Para que su aplicación sea compatible con la rotación, DEBE:

  1. Coloque un FocusParkingView en el diseño de actividad respectivo.
  2. Asegúrese de que las vistas sean (o no) enfocables.
  3. Use FocusArea s para envolver todas sus vistas enfocables, excepto FocusParkingView .

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:

  1. Toca los tres puntos en la parte inferior de la barra de herramientas:

    Acceder al controlador giratorio emulado
    Figura 1. Acceso al controlador giratorio emulado
  2. Seleccione Coche giratorio en la ventana de controles ampliados:

    Seleccionar coche giratorio
    Figura 2. Select Car rotativo

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 o eng .
  • 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 :

  1. 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 que RotaryService pueda estacionar el foco en él para eliminar el resaltado del foco.
  2. Si solo hay un FocusArea en la ventana actual, girar el controlador en FocusArea hace que RotaryService 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. Cuando RotaryService determina que el objetivo del foco es un FocusParkingView , puede determinar que está a punto de ocurrir una vuelta y en ese momento evita la vuelta al no mover el foco.
  3. 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:

  1. Para agregar un atributo personalizado a su vista. Por ejemplo, para agregar un estado personalizado state_rotary_enabled a la clase de vista CustomView , use:
    <declare-styleable name="CustomView">
        <attr name="state_rotary_enabled" format="boolean" />
    </declare-styleable>
    
  2. 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;
    }
    
  3. 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);
    
  4. 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;
    }
    
  5. 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 sea false .
  6. Para que el botón aparezca deshabilitado, en el fondo dibujable de su vista, use app:state_rotary_enabled en lugar de android:state_enabled . Si aún no lo tiene, deberá agregar:
    xmlns:app="http://schemas.android.com/apk/res-auto"
    
  7. Si su vista está deshabilitada en cualquier diseño, reemplace android:enabled="false" con app:state_rotary_enabled="false" y luego agregue el espacio de nombres app , como se indicó anteriormente.
  8. Si su vista está deshabilitada mediante programación, reemplace las llamadas a setEnabled() con llamadas a setRotaryEnabled() .

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:

  1. Al manejar acciones de rotación y empuje, RotaryService busca instancias de FocusArea en la jerarquía de vistas.
  2. Al recibir un evento de rotación, RotaryService mueve el foco a otra Vista que puede enfocarse en la misma FocusArea .
  3. Al recibir un evento de empujón, RotaryService mueve el foco a otra vista que puede enfocarse en otra FocusArea (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, use app: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 y app:defaultFocus (vea a continuación) en la misma FocusArea .

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.

  1. ( 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 este FocusArea .
  2. ( Android 11 QPR3, Android 11 Coche, Android 12 )
    app:defaultFocusOverridesHistory se puede establecer en true para que la vista especificada anteriormente tome el foco, incluso si con el historial para indicar que se ha enfocado otra vista en esta 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, consulte el contenido de los atajos de empuje a continuación.

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

  4. ( Android 11 QPR3, Android 11 Coche, Android 12 )
    Para permitir que la rotación se ajuste en esta FocusArea , app:wrapAround se puede establecer en true . Esto se suele utilizar cuando las vistas se organizan en un círculo o un óvalo.
  5. ( Android 11 QPR3, Android 11 Coche, Android 12 )
    Para ajustar el relleno del resaltado en esta FocusArea , use app:highlightPaddingStart , app:highlightPaddingEnd , app:highlightPaddingTop , app:highlightPaddingBottom , app:highlightPaddingHorizontal y app:highlightPaddingVertical .
  6. ( Android 11 QPR3, Android 11 Coche, Android 12 )
    Para ajustar los límites percibidos de esta FocusArea para encontrar un objetivo de empuje, use app:startBoundOffset , app:endBoundOffset , app:topBoundOffset , app:bottomBoundOffset , app:horizontalBoundOffset y app:verticalBoundOffset .
  7. ( Android 11 QPR3, Android 11 Coche, Android 12 )
    Para especificar explícitamente el ID de un FocusArea (o áreas) adyacente en las direcciones dadas, use app:nudgeLeft , app:nudgeRight , app:nudgeUp y app: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:

Atajo de empujón
Figura 3. Atajo de empuje

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:

Valores predeterminados para recursos en negrita
Figura 4. Valores predeterminados para recursos en negrita

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 :

Color de fondo sólido
  • ( 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 presionadoEnfocado, presionado
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 y AXIS_VSCROLL o AXIS_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:

  1. ( Android 11 QPR3, Android 11 Car, Android 12 ) DEBE escuchar un evento KEYCODE_DPAD_CENTER para ingresar al modo DM y escuchar un evento KEYCODE_BACK para salir del modo DM, llamando a DirectManipulationHelper.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() .
  2. DEBERÍA escuchar eventos de empujón ( KEYCODE_DPAD_UP , KEYCODE_DPAD_DOWN , KEYCODE_DPAD_LEFT o KEYCODE_DPAD_RIGHT ) si la vista debe manejar empujones.
  3. DEBERÍA escuchar MotionEvent s y obtener el recuento de rotación en AXIS_SCROLL si la vista quiere manejar la rotación. Hay varias formas de hacer esto:
    1. Registre un OnGenericMotionListener .
    2. Extienda la vista y anule su método dispatchTouchEvent() .
  4. 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.
  5. 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 de ActivityView DEBE contener FocusParkingView como la primera vista enfocable, y su atributo app:shouldRestoreFocus DEBE ser false .
  • El contenido de ActivityView no debe tener vistas android: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 como true 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).
  • 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ón FocusArea :
    • Envolver alrededor.
    • android:focusedByDefault y app:defaultFocus
    • .
    • Empuje de objetivos explícitos.
    • Empujar atajos.
    • FocusArea sin vistas enfocables.