Daftar dengan Compose untuk Wear OS


Daftar memungkinkan pengguna memilih item dari serangkaian pilihan di perangkat Wear OS.

Banyak perangkat Wear OS menggunakan layar bundar, yang menyulitkan untuk melihat item daftar yang muncul di dekat bagian atas dan bawah layar. Karena alasan ini, Compose untuk Wear OS menyertakan versi class LazyColumn yang disebut TransformingLazyColumn, yang mendukung animasi penskalaan dan perubahan bentuk. Saat dipindahkan ke tepi, item akan menjadi lebih kecil dan memudar.

Untuk menerapkan efek penskalaan dan scrolling yang direkomendasikan:

  1. Gunakan Modifier.transformedHeight agar Compose dapat menghitung perubahan tinggi saat item di-scroll melalui layar.
  2. Gunakan transformation = SurfaceTransformation(transformationSpec) untuk menerapkan efek visual, termasuk memperkecil konten item.
  3. Gunakan TransformationSpec kustom untuk komponen yang tidak menggunakan transformation sebagai parameter seperti Text.

Animasi berikut menunjukkan cara elemen daftar diskalakan dan mengubah bentuk saat mendekati bagian atas dan bawah layar:

Cuplikan kode berikut menunjukkan cara membuat daftar menggunakan TransformingLazyColumn tata letak untuk membuat konten yang terlihat bagus di berbagai ukuran layar Wear OS.

Cuplikan ini juga menunjukkan penggunaan pengubah minimumVerticalContentPadding, yang harus Anda tetapkan pada item daftar untuk menerapkan padding yang benar di bagian atas dan bawah daftar.

Untuk menampilkan indikator scroll, bagikan columnState antara ScreenScaffold dan TransformingLazyColumn:

val columnState = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()
ScreenScaffold(
    scrollState = columnState
) { contentPadding ->
    TransformingLazyColumn(
        state = columnState,
        contentPadding = contentPadding
    ) {
        item {
            ListHeader(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ListHeaderDefaults.minimumTopListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            ) {
                Text(text = "Header")
            }
        }
        // ... other items
        item {
            Button(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ButtonDefaults.minimumVerticalListContentPadding),
                transformation = SurfaceTransformation(transformationSpec),
                onClick = { /* ... */ },
                icon = {
                    Icon(
                        imageVector = Icons.Default.Build,
                        contentDescription = "build",
                    )
                },
            ) {
                Text(
                    text = "Build",
                    maxLines = 1,
                    overflow = TextOverflow.Ellipsis,
                )
            }
        }
    }
}

Menambahkan efek snap-dan-fling

Snapping memastikan bahwa saat pengguna menyelesaikan gestur scroll atau fling, daftar akan diselesaikan dengan item yang diposisikan secara tepat di titik tertentu, biasanya di tengah layar. Di layar bundar, tempat item diskalakan dan diubah bentuknya saat bergerak menjauhi bagian tengah, snapping sangat berguna untuk memastikan item yang paling relevan tetap terlihat sepenuhnya dan dapat dibaca di area tampilan yang optimal.

Untuk menambahkan perilaku snap-dan-fling, tetapkan parameter flingBehavior ke TransformingLazyColumnDefaults.snapFlingBehavior(columnState). Tetapkan rotaryScrollableBehavior agar cocok, menggunakan RotaryScrollableDefaults.snapBehavior(columnState) untuk pengalaman yang konsisten saat menggunakan crown atau bezel fisik.

val columnState = rememberTransformingLazyColumnState()
ScreenScaffold(scrollState = columnState) { contentPadding ->
    TransformingLazyColumn(
        state = columnState,
        flingBehavior = TransformingLazyColumnDefaults.snapFlingBehavior(columnState),
        rotaryScrollableBehavior = RotaryScrollableDefaults.snapBehavior(columnState)
    ) {
        // ...
        // ...
    }
}

Tata letak terbalik

Secara default, daftar yang dapat di-scroll ditambatkan ke tepi atasnya. Jika pengguna telah men-scroll ke bagian bawah daftar standar dan item baru ditambahkan ke bagian akhir, daftar akan mempertahankan tampilan pengguna pada item saat ini. Misalnya, jika pengguna melihat item 10 di bagian bawah layar, dan item 11 ditambahkan, tampilan akan tetap berfokus pada item 10, dan item 11 akan muncul di luar layar di bawah tampilan saat ini.

