Einhandgesten mit Compose


Unter Wear OS 7 (API‑Level 37) und höher können Nutzer mithilfe eines Frameworks für Einhandgesten und einer API, die Teil von Compose for Wear OS ist, berührungslos mit Ihrer App interagieren.

Das Framework wird anfangs auf Pixel Watch-Geräten (Pixel Watch 3 und höher) unterstützt, ist aber für alle OEMs verfügbar. Wenn Sie diese API verwenden, wird die Unterstützung von Gesten in Ihrer App automatisch auf das gesamte Ökosystem skaliert, wenn die Hardwareunterstützung erweitert wird.

Damit Nutzer verfügbare Gesten leichter finden, ohne dass die Benutzeroberfläche überladen wird, bietet das Wear OS-Framework animierte Gestenindikatoren. Diese visuellen Hinweise zeigen, wo eine Geste ausgeführt werden kann. Das System verwaltet automatisch die Häufigkeit der Anzeige und die Häufigkeit der Stummschaltung entsprechend den Nutzereinstellungen.

Unterstützte Touchgesten und Aktionen

Das Wear OS-Framework für Touch-Gesten unterstützt zwei Arten von Touch-Gesten:

  • Primäre Aktion (Doppel-Pinch): Wird der Hauptaktion auf einem Bildschirm zugeordnet, z. B. dem Annehmen eines Anrufs oder dem Umschalten der Medienwiedergabe.
  • Aktion verwerfen (Drehen des Handgelenks): Wird für die Rückwärtsnavigation, das Schließen eines Dialogfelds oder das Abbrechen eines Prompts verwendet.

Gesten in Compose konfigurieren

Die API für Einhandbedienungsgesten kann zwar die Benutzeroberfläche verbessern, es ist jedoch wichtig zu beachten, dass einige Hardware und OEMs diese Gesten nicht unterstützen. Wenn die API erkennt, dass Ihre App auf einem dieser nicht unterstützten Geräte ausgeführt wird, wird die Bibliothek automatisch deaktiviert, ohne dass dies Auswirkungen auf die Standard-Touch-Interaktionen hat.

Wie bei Standard-Compose-Verhaltensweisen aktivieren Sie Einhandgesten für UI-Elemente mit Modifikatoren. Sie konfigurieren die Gesten Ihrer App entsprechend der auszuführenden Aktion – entweder primär oder schließen – und einem gestureId, um sie an die Nutzereinstellungen auf Systemebene anzupassen, z. B. die Häufigkeit der Hinweisanzeige und die Frequenzdämpfung. Sie drücken diese Konfiguration aus, indem Sie ein OneHandedGestureConfiguration-Objekt erstellen. Wir empfehlen, dafür die Funktion rememberOneHandedGestureConfiguration zu verwenden. Im OneHandedGestureConfiguration können Sie auch die Priorität der Geste angeben.

Mit der Funktion rememberOneHandedGestureConfiguration wird der Verlauf der Nutzerinteraktionen über Recompositionen hinweg verfolgt, ohne den Anwendungsstatus preiszugeben. Nachdem Ihre App die Konfiguration erstellt hat, sollte sie die Konfiguration an Modifier.oneHandedGesture in Ihrem interaktiven Composable übergeben.

Damit Nutzer verfügbare Gesten leichter finden, bietet die Bibliothek die Methode OneHandedGestureClickIndicator. Diese Methode fungiert als Wrapper, der den zugrunde liegenden Inhalt ersetzt, um dem Nutzer anzuzeigen, dass eine Gestenaktion verfügbar ist.

Interaktive Komponenten

Wenn Sie Gesten für ein interaktives Steuerelement wie eine Schaltfläche aktivieren möchten, erstellen Sie eine Konfiguration, in der OneHandedGestureAction.Primary angegeben ist, und wenden Sie den Modifier oneHandedGesture an. Übergeben Sie denselben MutableInteractionSource sowohl an das Steuerelement als auch an den Modifier, damit bei Gestenereignissen ein visuelles Feedback für das Drücken auf das Steuerelement ausgegeben wird.

Um den Gestenindikator zu aktivieren, erstellen Sie eine Instanz von OneHandedGestureClickIndicatorState und merken Sie sich diese. Rufen Sie dann showIndicator im onGestureAvailable-Callback auf, der vom oneHandedGesture-Modifikator bereitgestellt wird, um das visuelle Feedback auszulösen. Dadurch wird dem System signalisiert, dass ein Hinweisereignis aufgetreten ist. Nach dem Aufruf wird der normale Inhalt der Komponente kurz durch eine Animationsgeste ersetzt.

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

Scrollbare Container

Erstellen Sie für scrollbare Bildschirme oder Listen eine Konfiguration, in der OneHandedGestureAction.Primary angegeben ist, und wenden Sie den Modifier oneHandedGesture auf den Container an. Rufen Sie dazu eine Scroll-Hilfsfunktion wie scrollDown auf.

Um visuelles Feedback für Scrollvorgänge zu geben, können Sie die OneHandedGestureScrollIndicator verwenden. Diese Komponente funktioniert als standardmäßiger Scrollindikator, der die Scrollposition anzeigt. Sie kann aber auch darauf hinweisen, dass dem Nutzer eine Scrollbewegung zur Verfügung steht. Dieser Indikator wird in der Regel an den scrollIndicator-Slot eines ScreenScaffold übergeben und ist an den Status eines scrollbaren Containers wie einem TransformingLazyColumn gekoppelt. Außerdem wird ein OneHandedGestureScrollIndicatorState beobachtet, um die visuellen Übergänge zu verwalten.

