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 pengubahan bentuk.
Saat dipindahkan ke tepi, item akan menjadi lebih kecil dan memudar.
Untuk menerapkan efek penskalaan dan scrolling yang direkomendasikan:
- Gunakan
Modifier.transformedHeightagar Compose dapat menghitung perubahan tinggi saat item di-scroll di layar. - Gunakan
transformation = SurfaceTransformation(transformationSpec)untuk menerapkan efek visual, termasuk memperkecil konten item. - Gunakan
TransformationSpeckustom untuk komponen yang tidak menggunakantransformationsebagai parameter sepertiText.
Animasi berikut menunjukkan cara elemen daftar diskalakan dan diubah bentuknya saat mendekati bagian atas dan bawah layar:
Cuplikan kode berikut menunjukkan cara membuat daftar menggunakan tata letakTransformingLazyColumn untuk membuat konten yang
tampak 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
Penyelarasan memastikan bahwa saat pengguna menyelesaikan gestur scroll atau geser, daftar akan berhenti dengan item yang diposisikan secara tepat pada titik tertentu, biasanya di tengah layar. Pada layar bulat, tempat item diskalakan dan diubah bentuknya saat bergerak menjauhi pusat, penarikan sangat berguna untuk memastikan item yang paling relevan tetap terlihat sepenuhnya dan dapat dibaca di area tampilan yang optimal.Untuk menambahkan perilaku mengepaskan dan melemparkan, tetapkan parameter flingBehavior ke
TransformingLazyColumnDefaults.snapFlingBehavior(columnState).
Setel rotaryScrollableBehavior agar cocok, menggunakan
RotaryScrollableDefaults.snapBehavior(columnState) untuk pengalaman yang konsisten
saat menggunakan kenop jam atau bingkai 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 tetap berfokus pada item 10, dan item 11 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 mereka sudah berada di bagian bawah daftar. Jika banyak item tiba sekaligus, daftar harus melewati untuk menampilkan item terbaru di bagian bawah (artinya beberapa item perantara mungkin tidak ditampilkan sama sekali kecuali pengguna men-scroll kembali ke atas).
Untuk mendukung kasus penggunaan ini, TransformingLazyColumn memungkinkan Anda membalikkan tata letak dengan menyetel reverseLayout = true. Tindakan ini mengubah anchor daftar dari
tepi atas ke tepi bawah.
Untuk memudahkan, menyetel reverseLayout = true juga 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 mengepaskan dan menggeser bersama dengan 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:
Tombol tepi pada daftar
Untuk Material 3, Anda dapat menambahkan EdgeButton yang merupakan tombol yang menempel di tepi
di bagian bawah daftar. Namun, berhati-hatilah agar tidak menambahkannya sebagai item dalam
TransformingLazyColumn, tetapi gunakan slot edgeButton di
ScreenScaffold.
Menggunakan slot edgeButton memastikan bahwa 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 di Daftar
Komponen SwipeToReveal memungkinkan Anda mengakses tindakan untuk item daftar, seperti
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 men-scroll: Saat pengguna men-scroll daftar, reset item yang dibuka dengan menggeser ke status tertutupnya.
- Tinggi yang konsisten: Tetapkan tinggi tombol tindakan agar sesuai dengan item yang digeser di dalamnya (baik
ButtonmaupunCard) untuk memastikan tampilan yang konsisten. - Mengubah penampung: Terapkan pengubah
transformedHeightdantransformationSpecke komponenSwipeToRevealitu sendiri. - Jangan melakukan transformasi ganda: Jangan menerapkan pengubah
transformedHeightatautransformationke item yang digeser di dalam (kartu atau tombol di dalam penampungSwipeToReveal).
Composable kustom dalam daftar
Saat membuat komponen platform kustom untuk TransformingLazyColumn, ikuti
praktik terbaik berikut agar composable Anda diskalakan, memudar, dan berubah bentuk dengan lancar di dekat
tepi layar:
- Expose
SurfaceTransformation: Menerima parameterSurfaceTransformationopsional (defaultnyanull), yang cocok dengan komponen Wear Compose Material 3 standar sepertiCarddanButton. Hal ini memungkinkan pemanggil dengan kartuTransformingLazyColumnSurfaceTransformation(transformationSpec)untuk melewati antrean, sekaligus memungkinkan komponen berfungsi normal di luar daftar. - Terapkan
Modifier.transformedHeightterlebih dahulu dalam kode panggilan: Saat menempatkan composable kustom Anda diTransformingLazyColumn, teruskanModifier.transformedHeight(this, transformationSpec)sebagai pengubah pertama dalam rantai pengubah kode panggilan. MeskipunSurfaceTransformationmenerapkan efek penskalaan dan pemudaran visual,transformedHeightsangat penting untuk memberi tahu tata letak daftar agar menghitung ulang tinggi item saat item menyusut. - Terapkan lapisan transformasi, pemanggil
modifier, dan pelukis secara berurutan:- Lapisan transformasi penampung: Mulai rantai pengubah penampung root dengan
Modifier.graphicsLayerdanapplyContainerTransformation(), sehingga latar belakang dan konten digambar dalam ruang koordinat yang diskalakan dan dimiringkan. - Pemanggil
modifier: Terapkan parametermodifieryang diteruskan oleh pemanggil (yang mencakupModifier.transformedHeight) berikutnya, sebelum pengaturan ukuran atau padding internal. - Klip bentuk saat tidak ada transformasi: Jika
transformationadalahnull, terapkanModifier.clip(shape)sebelum menggambar latar belakang. Pelukis yang ditampilkan olehcreateContainerPainter()memangkas dirinya sendiri ke bentuk, tetapi pelukis biasa tidak, jadi tanpa ini, latar belakang digambar dengan sudut persegi di luar daftar. - Pelukis latar belakang yang berubah bentuk: Menggambar latar belakang di dalam
lapisan penampung menggunakan
Modifier.drawBehinddan pelukis yang dibuat daricreateContainerPainter(). - Lapisan transformasi konten: Terapkan
Modifier.graphicsLayerkedua denganapplyContentTransformation()dan potong ke bentuk penampung sehingga konten dalam memudar lebih awal saat mendekati bezel.
- Lapisan transformasi penampung: Mulai rantai pengubah penampung root dengan
Cuplikan berikut menunjukkan cara mengimplementasikan composable BoardingPassCard kustom yang menerapkan transformasi ini secara berurutan:
@Composable fun BoardingPassCard( flightNumber: String, origin: String, destination: String, gate: String, seat: String, departureTime: String, modifier: Modifier = Modifier, transformation: SurfaceTransformation? = null, shape: Shape = RoundedCornerShape(18.dp), statusBadge: @Composable () -> Unit = {} ) { // 1. Create morphing container painter val backgroundPainter = ColorPainter(MaterialTheme.colorScheme.surfaceContainer) val finalPainter = if (transformation != null) { remember(transformation, backgroundPainter, shape) { transformation.createContainerPainter(backgroundPainter, shape, border = null) } } else { backgroundPainter } Column( modifier = Modifier // 2a. Container layer: Scales, fades, and tilts the whole card surface .then( if (transformation != null) { Modifier.graphicsLayer { transformation.run { applyContainerTransformation() } } } else Modifier ) // 2b. Caller modifier: Includes Modifier.transformedHeight in a list .then(modifier) .fillMaxWidth() // 2c. Shape clip: Only needed without a transformation, because the // painter from createContainerPainter clips itself to the shape .then(if (transformation == null) Modifier.clip(shape) else Modifier) // 2d. Morphing background: Drawn inside the transformed container layer .drawBehind { with(finalPainter) { draw(size) } } // 2e. Content layer: Fades content earlier and clips children to shape .then( if (transformation != null) { Modifier.graphicsLayer { this.shape = shape this.clip = true transformation.run { applyContentTransformation() } } } else Modifier ) .padding(horizontal = 14.dp, vertical = 10.dp) ) { // Card content goes here } }
Kemudian, Anda dapat menggunakan BoardingPassCard di dalam TransformingLazyColumn dengan meneruskan
Modifier.transformedHeight sebagai pengubah pertama, bersama dengan
SurfaceTransformation(transformationSpec):
@Composable fun BoardingPassListSample(flights: List<FlightInfo>) { val listState = rememberTransformingLazyColumnState() val transformationSpec = rememberTransformationSpec() ScreenScaffold(scrollState = listState) { contentPadding -> TransformingLazyColumn( state = listState, contentPadding = contentPadding, modifier = Modifier.fillMaxSize() ) { items(flights.size) { index -> val flight = flights[index] BoardingPassCard( flightNumber = flight.number, origin = flight.origin, destination = flight.destination, gate = flight.gate, seat = flight.seat, departureTime = flight.time, modifier = Modifier .transformedHeight(this, transformationSpec) .minimumVerticalContentPadding( CardDefaults.minimumVerticalListContentPadding ), transformation = SurfaceTransformation(transformationSpec) ) } } } }
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Codelab Compose untuk Wear OS
- Daftar dan Petak
- Menggunakan View di Compose