Développer des applications

Le contenu suivant est destiné aux développeurs d'applications.

Pour que votre application soit compatible avec les commandes rotatives, vous devez:

  1. Placez un FocusParkingView dans la mise en page de l'activité correspondante.
  2. Assurez-vous que les vues peuvent (ou ne peuvent pas) être sélectionnées.
  3. Utilisez des FocusArea pour encapsuler toutes vos vues sélectionnables, à l'exception de FocusParkingView.

Chacune de ces tâches est détaillée ci-dessous, une fois que vous avez configuré votre environnement pour développer des applications compatibles avec les dispositifs rotatifs.

Configurer un contrôleur rotatif

Avant de pouvoir commencer à développer des applications compatibles avec les commandes rotatives, vous avez besoin d'un contrôleur rotatif ou d'un appareil de substitution. Vous avez les options décrites ci-dessous.

Émulateur

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

Vous pouvez également utiliser aosp_car_x86_64-userdebug.

Pour accéder au contrôleur rotatif émulé:

  1. Appuyez sur les trois points en bas de la barre d'outils:

    Accéder au contrôleur rotatif émulé
    Figure 1. Accéder au contrôleur rotatif émulé
  2. Sélectionnez Commande rotative de la voiture dans la fenêtre des commandes avancées:

    Sélectionner "Car rotary" (Rotatif pour voiture)
    Figure 2. Sélectionnez "Rotatif pour voiture".

Clavier USB

  • Connectez un clavier USB à votre appareil équipé d'Android Automotive OS (AAOS). Dans certains cas, cela empêche l'affichage du clavier à l'écran.
  • Utilisez une version userdebug ou eng.
  • Activez le filtrage des événements clés:
    adb shell settings put secure android.car.ROTARY_KEY_EVENT_FILTER 1
    
  • Consultez le tableau ci-dessous pour trouver la clé correspondante à chaque action:
    Clé Action rotative
    Q Faire pivoter dans le sens inverse des aiguilles d'une montre
    E Faire pivoter dans le sens des aiguilles d'une montre
    A Déplacer vers la gauche
    D Déplacer vers la droite
    W Déplacer vers le haut
    S Déplacer vers le bas
    F ou virgule Bouton central
    R ou Échap Bouton Retour

Commandes ADB

Vous pouvez utiliser des commandes car_service pour injecter des événements de saisie par dispositif rotatif. Ces commandes peuvent être exécutées sur des appareils exécutant Android Automotive OS (AAOS) ou sur un émulateur.

Commandes car_service Saisie par dispositif rotatif
adb shell cmd car_service inject-rotary Faire pivoter dans le sens inverse des aiguilles d'une montre
adb shell cmd car_service inject-rotary -c true Faire pivoter dans le sens des aiguilles d'une montre
adb shell cmd car_service inject-rotary -dt 100 50 Faire pivoter dans le sens inverse des aiguilles d'une montre plusieurs fois (il y a 100 ms et 50 ms)
adb shell cmd car_service inject-key 282 Déplacer vers la gauche
adb shell cmd car_service inject-key 283 Déplacer vers la droite
adb shell cmd car_service inject-key 280 Déplacer vers le haut
adb shell cmd car_service inject-key 281 Déplacer vers le bas
adb shell cmd car_service inject-key 23 Clic sur le bouton central
adb shell input keyevent inject-key 4 Clic sur le bouton Retour

Contrôleur rotatif OEM

Lorsque le matériel de votre contrôleur rotatif est opérationnel, il s'agit de l'option la plus réaliste. Il est particulièrement utile pour tester la rotation rapide.

FocusParkingView

FocusParkingView est une vue transparente dans la bibliothèque d'UI pour voitures (car-ui-library). RotaryService l'utilise pour prendre en charge la navigation avec un contrôleur rotatif. FocusParkingView doit être la première vue sélectionnable dans la mise en page. Il doit être placé en dehors de tous les FocusArea. Chaque fenêtre doit comporter un élément FocusParkingView. Si vous utilisez déjà la mise en page de base de la bibliothèque car-ui, qui contient un FocusParkingView, vous n'avez pas besoin d'ajouter un autre FocusParkingView. Vous trouverez ci-dessous un exemple de FocusParkingView dans 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>

