Gestur satu tangan dengan Compose


Di Wear OS 7 (level API 37) dan yang lebih tinggi, framework gestur satu tangan, beserta API yang merupakan bagian dari Compose untuk Wear OS, memungkinkan pengguna berinteraksi dengan aplikasi Anda tanpa sentuhan.

Meskipun awalnya didukung di perangkat Pixel Watch (Pixel Watch 3 dan yang lebih baru), framework ini tersedia untuk semua OEM. Dengan mengadopsi API ini, dukungan gestur aplikasi Anda akan otomatis diskalakan di seluruh ekosistem seiring dengan perluasan dukungan hardware.

Untuk membantu pengguna menemukan gestur yang tersedia tanpa membuat UI berantakan, framework Wear OS menyediakan indikator gestur animasi. Petunjuk visual ini menyoroti tempat gestur dapat dilakukan, sementara sistem secara otomatis mengelola irama tampilan dan frekuensi peredamannya sesuai dengan preferensi pengguna.

Gestur dan tindakan yang didukung

Framework gestur Wear OS mendukung dua jenis gestur:

  • Tindakan utama (Mencubit dua jari): Memetakan ke tindakan utama di layar, seperti menjawab panggilan atau mengalihkan pemutaran media.
  • Tindakan tutup (Putar pergelangan tangan): Memetakan ke navigasi mundur, menutup dialog, atau membatalkan perintah.

Mengonfigurasi gestur di Compose

Meskipun API gestur satu tangan dapat meningkatkan kualitas UI Anda, penting untuk diingat bahwa beberapa hardware dan OEM tidak mendukung gestur ini. Jika API mendeteksi bahwa aplikasi Anda berjalan di salah satu perangkat yang tidak didukung ini, library akan otomatis melakukan no-op tanpa memengaruhi interaksi sentuh standar.

Seperti perilaku Compose standar, Anda dapat mengaktifkan gestur satu tangan pada elemen UI menggunakan pengubah. Anda mengonfigurasi gestur aplikasi sesuai dengan tindakan yang akan dilakukan—baik utama maupun tutup—dan gestureId untuk berkoordinasi dengan preferensi pengguna tingkat sistem, seperti irama tampilan saran dan peredaman frekuensi. Anda menyatakan konfigurasi ini dengan membuat objek OneHandedGestureConfiguration; sebaiknya gunakan fungsi rememberOneHandedGestureConfiguration untuk membuatnya. OneHandedGestureConfiguration juga merupakan tempat Anda dapat memberikan prioritas gestur.

Fungsi rememberOneHandedGestureConfiguration melacak histori interaksi pengguna di seluruh rekomposisi tanpa mengekspos status aplikasi. Setelah aplikasi Anda membuat konfigurasi, aplikasi tersebut harus meneruskan konfigurasi ke Modifier.oneHandedGesture pada composable interaktif Anda.

Untuk membantu pengguna menemukan gestur yang tersedia, library menyediakan metode OneHandedGestureClickIndicator. Metode ini berfungsi sebagai wrapper yang menggantikan konten dasarnya untuk menunjukkan kepada pengguna bahwa tindakan gestur tersedia.

Komponen interaktif

Untuk mengaktifkan gestur pada kontrol interaktif seperti tombol, buat konfigurasi yang menentukan OneHandedGestureAction.Primary dan terapkan pengubah oneHandedGesture. Teruskan MutableInteractionSource yang sama ke kontrol dan pengubah sehingga peristiwa gestur memancarkan masukan tekanan visual ke kontrol.

Untuk mengaktifkan indikator gestur, buat dan ingat instance OneHandedGestureClickIndicatorState. Kemudian, untuk memicu masukan visual, panggil showIndicator dalam callback onGestureAvailable yang disediakan oleh pengubah oneHandedGesture, yang memberi sinyal kepada sistem bahwa peristiwa indikasi telah terjadi. Setelah dipanggil, komponen akan mengganti konten normalnya dengan animasi gestur untuk sementara.

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

Penampung yang dapat di-scroll

Untuk layar atau daftar yang dapat di-scroll, buat konfigurasi yang menentukan OneHandedGestureAction.Primary dan terapkan pengubah oneHandedGesture ke penampung Anda, dengan memanggil helper scrolling seperti scrollDown.

Untuk memberikan masukan visual untuk tindakan men-scroll, Anda dapat menggunakan OneHandedGestureScrollIndicator. Komponen ini berfungsi sebagai indikator scroll standar yang menunjukkan posisi scroll, tetapi juga dapat menunjukkan bahwa gestur scroll tersedia untuk pengguna. Indikator ini biasanya diteruskan ke slot scrollIndicator dari ScreenScaffold dan digabungkan dengan status penampung yang dapat di-scroll, seperti TransformingLazyColumn. ini juga mengamati OneHandedGestureScrollIndicatorState untuk mengelola transisi visualnya.