Um das visuelle Feedback auszulösen, rufen Sie showIndicator in diesem Status auf, in der Regel im onGestureAvailable-Callback des oneHandedGesture-Modifiers. Wenn die Anzeige ausgelöst wird, wird ihr visueller Standardstatus vorübergehend durch eine Animationssequenz ersetzt, um den Nutzer zu benachrichtigen.

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

Mehrere Gesten kombinieren

Sie können sowohl eine Scroll- als auch eine Klickbewegung mit derselben primären Aktion konfigurieren, indem Sie Ihrem OneHandedGestureConfiguration-Objekt gesturePriority hinzufügen:

  • OneHandedGesturePriority.Clickable (höchste Priorität): Interaktiven Steuerelementen wie denen vom Typ Button oder Card zuweisen, damit sie Gesten erfassen, wenn sie auf dem Bildschirm sichtbar sind.
  • OneHandedGesturePriority.Scrollable (mittel): Zuweisen zu scrollbaren oder seitenweisen Containern, damit sie anklickbaren untergeordneten Elementen weichen, aber scrollen, wenn kein anklickbares Steuerelement sichtbar ist.
  • OneHandedGesturePriority.Unspecified (niedrigste Priorität): Eine nicht zugewiesene Priorität. Dies ist der Standardwert für eine Geste, für die kein priority festgelegt ist.

Wenn Sie priority = OneHandedGesturePriority.Clickable explizit für eine innere Schaltfläche und priority = OneHandedGesturePriority.Scrollable für die übergeordnete Liste festlegen, kann das System dieses Verhalten mit Priorität für die Geste anzeigen. Wenn der Nutzer die primäre Aktion mit der Einhandbedienung auslöst, wird zuerst die Liste nach unten gescrollt, bis die Schaltfläche sichtbar ist. Anschließend wird die Klickaktion der Schaltfläche erfasst.

Gesten mit ADB testen und Fehler beheben

Sie können Einhandgesten auf einem physischen Gerät oder Emulator testen, ohne physische Handgelenkbewegungen auszuführen. Verwenden Sie dazu die Android Debug Bridge (adb) und den Systemdienst IWearGestureService.

Touchgesten simulieren

Bevor Sie Gesten mit ADB simulieren, müssen Sie die Geräteeinstellungen und Constraint-Überschreibungen konfigurieren:

  1. Prüfe, ob auf deinem Wear OS-Gerät Wear OS 7 (API-Level 37) oder höher ausgeführt wird:

    adb shell getprop ro.build.version.sdk
    
  2. Wenn Sie auf einem physischen Gerät testen, das sich nicht an Ihrem Handgelenk befindet oder auf einem Ladegerät liegt, überschreiben Sie die Einschränkung für das Tragen am Körper, damit das Gesten-Framework aktiv bleibt:

    adb shell cmd IWearGestureService override-constraints offbody-state
    

Gestenereignisse mit ADB auslösen

Führen Sie den folgenden ADB-Shell-Befehl aus, um die Geste Doppeltippen zu simulieren (die Aktion Primary auf Pixel Watches):

adb shell cmd IWearGestureService gesture DoublePinch

Führen Sie den folgenden ADB-Shell-Befehl aus, um die Geste Handgelenk drehen (die Aktion Dismiss auf Pixel Watches) zu simulieren:

adb shell cmd IWearGestureService gesture WristTurn

Tracking von Hinweisen zu Touch-Gesten zurücksetzen

Das System verfolgt den Verlauf der Nutzerinteraktionen und zeigt Hinweise zu schwebenden Gesten basierend auf der globalen Häufigkeitseinstellung an (z. B. Immer oder Täglich). Wenn Sie die Gestenanzeigen Ihrer App debuggen, setzen Sie diesen Trackingverlauf zurück, damit für Ihr Paket wieder Hinweise angezeigt werden:

  • Auf userdebug-Builds oder Emulatoren:

    adb shell cmd IWearGestureService hint clear <your_package_name>
    
  • Bei Einzelhandels-Builds (user):

    Auf kommerziellen Geräten ohne Root-Zugriff wird hint clear durch Systemberechtigungen blockiert. So löschen Sie die lokalen Daten der App, um die Suche nach Hinweisen zurückzusetzen:

    adb shell pm clear <your_package_name>
    

Standardeinschränkungen wiederherstellen

So setzen Sie alle Überschreibungen von Debug-Einschränkungen zurück, wenn Sie mit dem Testen fertig sind:

adb shell cmd IWearGestureService override-constraints reset

Fehlerbehebung bei der Gesteneingabe

Wenn Ihre App keine simulierten Gesten empfängt:

  1. Prüfe, ob das Display der Smartwatch aktiviert und eingeschaltet ist. Das Gesten-Framework leitet keine Gesten an Apps weiter, wenn das Display ausgeschaltet ist oder sich im Inaktivmodus befindet. So aktivierst du das Display mit ADB:

    adb shell input keyevent KEYCODE_WAKEUP
    
  2. Prüfen Sie, ob Ihre App als aktiver Gestenabonnent registriert ist und derzeit den Fensterfokus hat:

    adb shell cmd IWearGestureService get-active-gestures -readable
    

    Wenn der Gestenbildschirm Ihrer App im Vordergrund und der Bildschirm aktiv ist, wird mit diesem Befehl [DoublePinch] oder [DoublePinch, WristTurn] zurückgegeben. Wenn eine leere Liste ([]) zurückgegeben wird, prüfen Sie, ob Ihr Fenster den Fokus hat oder ob die Aktivierung durch Off-Body-Einschränkungen blockiert wird.

  3. Prüfen Sie den Status des internen Gestendienstes und die aktiven Abonnententokens:

    adb shell dumpsys IWearGestureService
    

Zusätzliche Ressourcen

Designrichtlinien dazu, wann und wo Einhandgesten verwendet werden sollten, finden Sie unter Einhandgesten.