Voici pourquoi vous avez besoin d'un FocusParkingView:

  1. Android ne supprime pas automatiquement le focus lorsqu'il est défini sur une autre fenêtre. Si vous essayez de supprimer la sélection dans la fenêtre précédente, Android recentre une vue dans cette fenêtre, ce qui entraîne la sélection de deux fenêtres simultanément. Ajouter un FocusParkingView à chaque fenêtre peut résoudre ce problème. Cette vue est transparente et son surlignage par défaut est désactivé, de sorte qu'elle est invisible pour l'utilisateur, que le focus soit activé ou non. Il peut prendre le focus afin que RotaryService puisse le mettre en veille pour supprimer la surbrillance.
  2. S'il n'y a qu'un seul FocusArea dans la fenêtre actuelle, la rotation du contrôleur dans FocusArea entraîne le déplacement du focus de la vue de droite vers la vue de gauche (et inversement) par RotaryService. Ajouter cette vue à chaque fenêtre peut résoudre le problème. Lorsque RotaryService détermine que la cible de sélection est un FocusParkingView, il peut déterminer qu'un retour à la ligne est sur le point de se produire, auquel cas il évite le retour à la ligne en ne déplaçant pas la sélection.
  3. Lorsque le bouton rotatif lance une application, Android met en surbrillance la première vue sélectionnable, qui est toujours FocusParkingView. FocusParkingView détermine la vue optimale à mettre en surbrillance, puis applique la mise au point.

Vues sélectionnables

RotaryService s'appuie sur le concept existant de mise au point de la vue du framework Android, qui remonte à l'époque où les téléphones étaient dotés de claviers physiques et de pavés directionnels. L'attribut android:nextFocusForward existant est réutilisé pour les éléments rotatifs (voir la section Personnalisation de FocusArea), mais android:nextFocusLeft, android:nextFocusRight, android:nextFocusUp et android:nextFocusDown ne le sont pas.

RotaryService ne se concentre que sur les vues pouvant être sélectionnées. Certaines vues, telles que les Button, peuvent généralement être sélectionnées. D'autres, comme les TextView et les ViewGroup, ne le sont généralement pas. Les vues cliquables peuvent être sélectionnées automatiquement et sont automatiquement cliquables lorsqu'elles disposent d'un écouteur de clics. Si cette logique automatique permet d'obtenir la sélection souhaitée, vous n'avez pas besoin de définir explicitement la sélection de la vue. Si la logique automatique ne permet pas d'obtenir la sélection souhaitée, définissez l'attribut android:focusable sur true ou false, ou définissez de manière programmatique la sélection de la vue avec View.setFocusable(boolean). Pour que RotaryService puisse s'y concentrer, une vue DOIT répondre aux exigences suivantes:

  • Sélectionnable
  • Activé
  • Visible
  • avoir des valeurs non nulles pour la largeur et la hauteur ;

Si une vue ne répond pas à toutes ces exigences (par exemple, un bouton pouvant être sélectionné mais désactivé), l'utilisateur ne peut pas utiliser le dispositif rotatif pour la sélectionner. Si vous souhaitez vous concentrer sur les vues désactivées, envisagez d'utiliser un état personnalisé plutôt que android:state_enabled pour contrôler l'affichage de la vue sans indiquer qu'Android doit la considérer comme désactivée. Votre application peut informer l'utilisateur de la raison pour laquelle la vue est désactivée lorsqu'il appuie dessus. La section suivante explique comment procéder.

État personnalisé

