Halaman ini menjelaskan cara menangani ukuran dan menyediakan tata letak yang fleksibel dan responsif dengan Glance, menggunakan komponen Glance yang ada.
Menggunakan Box, Column, dan Row
Glance memiliki tiga tata letak composable utama:
Box: Menempatkan elemen di atas elemen lain. Elemen ini diterjemahkan keRelativeLayout.Column: Menempatkan elemen satu sama lain di sumbu vertikal. Elemen ini diterjemahkan keLinearLayoutdengan orientasi vertikal.Row: Menempatkan elemen satu sama lain di sumbu horizontal. Elemen ini diterjemahkan keLinearLayoutdengan orientasi horizontal.
Glance mendukung Scaffold objek. Tempatkan composable Column, Row, dan Box dalam objek Scaffold tertentu.
Setiap composable ini memungkinkan Anda menentukan perataan vertikal dan horizontal kontennya serta batasan lebar, tinggi, bobot, atau padding menggunakan pengubah. Selain itu, setiap turunan dapat menentukan pengubahnya untuk mengubah ruang dan penempatan di dalam induk.
Contoh berikut menunjukkan cara membuat Row yang mendistribusikan turunannya secara merata secara horizontal, seperti yang terlihat pada Gambar 1:
Row(modifier = GlanceModifier.fillMaxWidth().padding(16.dp)) { val modifier = GlanceModifier.defaultWeight() Text("first", modifier) Text("second", modifier) Text("third", modifier) }
Row mengisi lebar maksimum yang tersedia, dan karena setiap turunan memiliki bobot yang sama, mereka berbagi ruang yang tersedia secara merata. Anda dapat menentukan bobot, ukuran, padding, atau perataan yang berbeda untuk menyesuaikan tata letak dengan kebutuhan Anda.
Menggunakan tata letak yang dapat di-scroll
Cara lain untuk menyediakan konten responsif adalah dengan membuatnya dapat di-scroll. Hal ini dapat dilakukan dengan composable LazyColumn. Composable ini memungkinkan Anda menentukan kumpulan item yang akan ditampilkan di dalam container yang dapat di-scroll di widget aplikasi.
Cuplikan berikut menunjukkan berbagai cara untuk menentukan item di dalam LazyColumn.
Anda dapat memberikan jumlah item:
// Remember to import Glance Composables // import androidx.glance.appwidget.layout.LazyColumn LazyColumn { items(10) { index: Int -> Text( text = "Item $index", modifier = GlanceModifier.fillMaxWidth() ) } }
Memberikan item individual:
LazyColumn { item { Text("First Item") } item { Text("Second Item") } }
Menyediakan daftar atau array item:
LazyColumn { items(peopleNameList) { name -> Text(name) } }
Anda juga dapat menggunakan kombinasi contoh sebelumnya:
LazyColumn { item { Text("Names:") } items(peopleNameList) { name -> Text(name) } // or in case you need the index: itemsIndexed(peopleNameList) { index, person -> Text("$person at index $index") } }
Perhatikan bahwa cuplikan sebelumnya tidak menentukan itemId. Menentukan itemId membantu meningkatkan performa dan mempertahankan posisi scroll melalui pembaruan daftar dan appWidget dari Android 12 dan yang lebih baru (misalnya, saat menambahkan atau menghapus item dari daftar). Contoh berikut menunjukkan cara menentukan itemId:
items(items = peopleList, itemId = { person -> person.id.hashCode().toLong() }) { person -> Text(person.name) }
Snap Scrolling
Snap scrolling adalah animasi yang memungkinkan konten yang dapat di-scroll di-snap ke bagian atas container widget.
Untuk menerapkan snap scrolling, pastikan Anda memenuhi kondisi berikut:
- Perbarui dependensi Glance ke 1.3.0-alpha02 atau yang lebih tinggi.
- Tetapkan
compileSdkke 37 atau yang lebih tinggi, karena snap scrolling didukung di perangkat yang menjalankan Android 17 dan yang lebih tinggi. - Konfigurasi
LazyColumndenganVerticalScrollMode. Jika perangkat mendukung snap scrolling, gunakanSnapScrollMatchHeight. Jika tidak, gunakanNormal.
Jika Anda menggunakan snap scrolling dengan gambar, lihat full bleed gambar Tata Letak Kanonis.
@Composable fun SnapScrollLayout() { val height = LocalSize.current.height val items = listOf( ColorItem(Color.Red, "Red"), ColorItem(Color.Yellow, "Yellow"), ColorItem(Color.Blue, "Blue") ) val scrollMode = if (Build.VERSION.SDK_INT >= 37) { VerticalScrollMode.SnapScrollMatchHeight(height) } else { VerticalScrollMode.Normal } LazyColumn( verticalScrollMode = scrollMode ) { items(items) { item -> ColorCard(item, height) } } } @Composable private fun ColorCard(item: ColorItem, height: Dp) { Box( modifier = GlanceModifier .background(item.color) .fillMaxWidth() .height(height), contentAlignment = Alignment.Center ) { Text( text = item.name, modifier = GlanceModifier.background(Color.White) ) } }
Menentukan SizeMode
Ukuran AppWidget dapat berbeda-beda bergantung pada perangkat, pilihan pengguna, atau peluncur,
sehingga penting untuk menyediakan tata letak yang fleksibel seperti yang dijelaskan di halaman Menyediakan
tata letak widget yang fleksibel. Glance menyederhanakan hal ini dengan definisi SizeMode dan nilai LocalSize. Bagian berikut menjelaskan tiga mode.
SizeMode.Single
SizeMode.Single adalah mode default. Mode ini menunjukkan bahwa hanya satu jenis konten yang disediakan; yaitu, meskipun ukuran AppWidget yang tersedia berubah, ukuran konten tidak berubah.
class MyAppWidget : GlanceAppWidget() { override val sizeMode = SizeMode.Single override suspend fun provideGlance(context: Context, id: GlanceId) { // ... provideContent { MyContent() } } @Composable private fun MyContent() { // Size will be the minimum size or resizable // size defined in the App Widget metadata val size = LocalSize.current // ... } }
Saat menggunakan mode ini, pastikan bahwa:
- Nilai metadata ukuran minimum dan maksimum ditentukan dengan benar berdasarkan ukuran konten.
- Konten cukup fleksibel dalam rentang ukuran yang diharapkan.
Secara umum, Anda harus menggunakan mode ini jika:
a) AppWidget memiliki ukuran tetap, atau b) tidak mengubah kontennya saat ukuran diubah.
SizeMode.Responsive
Mode ini setara dengan menyediakan tata letak responsif, yang memungkinkan
GlanceAppWidget menentukan kumpulan tata letak responsif yang dibatasi oleh ukuran tertentu. Untuk setiap ukuran yang ditentukan, konten dibuat dan dipetakan ke ukuran tertentu saat AppWidget dibuat atau diperbarui. Sistem kemudian memilih yang paling sesuai berdasarkan ukuran yang tersedia.
Misalnya, di AppWidget tujuan, Anda dapat menentukan tiga ukuran dan kontennya:
class MyAppWidget : GlanceAppWidget() { companion object { private val SMALL_SQUARE = DpSize(100.dp, 100.dp) private val HORIZONTAL_RECTANGLE = DpSize(250.dp, 100.dp) private val BIG_SQUARE = DpSize(250.dp, 250.dp) } override val sizeMode = SizeMode.Responsive( setOf( SMALL_SQUARE, HORIZONTAL_RECTANGLE, BIG_SQUARE ) ) override suspend fun provideGlance(context: Context, id: GlanceId) { // ... provideContent { MyContent() } } @Composable private fun MyContent() { // Size will be one of the sizes defined above. val size = LocalSize.current Column { if (size.height >= BIG_SQUARE.height) { Text(text = "Where to?", modifier = GlanceModifier.padding(12.dp)) } Row(horizontalAlignment = Alignment.CenterHorizontally) { Button() Button() if (size.width >= HORIZONTAL_RECTANGLE.width) { Button("School") } } if (size.height >= BIG_SQUARE.height) { Text(text = "provided by X") } } } }
Pada contoh sebelumnya, metode provideContent dipanggil tiga kali dan dipetakan ke ukuran yang ditentukan.
- Pada panggilan pertama, ukuran dievaluasi menjadi
100x100. Konten tidak menyertakan tombol tambahan, maupun teks atas dan bawah. - Pada panggilan kedua, ukuran dievaluasi menjadi
250x100. Konten menyertakan tombol tambahan, tetapi tidak menyertakan teks atas dan bawah. - Pada panggilan ketiga, ukuran dievaluasi menjadi
250x250. Konten menyertakan tombol tambahan dan kedua teks.
SizeMode.Responsive adalah kombinasi dari dua mode lainnya, dan memungkinkan Anda menentukan konten responsif dalam batas yang telah ditentukan. Secara umum, mode ini berperforma lebih baik dan memungkinkan transisi yang lebih lancar saat ukuran AppWidget diubah.
Tabel berikut menunjukkan nilai ukuran, bergantung pada SizeMode dan ukuran AppWidget yang tersedia:
| Ukuran yang tersedia | 105 x 110 | 203 x 112 | 72 x 72 | 203 x 150 |
|---|---|---|---|---|
SizeMode.Single |
110 x 110 | 110 x 110 | 110 x 110 | 110 x 110 |
SizeMode.Exact |
105 x 110 | 203 x 112 | 72 x 72 | 203 x 150 |
SizeMode.Responsive |
80 x 100 | 80 x 100 | 80 x 100 | 150 x 120 |
| * Nilai pastinya hanya untuk tujuan demo. |
SizeMode.Exact
SizeMode.Exact setara dengan menyediakan tata letak yang tepat, yang
meminta konten GlanceAppWidget setiap kali ukuran AppWidget yang tersedia
berubah (misalnya, saat pengguna mengubah ukuran AppWidget di layar utama).
Misalnya, di widget tujuan, tombol tambahan dapat ditambahkan jika lebar yang tersedia lebih besar dari nilai tertentu.
class MyAppWidget : GlanceAppWidget() { override val sizeMode = SizeMode.Exact override suspend fun provideGlance(context: Context, id: GlanceId) { // ... provideContent { MyContent() } } @Composable private fun MyContent() { // Size will be the size of the AppWidget val size = LocalSize.current Column { Text(text = "Where to?", modifier = GlanceModifier.padding(12.dp)) Row(horizontalAlignment = Alignment.CenterHorizontally) { Button() Button() if (size.width > 250.dp) { Button("School") } } } } }
Mode ini memberikan lebih banyak fleksibilitas daripada mode lainnya, tetapi memiliki beberapa peringatan:
AppWidgetharus dibuat ulang sepenuhnya setiap kali ukuran berubah. Hal ini dapat menyebabkan masalah performa dan lompatan UI saat kontennya kompleks.- Ukuran yang tersedia mungkin berbeda-beda bergantung pada penerapan peluncur. Misalnya, jika peluncur tidak memberikan daftar ukuran, ukuran minimum yang mungkin akan digunakan.
- Di perangkat sebelum Android 12, logika perhitungan ukuran mungkin tidak berfungsi di semua situasi.
Secara umum, Anda harus menggunakan mode ini jika SizeMode.Responsive tidak dapat digunakan (yaitu, kumpulan kecil tata letak responsif tidak layak).
Akses materi
Gunakan LocalContext.current untuk mengakses resource Android apa pun, seperti yang ditunjukkan dalam contoh berikut:
LocalContext.current.getString(R.string.glance_title)
Sebaiknya berikan ID resource secara langsung untuk mengurangi ukuran objek akhir
RemoteViews dan mengaktifkan resource dinamis, seperti warna
dinamis.
Composable dan metode menerima resource menggunakan "penyedia", seperti ImageProvider, atau menggunakan metode overload seperti GlanceModifier.background(R.color.blue). Contoh:
Column( modifier = GlanceModifier.background(R.color.default_widget_background) ) { /**...*/ } Image( provider = ImageProvider(R.drawable.ic_logo), contentDescription = "My image", )
Menangani teks
Glance 1.1.0 menyertakan API untuk menetapkan gaya teks Anda. Tetapkan gaya teks menggunakan atribut fontSize, fontWeight, atau fontFamily dari class TextStyle.
fontFamily mendukung semua font sistem, seperti yang ditunjukkan dalam contoh berikut, tetapi font kustom di aplikasi tidak didukung:
Text(
style = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 18.sp,
fontFamily = FontFamily.Monospace
),
text = "Example Text"
)
Menambahkan tombol compound
Tombol compound diperkenalkan di Android 12. Glance mendukung kompatibilitas mundur untuk jenis tombol compound berikut:
Setiap tombol compound ini menampilkan tampilan yang dapat diklik yang mewakili status "dicentang".
var isApplesChecked by remember { mutableStateOf(false) } var isEnabledSwitched by remember { mutableStateOf(false) } var isRadioChecked by remember { mutableIntStateOf(0) } CheckBox( checked = isApplesChecked, onCheckedChange = { isApplesChecked = !isApplesChecked }, text = "Apples" ) Switch( checked = isEnabledSwitched, onCheckedChange = { isEnabledSwitched = !isEnabledSwitched }, text = "Enabled" ) RadioButton( checked = isRadioChecked == 1, onClick = { isRadioChecked = 1 }, text = "Checked" )
Saat status berubah, lambda yang disediakan akan dipicu. Anda dapat menyimpan status centang, seperti yang ditunjukkan dalam contoh berikut:
class MyAppWidget : GlanceAppWidget() { override suspend fun provideGlance(context: Context, id: GlanceId) { val myRepository = MyRepository.getInstance() provideContent { val scope = rememberCoroutineScope() val saveApple: (Boolean) -> Unit = { scope.launch { myRepository.saveApple(it) } } MyContent(saveApple) } } @Composable private fun MyContent(saveApple: (Boolean) -> Unit) { var isAppleChecked by remember { mutableStateOf(false) } Button( text = "Save", onClick = { saveApple(isAppleChecked) } ) } }
Anda juga dapat memberikan atribut colors ke CheckBox, Switch, dan
RadioButton untuk menyesuaikan warnanya:
CheckBox( // ... colors = CheckboxDefaults.colors( checkedColor = ColorProvider(day = colorAccentDay, night = colorAccentNight), uncheckedColor = ColorProvider(day = Color.DarkGray, night = Color.LightGray) ), checked = isChecked, onCheckedChange = { isChecked = !isChecked } ) Switch( // ... colors = SwitchDefaults.colors( checkedThumbColor = ColorProvider(day = Color.Red, night = Color.Cyan), uncheckedThumbColor = ColorProvider(day = Color.Green, night = Color.Magenta), checkedTrackColor = ColorProvider(day = Color.Blue, night = Color.Yellow), uncheckedTrackColor = ColorProvider(day = Color.Magenta, night = Color.Green) ), checked = isChecked, onCheckedChange = { isChecked = !isChecked }, text = "Enabled" ) RadioButton( // ... colors = RadioButtonDefaults.colors( checkedColor = ColorProvider(day = Color.Cyan, night = Color.Yellow), uncheckedColor = ColorProvider(day = Color.Red, night = Color.Blue) ), )
Komponen tambahan
Glance 1.1.0 menyertakan rilis komponen tambahan, seperti yang dijelaskan dalam tabel berikut:
| Nama | Gambar | Link referensi | Catatan tambahan |
|---|---|---|---|
| Tombol Fill |
|
Komponen | |
| Tombol Outline |
|
Komponen | |
| Tombol Ikon |
|
Komponen | Utama / Sekunder / Khusus ikon |
| Kolom Judul |
|
Komponen | |
| Scaffold | Scaffold dan Kolom judul berada dalam demo yang sama. |
Untuk mengetahui informasi selengkapnya tentang spesifikasi desain, lihat desain komponen di kit desain ini di Figma.
Untuk mengetahui informasi selengkapnya tentang tata letak kanonis, lihat Tata letak widget kanonis.