Gestes à une main avec Compose


À partir de Wear OS 7 (niveau d'API 37), un framework de gestes à une main, ainsi qu'une API faisant partie de Compose pour Wear OS, permettent aux utilisateurs d'interagir avec votre application sans la toucher.

Bien qu'il soit initialement compatible avec les appareils Pixel Watch (Pixel Watch 3 et modèles ultérieurs), le framework est disponible pour tous les OEM. En adoptant cette API, la compatibilité des gestes de votre application s'adapte automatiquement à l'écosystème à mesure que la compatibilité matérielle s'étend.

Pour aider les utilisateurs à découvrir les gestes disponibles sans encombrer l'interface utilisateur, le framework Wear OS fournit des indicateurs de gestes animés. Ces indications visuelles mettent en évidence l'endroit où un geste peut être effectué, tandis que le système gère automatiquement leur cadence d'affichage et leur fréquence de mise en sourdine en fonction des préférences de l'utilisateur.

Gestes et actions compatibles

Le framework de gestes Wear OS est compatible avec deux types de gestes :

  • Action principale (pincer deux fois) : correspond à l'action principale sur un écran, comme répondre à un appel ou activer/désactiver la lecture multimédia.
  • Action de rejet (rotation du poignet) : correspond à la navigation vers l'arrière, au rejet d'une boîte de dialogue ou à l'annulation d'un prompt.

Configurer des gestes dans Compose

Bien que l'API de gestes à une main puisse améliorer votre interface utilisateur, il est important de garder à l'esprit que certains matériels et OEM ne sont pas compatibles avec ces gestes. Si l'API détecte que votre application s'exécute sur l'un de ces appareils non compatibles, la bibliothèque n'effectue automatiquement aucune opération sans affecter les interactions tactiles standards.

Comme pour les comportements Compose standards, vous activez les gestes à une main sur les éléments d'interface utilisateur à l'aide de modificateurs. Vous configurez les gestes de votre application en fonction de l'action à effectuer (principale ou rejet) et d'un gestureId pour coordonner les préférences utilisateur au niveau du système, telles que la cadence d'affichage des indications et la fréquence de mise en sourdine. Vous exprimez cette configuration en créant un OneHandedGestureConfiguration objet. Nous vous recommandons d'utiliser la rememberOneHandedGestureConfiguration fonction pour le créer. Vous pouvez également définir la priorité des gestes dans OneHandedGestureConfiguration.

La fonction rememberOneHandedGestureConfiguration suit l'historique des interactions de l'utilisateur entre les recompositions sans exposer l'état de l'application. Une fois que votre application a créé la configuration, elle doit la transmettre à Modifier.oneHandedGesture sur votre composable interactif.

Pour aider les utilisateurs à découvrir les gestes disponibles, la bibliothèque fournit la méthode OneHandedGestureClickIndicator. Cette méthode fait office de wrapper qui remplace son contenu sous-jacent pour indiquer à l'utilisateur qu'une action de geste est disponible.

Composants interactifs

Pour activer les gestes sur une commande interactive telle qu'un bouton, créez une configuration spécifiant OneHandedGestureAction.Primary et appliquez le modificateur oneHandedGesture. Transmettez le même MutableInteractionSource à la commande et au modificateur afin que les événements de geste émettent un retour visuel sur la commande.

Pour activer l'indicateur de geste, créez et mémorisez une instance de OneHandedGestureClickIndicatorState. Ensuite, pour déclencher le retour visuel, appelez showIndicator dans le rappel onGestureAvailable fourni par le modificateur oneHandedGesture, qui signale au système qu'un événement d'indication s'est produit. Une fois appelé, le composant remplace brièvement son contenu normal par une animation de geste.

var isPlaying by remember { mutableStateOf(false) }
val onClick = { isPlaying = !isPlaying }

val gestureConfig = rememberOneHandedGestureConfiguration(
    action = OneHandedGestureAction.Primary
)
val indicatorState = remember { OneHandedGestureClickIndicatorState() }
val coroutineScope = rememberCoroutineScope()
val interactionSource = remember { MutableInteractionSource() }

Button(
    onClick = onClick,
    interactionSource = interactionSource,
    modifier = Modifier
        .fillMaxWidth()
        .oneHandedGesture(
            gestureConfiguration = gestureConfig,
            interactionSource = interactionSource,
            onGestureLabel = if (isPlaying) "pause" else "play",
            onGestureAvailable = { coroutineScope.launch { indicatorState.showIndicator() } },
            onGesture = onClick
        )
) {
    OneHandedGestureClickIndicator(
        gestureConfiguration = gestureConfig,
        state = indicatorState
    ) {
        Text(if (isPlaying) "Pause" else "Play", modifier = Modifier.fillMaxWidth())
    }
}

Conteneurs pouvant être parcourus

Pour les écrans ou les listes pouvant être parcourus, créez une configuration spécifiant OneHandedGestureAction.Primary et appliquez le modificateur oneHandedGesture à votre conteneur, en appelant un assistant de défilement tel que scrollDown.

Pour fournir un retour visuel pour les actions de défilement, vous pouvez utiliser le OneHandedGestureScrollIndicator. Ce composant fonctionne comme un indicateur de défilement standard qui affiche la position de défilement, mais il peut également indiquer qu'un geste de défilement est disponible pour l'utilisateur. Cet indicateur est généralement transmis à l'emplacement scrollIndicator d'un ScreenScaffold et est associé à l'état d'un conteneur pouvant être parcouru, tel qu'un TransformingLazyColumn. Il observe également un OneHandedGestureScrollIndicatorState pour gérer ses transitions visuelles.

Pour déclencher le retour visuel, appelez showIndicator sur cet état, généralement dans le rappel onGestureAvailable du modificateur oneHandedGesture. Une fois déclenché, l'indicateur remplace temporairement son état visuel standard par une séquence d'animation de geste pour alerter l'utilisateur.

val scrollState = rememberTransformingLazyColumnState()
val gestureConfig = rememberOneHandedGestureConfiguration(
    action = OneHandedGestureAction.Primary,
    priority = OneHandedGesturePriority.Scrollable
)
val indicatorState = remember(gestureConfig) { OneHandedGestureScrollIndicatorState() }
val coroutineScope = rememberCoroutineScope()

ScreenScaffold(
    scrollState = scrollState,
    scrollIndicator = {
        OneHandedGestureScrollIndicator(
            gestureConfiguration = gestureConfig,
            indicatorState = indicatorState,
            scrollState = scrollState,
            modifier = Modifier.align(Alignment.CenterEnd)
        )
    }
) { contentPadding ->
    TransformingLazyColumn(
        state = scrollState,
        contentPadding = contentPadding,
        modifier = Modifier
            .fillMaxSize()
            .oneHandedGesture(
                gestureConfiguration = gestureConfig,
                onGestureLabel = "scroll",
                onGestureAvailable = {
                    coroutineScope.launch { indicatorState.showIndicator() }
                },
                onGesture = { OneHandedGestureDefaults.scrollDown(scrollState) }
            )
    ) {
        items(10) { index ->
            Text("Item $index", modifier = Modifier.padding(8.dp))
        }
    }
}

Combiner plusieurs gestes

Vous pouvez configurer un geste de défilement et un geste de clic avec la même action principale en ajoutant gesturePriority à votre objet OneHandedGestureConfiguration :

  • OneHandedGesturePriority.Clickable (priorité la plus élevée) : attribuez-le aux commandes interactives, telles que celles de type Button ou Card, afin qu'elles capturent les gestes lorsqu'elles sont visibles à l'écran.
  • OneHandedGesturePriority.Scrollable (priorité moyenne) : attribuez-le aux conteneurs pouvant être parcourus ou paginés afin qu'ils cèdent la place aux enfants cliquables, mais qu'ils défilent lorsqu'aucune commande cliquable n'est visible.
  • OneHandedGesturePriority.Unspecified (priorité la plus faible) : priorité non attribuée. Il s'agit de la valeur par défaut pour un geste dont aucun priority n'est défini.

En définissant explicitement priority = OneHandedGesturePriority.Clickable sur un bouton interne et priority = OneHandedGesturePriority.Scrollable sur sa liste parente, le système peut afficher ce comportement de priorité des gestes. Lorsque l'utilisateur déclenche l'action principale par le geste à une main, la liste défile d'abord vers le bas jusqu'à ce que le bouton soit visible. Ensuite, il capture l'action de clic du bouton.

Tester et déboguer des gestes avec ADB

Vous pouvez tester les gestes à une main sur un appareil physique ou un émulateur sans effectuer de mouvements physiques du poignet à l'aide d'Android Debug Bridge (adb) et du service système IWearGestureService.

Activer la simulation de gestes

  1. Vérifiez que votre appareil Wear OS exécute Wear OS 7 (niveau d'API 37) ou une version ultérieure.
  2. Si vous effectuez des tests sur un appareil physique qui n'est pas au poignet ou sur un chargeur, remplacez l'état du capteur hors corps afin que l'appareil reste actif :
adb shell cmd sensorservice set-off-body-state 0

Déclencher des événements de geste à l'aide d'ADB

Pour simuler le geste Double pincement (qui est l'action Primary sur les montres Pixel), exécutez la commande shell ADB suivante :

adb shell cmd IWearGestureService gesture 1

Pour simuler le geste Rotation du poignet (qui est l'action Dismiss sur les montres Pixel), exécutez la commande shell ADB suivante :

adb shell cmd IWearGestureService gesture 2

Réinitialiser le suivi des indications de gestes

Le système suit l'historique des interactions de l'utilisateur et affiche des indications de gestes flottantes en fonction du paramètre de cadence global (par exemple, Toujours ou Tous les jours). Lorsque vous déboguez les indicateurs de gestes de votre application, réinitialisez cet historique de suivi afin que les indications s'affichent à nouveau pour votre package :

adb shell cmd IWearGestureService hint clear <your_package_name>

Pour réinitialiser l'état du capteur hors corps une fois les tests terminés :

adb shell cmd sensorservice reset-off-body-state

Ressources supplémentaires

Pour obtenir des conseils de conception sur quand et où utiliser les gestes à une main, consultez Gestes à une main.