Pour ajouter un état personnalisé:

  1. Pour ajouter un attribut personnalisé à votre vue. Par exemple, pour ajouter un état personnalisé state_rotary_enabled à la classe de vue CustomView, utilisez:
    <declare-styleable name="CustomView">
        <attr name="state_rotary_enabled" format="boolean" />
    </declare-styleable>
    
  2. Pour suivre cet état, ajoutez une variable d'instance à votre vue, ainsi que des méthodes d'accès:
    private boolean mRotaryEnabled;
    public boolean getRotaryEnabled() { return mRotaryEnabled; }
    public void setRotaryEnabled(boolean rotaryEnabled) {
        mRotaryEnabled = rotaryEnabled;
    }
    
  3. Pour lire la valeur de votre attribut lorsque votre vue est créée:
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
    mRotaryEnabled = a.getBoolean(R.styleable.CustomView_state_rotary_enabled);
    
  4. Dans votre classe de vue, remplacez la méthode onCreateDrawableState(), puis ajoutez l'état personnalisé, le cas échéant. Exemple :
    @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. Faites en sorte que le gestionnaire de clics de votre vue s'exécute différemment en fonction de son état. Par exemple, le gestionnaire de clics peut ne rien faire ou afficher un toast lorsque mRotaryEnabled est false.
  6. Pour que le bouton apparaisse désactivé, dans le drawable d'arrière-plan de votre vue, utilisez app:state_rotary_enabled au lieu de android:state_enabled. Si vous ne l'avez pas déjà fait, vous devez ajouter les éléments suivants:
    xmlns:app="http://schemas.android.com/apk/res-auto"
    
  7. Si votre vue est désactivée dans certaines mises en page, remplacez android:enabled="false" par app:state_rotary_enabled="false", puis ajoutez l'espace de noms app, comme ci-dessus.
  8. Si votre vue est désactivée par programmation, remplacez les appels à setEnabled() par des appels à setRotaryEnabled().

FocusArea

Utilisez FocusAreas pour partitionner les vues sélectionnables en blocs afin de faciliter la navigation et d'être cohérent avec les autres applications. Par exemple, si votre application comporte une barre d'outils, elle doit se trouver dans un FocusArea distinct du reste de l'application. Les barres d'onglets et les autres éléments de navigation doivent également être séparés du reste de l'application. Les grandes listes doivent généralement avoir leur propre FocusArea. Sinon, les utilisateurs doivent faire défiler la liste complète pour accéder à certaines vues.

FocusArea est une sous-classe de LinearLayout dans la bibliothèque car-ui. Lorsque cette fonctionnalité est activée, FocusArea dessine un surlignage lorsqu'un de ses descendants est sélectionné. Pour en savoir plus, consultez la section Personnaliser les surbrillances de mise au point.

Lorsque vous créez un bloc de navigation dans le fichier de mise en page, si vous prévoyez d'utiliser un LinearLayout comme conteneur pour ce bloc, utilisez plutôt FocusArea. Sinon, encapsulez le bloc dans un FocusArea.

NE PAS imbriquer un FocusArea dans un autre FocusArea. Cela entraîne un comportement de navigation non défini. Assurez-vous que toutes les vues pouvant être sélectionnées sont imbriquées dans un FocusArea.

Voici un exemple de FocusArea dans 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 fonctionne comme suit:

  1. Lors de la gestion des actions de rotation et de pincement, RotaryService recherche des instances de FocusArea dans la hiérarchie des vues.
  2. Lorsqu'il reçoit un événement de rotation, RotaryService déplace la sélection vers une autre vue pouvant être sélectionnée dans le même FocusArea.
  3. Lorsqu'il reçoit un événement de rappel, RotaryService déplace le focus vers une autre vue pouvant être sélectionnée dans un autre FocusArea (généralement adjacent).

Si vous n'incluez aucun FocusAreas dans votre mise en page, la vue racine est traitée comme une zone de sélection implicite. L'utilisateur ne peut pas effectuer de pression pour naviguer dans l'application. Au lieu de cela, il fait défiler toutes les vues pouvant être sélectionnées, ce qui peut être adapté aux boîtes de dialogue.

Personnalisation de FocusArea