Untuk kasus penggunaan seperti aplikasi pesan atau log langsung, perilaku ini biasanya tidak diinginkan. Saat item baru tiba, pengguna biasanya ingin segera melihat konten terbaru jika sudah berada di bagian bawah daftar. Jika banyak item tiba sekaligus, daftar harus dilewati untuk menampilkan item terbaru di bagian bawah (artinya, beberapa item perantara mungkin tidak ditampilkan sama sekali kecuali jika pengguna men-scroll kembali ke atas).

Untuk mendukung kasus penggunaan ini, TransformingLazyColumn memungkinkan Anda membalikkan tata letak dengan menetapkan reverseLayout = true. Tindakan ini mengubah anchor daftar dari tepi atas ke tepi bawah.

Untuk kemudahan, menetapkan reverseLayout = true juga akan membalikkan urutan visual item dan arah gestur scrolling:

  • Item disusun dari bawah ke atas, yang berarti indeks 0 muncul di bagian bawah layar.
  • Men-scroll ke atas akan menampilkan item dengan indeks yang lebih tinggi.

Untuk menambahkan perilaku snap-dan-fling beserta tata letak terbalik, Anda dapat menggabungkan flingBehavior dan rotaryScrollableBehavior seperti yang ditunjukkan dalam cuplikan berikut:

val columnState = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()
ScreenScaffold(scrollState = columnState) { contentPadding ->
    TransformingLazyColumn(
        state = columnState,
        contentPadding = contentPadding,
        reverseLayout = true,
        modifier = Modifier.fillMaxWidth()
    ) {
        items(10) { index ->
            Button(
                label = {
                    Text(
                        text = "Item ${index + 1}"
                    )
                },
                onClick = {},
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ButtonDefaults.minimumVerticalListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            )
        }
        item {
            // With reverseLayout = true, the last item declared appears at the top.
            ListHeader(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ListHeaderDefaults.minimumTopListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            ) {
                Text("Header")
            }
        }
    }
}

Gambar berikut menunjukkan perbedaan antara daftar normal dan daftar terbalik:

TransformingLazyColumn dengan tata letak normal, yang menampilkan Item 1 di bagian atas dan item dalam urutan menaik.
Gambar 1. Tata letak daftar standar tempat konten diisi dari atas ke bawah.
TransformingLazyColumn dengan tata letak terbalik, yang menampilkan Item 1 di bagian bawah dan item dalam urutan menurun ke arah atas.
Gambar 2. Tata letak daftar terbalik tempat konten diisi dari bawah ke atas.

Tombol tepi pada daftar

Untuk Material 3, Anda dapat menambahkan EdgeButton yang merupakan tombol tepi di bagian bawah daftar. Namun, berhati-hatilah untuk tidak menambahkannya sebagai item dalam TransformingLazyColumn, tetapi gunakan slot edgeButton di ScreenScaffold.

Menggunakan slot edgeButton memastikan tombol diposisikan dengan benar di bagian bawah layar dan berperilaku dengan tepat saat daftar di-scroll.

Cuplikan kode berikut menunjukkan cara mengimplementasikan EdgeButton:

val columnState = rememberTransformingLazyColumnState()
ScreenScaffold(
    scrollState = columnState,
    edgeButton = {
        EdgeButton(
            onClick = { /* TODO */ },
            modifier = Modifier.scrollable(
                columnState,
                orientation = Orientation.Vertical,
                reverseDirection = true,
                // Apply overscroll to the EdgeButton for proper scrolling behavior.
                overscrollEffect = rememberOverscrollEffect(),
            )
        ) {
            Text("More")
        }
    }
) { contentPadding ->
    TransformingLazyColumn(
        contentPadding = contentPadding,
        state = columnState,
    ) {
        // ...
        // ...
    }
}

SwipeToReveal dalam Daftar

Komponen SwipeToReveal memungkinkan Anda mengakses tindakan untuk item daftar, seperti a Card atau Chip, dengan menggeser. Menggeser biasanya akan menampilkan satu atau dua tombol tindakan (seperti "Hapus" atau "Lainnya") dari samping.

Saat menggunakan SwipeToReveal dalam TransformingLazyColumn, ikuti panduan berikut:

  • Reset saat di-scroll: Saat pengguna men-scroll daftar, reset item yang dibuka dengan geser ke status tertutup.
  • Tinggi yang konsisten: Tetapkan tinggi tombol tindakan agar sesuai dengan item yang digeser di dalamnya (baik Button maupun Card) untuk memastikan tampilan yang konsisten.
  • Ubah penampung: Terapkan pengubah transformedHeight dan transformationSpec ke komponen SwipeToReveal itu sendiri.
  • Jangan lakukan transformasi ganda: Jangan terapkan transformedHeight atau transformation pengubah ke item yang digeser di dalamnya (kartu atau tombol di dalam penampung SwipeToReveal).