Untuk memicu masukan visual, panggil showIndicator pada status ini—biasanya di dalam callback onGestureAvailable dari pengubah oneHandedGesture. Setelah dipicu, indikator akan menggantikan status visual standarnya untuk sementara dengan urutan animasi gestur guna memberi tahu pengguna.

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

Menggabungkan beberapa gestur

Anda dapat mengonfigurasi gestur scroll dan gestur klik dengan tindakan utama yang sama dengan menambahkan gesturePriority ke objek OneHandedGestureConfiguration:

  • OneHandedGesturePriority.Clickable (tertinggi): Tetapkan ke kontrol interaktif—seperti yang memiliki jenis Button atau Card—sehingga kontrol tersebut dapat merekam gestur saat terlihat di layar.
  • OneHandedGesturePriority.Scrollable (sedang): Tetapkan ke penampung yang dapat di-scroll atau di-page sehingga elemen tersebut tunduk pada turunan yang dapat diklik, tetapi di-scroll saat tidak ada kontrol yang dapat diklik yang terlihat.
  • OneHandedGesturePriority.Unspecified (terendah): Prioritas yang belum ditetapkan. Ini adalah nilai default untuk gestur yang tidak memiliki setelan priority.

Dengan menetapkan priority = OneHandedGesturePriority.Clickable secara eksplisit pada tombol dalam dan priority = OneHandedGesturePriority.Scrollable pada daftar induknya, sistem dapat menampilkan perilaku prioritas gestur ini. Saat pengguna memicu tindakan utama dengan gestur satu tangan, tindakan tersebut pertama-tama akan men-scroll daftar ke bawah hingga tombol terlihat, lalu merekam tindakan klik tombol.

Menguji dan men-debug gestur dengan ADB

Anda dapat menguji gestur satu tangan di perangkat fisik atau emulator tanpa melakukan gerakan pergelangan tangan fisik menggunakan Android Debug Bridge (adb) dan layanan sistem IWearGestureService.

Mengaktifkan simulasi gestur

Sebelum menyimulasikan gestur menggunakan ADB, konfigurasi setelan perangkat dan penggantian batasan:

  1. Verifikasi bahwa perangkat Wear OS Anda menjalankan Wear OS 7 (level API 37) dan yang lebih tinggi:

    adb shell getprop ro.build.version.sdk
    
  2. Jika Anda melakukan pengujian di perangkat fisik yang tidak terpasang di pergelangan tangan atau sedang diisi daya, ganti batasan di luar tubuh agar framework gestur tetap aktif:

    adb shell cmd IWearGestureService override-constraints offbody-state
    

Memicu peristiwa gestur menggunakan ADB

Untuk menyimulasikan gestur cubit dua kali (yang merupakan tindakan Primary di smartwatch Pixel), jalankan perintah shell ADB berikut:

adb shell cmd IWearGestureService gesture DoublePinch

Untuk menyimulasikan gestur Putar pergelangan tangan (yang merupakan tindakan Dismiss di smartwatch Pixel), jalankan perintah shell ADB berikut:

adb shell cmd IWearGestureService gesture WristTurn

Mereset pelacakan petunjuk gestur

Sistem melacak histori interaksi pengguna dan menampilkan petunjuk gestur mengambang berdasarkan setelan irama global (seperti Selalu atau Harian). Saat men-debug indikator gestur aplikasi, reset histori pelacakan ini agar petunjuk muncul lagi untuk paket Anda:

  • Pada build atau emulator userdebug:

    adb shell cmd IWearGestureService hint clear <your_package_name>
    
  • Pada build retail (user):

    Di perangkat komersial tanpa akses root, hint clear diblokir oleh izin sistem. Hapus data lokal aplikasi untuk mereset penemuan saran:

    adb shell pm clear <your_package_name>
    

Memulihkan batasan default

Untuk mereset semua penggantian batasan debug setelah Anda selesai menguji:

adb shell cmd IWearGestureService override-constraints reset

Memecahkan masalah injeksi gestur

Jika aplikasi Anda tidak menerima gestur simulasi:

  1. Pastikan layar smartwatch aktif dan DINYALAKAN. Framework gestur tidak mengirimkan gestur ke aplikasi saat layar mati atau dalam mode standby. Untuk mengaktifkan layar menggunakan ADB, jalankan:

    adb shell input keyevent KEYCODE_WAKEUP
    
  2. Periksa apakah aplikasi Anda terdaftar sebagai pelanggan gestur aktif dan saat ini memegang fokus jendela:

    adb shell cmd IWearGestureService get-active-gestures -readable
    

    Saat layar gestur aplikasi Anda berada di latar depan dan layar aktif, perintah ini akan menampilkan [DoublePinch] atau [DoublePinch, WristTurn]. Jika daftar kosong ([]) ditampilkan, periksa apakah jendela Anda memiliki fokus atau apakah batasan di luar tubuh menghalangi aktivasi.

  3. Periksa status layanan gestur internal dan token pelanggan aktif:

    adb shell dumpsys IWearGestureService
    

Referensi lainnya

Untuk panduan desain tentang kapan dan di mana menggunakan gestur satu tangan, lihat Gestur satu tangan.