Vous pouvez utiliser deux attributs de vue standards pour personnaliser la navigation par dispositif rotatif:

  • android:nextFocusForward permet aux développeurs d'applications de spécifier l'ordre de rotation dans une zone de mise au point. Il s'agit du même attribut que celui utilisé pour contrôler l'ordre des tabulations pour la navigation au clavier. N'utilisez PAS cet attribut pour créer une boucle. Utilisez plutôt app:wrapAround (voir ci-dessous) pour créer une boucle.
  • android:focusedByDefault permet aux développeurs d'applications de spécifier la vue de focus par défaut dans la fenêtre. N'utilisez PAS cet attribut et app:defaultFocus (voir ci-dessous) dans le même FocusArea.

FocusArea définit également certains attributs pour personnaliser la navigation par dispositif rotatif. Vous ne pouvez pas personnaliser les zones de focus implicites avec ces attributs.

  1. (Android 11 QPR3, Android 11 Car, Android 12)
    app:defaultFocus peut être utilisé pour spécifier l'ID d'une vue descendante sélectionnable, qui doit être sélectionnée lorsque l'utilisateur effectue un geste de pression sur cette FocusArea.
  2. (Android 11 QPR3, Android 11 Car, Android 12)
    app:defaultFocusOverridesHistory peut être défini sur true pour que la vue spécifiée ci-dessus soit sélectionnée, même si l'historique indique qu'une autre vue de cette FocusArea était sélectionnée.
  3. (Android 12)
    Utilisez app:nudgeLeftShortcut, app:nudgeRightShortcut, app:nudgeUpShortcut et app:nudgeDownShortcut pour spécifier l'ID d'une vue descendante sélectionnable, qui doit être sélectionnée lorsque l'utilisateur effectue un léger mouvement dans une direction donnée. Pour en savoir plus, consultez la section sur les raccourcis de rappel ci-dessous.

    (Android 11 QPR3, Android 11 Car, obsolète dans Android 12) app:nudgeShortcut et app:nudgeShortcutDirection ne prenaient en charge qu'un seul raccourci de pression.

  4. (Android 11 QPR3, Android 11 Car, Android 12)
    Pour activer la rotation dans ce FocusArea, app:wrapAround peut être défini sur true. Cette option est généralement utilisée lorsque les vues sont disposées en cercle ou en ovale.
  5. (Android 11 QPR3, Android 11 Car, Android 12)
    Pour ajuster la marge intérieure de la mise en surbrillance dans ce FocusArea, utilisez app:highlightPaddingStart, app:highlightPaddingEnd, app:highlightPaddingTop, app:highlightPaddingBottom, app:highlightPaddingHorizontal et app:highlightPaddingVertical.
  6. (Android 11 QPR3, Android 11 Car, Android 12)
    Pour ajuster les limites perçues de cet élément FocusArea afin de trouver une cible de nudge, utilisez app:startBoundOffset, app:endBoundOffset, app:topBoundOffset, app:bottomBoundOffset, app:horizontalBoundOffset et app:verticalBoundOffset.
  7. (Android 11 QPR3, Android 11 Car, Android 12)
    Pour spécifier explicitement l'ID d'un FocusArea (ou d'une zone) adjacent dans les itinéraires donnés, utilisez app:nudgeLeft, app:nudgeRight, app:nudgeUp et app:nudgeDown. Utilisez cette option lorsque la recherche géométrique utilisée par défaut ne trouve pas la cible souhaitée.

Les nudges permettent généralement de naviguer entre les zones de focus. Toutefois, avec les raccourcis de rappel, le rappel permet parfois d'abord de naviguer dans un FocusArea, de sorte que l'utilisateur peut avoir besoin de le faire deux fois pour accéder au FocusArea suivant. Les raccourcis de nudge sont utiles lorsqu'un FocusArea contient une longue liste suivie d'un bouton d'action flottant, comme dans l'exemple ci-dessous:

Raccourci pour envoyer un rappel
Figure 3. Raccourci pour le rappel

Sans le raccourci de rappel, l'utilisateur devrait faire défiler toute la liste pour atteindre le bouton d'action flottant.

Personnalisation de la mise en surbrillance du focus

Comme indiqué ci-dessus, RotaryService s'appuie sur le concept existant de ciblage de vue du framework Android. Lorsque l'utilisateur fait pivoter et ajuste l'écran, RotaryService déplace le focus, en mettant en surbrillance une vue et en désactivant une autre. Dans Android, lorsqu'une vue est sélectionnée, si la vue:

  • A spécifié son propre surbrillance de sélection. Android dessine la surbrillance de sélection de la vue.
  • Si vous ne spécifiez pas de surbrillance de sélection et que la surbrillance de sélection par défaut n'est pas désactivée, Android dessine la surbrillance de sélection par défaut pour la vue.

Les applications conçues pour l'écran tactile ne spécifient généralement pas les surbrillances de mise au point appropriées.

La mise en surbrillance de l'élément sélectionné par défaut est fournie par le framework Android et peut être remplacée par l'OEM. Les développeurs d'applications le reçoivent lorsque le thème qu'ils utilisent est dérivé de Theme.DeviceDefault.

Pour une expérience utilisateur cohérente, utilisez autant que possible la mise en surbrillance par défaut. Si vous avez besoin d'un surlignage de mise au point de forme personnalisée (par exemple, ronde ou en forme de pilule) ou si vous utilisez un thème non dérivé de Theme.DeviceDefault, utilisez les ressources de la bibliothèque d'UI de voiture pour spécifier votre propre surlignage de mise au point pour chaque vue.

Pour spécifier un surlignage personnalisé pour une vue, remplacez le drawable d'arrière-plan ou de premier plan de la vue par un drawable qui diffère lorsque la vue est sélectionnée. En règle générale, vous devez modifier l'arrière-plan. Si vous utilisez le drawable suivant comme arrière-plan d'une vue carrée, il produit un surlignage de mise au point rond:

<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) Les références de ressources en gras dans l'exemple ci-dessus identifient les ressources définies par la bibliothèque car-ui. L'OEM les remplace pour qu'ils soient cohérents avec la mise en surbrillance de l'élément sélectionné par défaut qu'il spécifie. Cela garantit que la couleur de surbrillance de l'élément sélectionné, la largeur du trait, etc. ne changent pas lorsque l'utilisateur passe d'une vue avec une surbrillance personnalisée à une vue avec la surbrillance par défaut. Le dernier élément est une ondulation utilisée pour le toucher. Les valeurs par défaut utilisées pour les ressources en gras s'affichent comme suit:

Valeurs par défaut pour les ressources en gras
Figure 4. Valeurs par défaut pour les ressources en gras

De plus, une mise en surbrillance personnalisée est appelée lorsqu'une couleur d'arrière-plan unie est appliquée à un bouton pour attirer l'attention de l'utilisateur, comme dans l'exemple ci-dessous. Cela peut rendre le surlignage de l'élément sélectionné difficile à voir. Dans ce cas, spécifiez un surlignage personnalisé à l'aide de couleurs secondaires:

Couleur d&#39;arrière-plan unie
  • (Android 11 QPR3, Android 11 Car, 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

Exemple :

Mise au point, pas enfoncé Concentré, pressé
En mode focus, mais non enfoncée En mode ciblé, enfoncé

Défilement par dispositif rotatif

Si votre application utilise des RecyclerView, vous DEVEZ utiliser des CarUiRecyclerView à la place. Cela garantit que votre UI est cohérente avec les autres, car la personnalisation d'un OEM s'applique à tous les CarUiRecyclerView.

Si tous les éléments de votre liste peuvent être sélectionnés, aucune autre action n'est requise. La navigation par dispositif rotatif déplace le focus sur les éléments de la liste, et la liste défile pour rendre l'élément mis en surbrillance visible.

(Android 11 QPR3, Android 11 Car, Android 12)
Si des éléments peuvent être sélectionnés et d'autres non, ou si tous les éléments ne peuvent pas être sélectionnés, vous pouvez activer le défilement par dispositif rotatif, ce qui permet à l'utilisateur d'utiliser le dispositif rotatif pour faire défiler progressivement la liste sans ignorer les éléments non sélectionnables. Pour activer le défilement rotatif, définissez l'attribut app:rotaryScrollEnabled sur true.

(Android 11 QPR3, Android 11 Car, Android 12)
Vous pouvez activer le défilement rotatif dans n'importe quelle vue à faire défiler, y compris avCarUiRecyclerView, avec la méthode setRotaryScrollEnabled() dans CarUiUtils. Dans ce cas, vous devez:

  • Rendre la vue déroulante sélectionnable afin qu'elle puisse être sélectionnée lorsqu'aucune de ses vues descendantes sélectionnables n'est visible
  • Désactivez la mise en surbrillance de l'élément sélectionné par défaut dans la vue à faire défiler en appelant setDefaultFocusHighlightEnabled(false) afin que la vue à faire défiler ne semble pas être sélectionnée.
  • Assurez-vous que la vue déroulante est mise au point avant ses descendants en appelant setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS).
  • Écoutez des MotionEvents avec SOURCE_ROTARY_ENCODER et AXIS_VSCROLL ou AXIS_HSCROLL pour indiquer la distance à faire défiler et la direction (via le signe).

Lorsque le défilement par dispositif rotatif est activé sur un CarUiRecyclerView et que l'utilisateur fait pivoter l'écran vers une zone où aucune vue ne peut être sélectionnée, la barre de défilement passe du gris au bleu, comme pour indiquer qu'elle est sélectionnée. Vous pouvez implémenter un effet similaire si vous le souhaitez.

Les événements MotionEvents sont identiques à ceux générés par une molette de défilement sur une souris, à l'exception de la source.

Mode de manipulation directe

Normalement, les pressions et la rotation permettent de naviguer dans l'interface utilisateur, tandis que les pressions sur le bouton central permettent d'effectuer une action, mais ce n'est pas toujours le cas. Par exemple, si un utilisateur souhaite régler le volume de l'alarme, il peut utiliser le contrôleur rotatif pour accéder au curseur de volume, appuyer sur le bouton central, faire pivoter le contrôleur pour régler le volume de l'alarme, puis appuyer sur le bouton Retour pour revenir à la navigation. Il s'agit du mode manipulation directe (DM). Dans ce mode, le contrôleur rotatif permet d'interagir directement avec la vue plutôt que de naviguer.

Vous pouvez implémenter les DM de deux manières : Si vous ne devez gérer que la rotation et que la vue que vous souhaitez manipuler répond de manière appropriée aux AccessibilityEvent ACTION_SCROLL_FORWARD et ACTION_SCROLL_BACKWARD, utilisez le mécanisme simple. Sinon, utilisez le mécanisme avancé.

Le mécanisme simple est la seule option disponible dans les fenêtres système. Les applications peuvent utiliser l'un ou l'autre des mécanismes.

Mécanisme simple

(Android 11 QPR3, Android 11 Car, Android 12)
Votre application doit appeler DirectManipulationHelper.setSupportsRotateDirectly(View view, boolean enable). RotaryService reconnaît quand l'utilisateur est en mode DM et passe en mode DM lorsque l'utilisateur appuie sur le bouton Center (Centre) alors qu'une vue est sélectionnée. En mode MP, les rotations effectuent ACTION_SCROLL_FORWARD ou ACTION_SCROLL_BACKWARD et quittent le mode MP lorsque l'utilisateur appuie sur le bouton "Retour". Le mécanisme simple active et désactive l'état sélectionné de la vue lorsque vous accédez et quittez le mode DM.

Pour indiquer visuellement que l'utilisateur est en mode DM, faites en sorte que votre vue apparaisse différente lorsqu'elle est sélectionnée. Par exemple, modifiez l'arrière-plan lorsque android:state_selected est true.

Mécanisme avancé

L'application détermine quand RotaryService passe en mode DM et en sort. Pour une expérience utilisateur cohérente, appuyer sur le bouton central avec une vue de DM en mode focus doit activer le mode DM, et le bouton Retour doit quitter le mode DM. Si le bouton central et/ou le nudge ne sont pas utilisés, ils peuvent être utilisés comme alternative pour quitter le mode DM. Pour les applications telles que Maps, un bouton représentant les DM peut être utilisé pour passer en mode DM.

Pour être compatible avec le mode DM avancé, une vue:

  1. (Android 11 QPR3, Android 11 Car, Android 12) DOIT écouter un événement KEYCODE_DPAD_CENTER pour entrer en mode DM et écouter un événement KEYCODE_BACK pour quitter le mode DM, en appelant DirectManipulationHelper.enableDirectManipulationMode() dans chaque cas. Pour écouter ces événements, effectuez l'une des opérations suivantes :
    • Enregistrez un OnKeyListener.
    • ou :
    • Étendez la vue, puis remplacez sa méthode dispatchKeyEvent().
  2. DOIT écouter les événements de rappel (KEYCODE_DPAD_UP, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_LEFT ou KEYCODE_DPAD_RIGHT) si la vue doit gérer les rappels.
  3. DOIT écouter les MotionEvent et obtenir le nombre de rotations dans AXIS_SCROLL si la vue souhaite gérer la rotation. Pour ce faire, vous pouvez procéder de plusieurs manières :
    1. Enregistrez un OnGenericMotionListener.
    2. Développez la vue et remplacez sa méthode dispatchTouchEvent().
  4. Pour éviter de rester bloqué en mode DM, vous devez quitter ce mode lorsque le fragment ou l'activité à laquelle la vue appartient n'est pas interactif.
  5. DOIT fournir un indice visuel pour indiquer que la vue est en mode DM.

Vous trouverez ci-dessous un exemple de vue personnalisée qui utilise le mode DM pour effectuer un panoramique et un zoom sur une carte:

/** 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(); }

Vous trouverez d'autres exemples dans le projet RotaryPlayground.

ActivityView

Lorsque vous utilisez une ActivityView:

  • ActivityView ne doit pas pouvoir être sélectionné.
  • (Android 11 QPR3, Android 11 Car, obsolète dans Android 11)
    Le contenu du ActivityView DOIT contenir un FocusParkingView comme première vue sélectionnable, et son attribut app:shouldRestoreFocus DOIT être false.
  • Le contenu de ActivityView ne doit pas comporter de vues android:focusByDefault.

Pour l'utilisateur, les ActivityViews ne doivent avoir aucun effet sur la navigation, sauf que les zones de focus ne peuvent pas s'étendre sur les ActivityViews. En d'autres termes, vous ne pouvez pas avoir une seule zone de mise au point contenant du contenu à l'intérieur et en dehors d'un ActivityView. Si vous n'ajoutez pas de FocusAreas à votre ActivityView, la racine de la hiérarchie des vues dans la ActivityView est considérée comme une zone de focus implicite.

Boutons qui fonctionnent lorsqu'ils sont maintenus enfoncés

La plupart des boutons déclenchent une action lorsqu'ils sont cliqués. Certains boutons fonctionnent en revanche lorsque vous les maintenez enfoncés. Par exemple, les boutons d'avance rapide et de retour en arrière fonctionnent généralement lorsque vous les maintenez enfoncés. Pour que ces boutons soient compatibles avec les boutons rotatifs, écoutez KEYCODE_DPAD_CENTER KeyEvents comme suit:

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;
});

Dans ce cas, mRunnable effectue une action (par exemple, le retour en arrière) et se planifie pour être exécuté après un délai.

Mode tactile

Les utilisateurs peuvent interagir avec l'unité principale d'une voiture de deux manières : à l'aide d'un contrôleur rotatif ou en appuyant sur l'écran. Lorsque vous utilisez le contrôleur rotatif, l'une des vues pouvant être sélectionnées est mise en surbrillance. Lorsque vous appuyez sur l'écran, aucun surlignage de sélection ne s'affiche. L'utilisateur peut passer d'un mode de saisie à l'autre à tout moment:

  • Cadran → tactile. Lorsque l'utilisateur appuie sur l'écran, le surlignage de l'élément en cours de sélection disparaît.
  • Appuyez sur → rotatif. Lorsque l'utilisateur pousse, fait pivoter ou appuie sur le bouton central, le surlignage de la zone de focus s'affiche.

Les boutons Retour et Accueil n'ont aucun effet sur le mode de saisie.

Le dispositif rotatif s'appuie sur le concept existant d'mode tactile d'Android. Vous pouvez utiliser View.isInTouchMode() pour déterminer le mode de saisie utilisé par l'utilisateur. Vous pouvez utiliser OnTouchModeChangeListener pour écouter les modifications. Bien que vous puissiez utiliser cette fonctionnalité pour personnaliser votre interface utilisateur pour le mode de saisie actuel, évitez toute modification majeure, car elle peut être déconcertante.

Dépannage

Dans une application conçue pour l'écran tactile, il est courant d'avoir des vues enfouies pouvant être sélectionnées. Par exemple, il peut y avoir un FrameLayout autour d'un ImageButton, qui sont tous deux sélectionnables. Cela n'a aucun impact sur l'écran tactile, mais cela peut entraîner une mauvaise expérience utilisateur pour les commandes rotatives, car l'utilisateur doit faire pivoter le contrôleur deux fois pour passer à la vue interactive suivante. Pour une bonne expérience utilisateur, Google vous recommande de rendre la vue extérieure ou la vue intérieure sélectionnable, mais pas les deux.

Si un bouton ou un bouton-poussoir perd la sélection lorsqu'il est enfoncé à l'aide du contrôleur rotatif, l'une des conditions suivantes peut s'appliquer:

  • Le bouton ou l'interrupteur est désactivé (brièvement ou indéfiniment) en raison de l'appui sur le bouton. Dans les deux cas, vous pouvez résoudre ce problème de deux manières :
    • Laissez l'état android:enabled défini sur true et utilisez un état personnalisé pour griser le bouton ou le bouton d'activation/de désactivation, comme décrit dans la section État personnalisé.
    • Entourez le bouton ou le bouton bascule avec un conteneur et rendez-le sélectionnable au lieu du bouton ou du bouton bascule. (L'écouteur de clic doit se trouver sur le conteneur.)
  • Le bouton ou le contacteur est en cours de remplacement. Par exemple, l'action effectuée lorsque le bouton est enfoncé ou que le bouton bascule est activé peut déclencher une actualisation des actions disponibles, ce qui entraîne le remplacement des boutons existants par de nouveaux boutons. Pour cela, vous pouvez procéder de deux façons :
    • Au lieu de créer un bouton ou un bouton bascule, définissez l'icône et/ou le texte du bouton ou du bouton bascule existant.
    • Comme ci-dessus, ajoutez un conteneur sélectionnable autour du bouton ou du bouton bascule.

RotaryPlayground

RotaryPlayground est une application de référence pour les dispositifs rotatifs. Utilisez-le pour découvrir comment intégrer des fonctionnalités rotatives à vos applications. RotaryPlayground est inclus dans les builds de l'émulateur et dans les builds pour les appareils exécutant Android Automotive OS (AAOS).

  • Dépôt RotaryPlayground: packages/apps/Car/tests/RotaryPlayground/
  • Versions: Android 11 QPR3, Android 11 Car et Android 12

L'application RotaryPlayground affiche les onglets suivants sur la gauche:

  • Fiches Testez la navigation dans les zones de focus, en ignorant les éléments non sélectionnables et la saisie de texte.
  • Manipulation directe Testez les widgets compatibles avec le mode de manipulation directe simple et avancé. Cet onglet est spécifiquement destiné à la manipulation directe dans la fenêtre de l'application.
  • Manipulation de l'UI système Testez les widgets compatibles avec la manipulation directe dans les fenêtres système où seul le mode de manipulation directe simple est pris en charge.
  • Grille Tester la navigation par dispositif rotatif en forme de Z avec le défilement
  • Notification. Testez l'activation et la désactivation des notifications prioritaires.
  • Faites défiler l'écran. Testez le défilement d'un mélange de contenus pouvant être sélectionnés et non sélectionnables.
  • WebView Testez la navigation via des liens dans un WebView.
  • FocusArea personnalisée Testez la personnalisation de FocusArea :
    • Passe à une main sur le côté.
    • android:focusedByDefault et app:defaultFocus
    • .
    • Cibles de rappel explicites.
    • Raccourcis de rappel.
    • FocusArea sans vue sélectionnable.