Trang này mô tả cách xử lý các kích thước cũng như cung cấp bố cục linh hoạt và thích ứng bằng tính năng Waze.
Sử dụng Box
, Column
và Row
Xem nhanh có 3 bố cục thành phần kết hợp chính:
Box
: Đặt các thành phần chồng lên nhau. Dòng này sẽ được dịch sangRelativeLayout
.Column
: Đặt các phần tử sau nhau trong trục tung. Giá trị này được dịch sangLinearLayout
theo hướng dọc.Row
: Đặt các phần tử sau nhau trong trục hoành. Mã này được dịch sangLinearLayout
theo hướng ngang.
Mỗi thành phần kết hợp này cho phép bạn xác định cách căn chỉnh dọc và ngang của nội dung, cũng như các giới hạn chiều rộng, chiều cao, trọng số hoặc khoảng đệm bằng đối tượng sửa đổi. Ngoài ra, mỗi thành phần con có thể xác định đối tượng sửa đổi để thay đổi không gian và vị trí bên trong thành phần mẹ.
Ví dụ sau đây minh hoạ cách tạo Row
giúp phân bổ đồng đều các phần tử con của nó theo chiều ngang, như bạn thấy trong Hình 1:
Row(modifier = GlanceModifier.fillMaxWidth().padding(16.dp)) { val modifier = GlanceModifier.defaultWeight() Text("first", modifier) Text("second", modifier) Text("third", modifier) }
Row
lấp đầy chiều rộng tối đa có sẵn và vì mỗi thành phần con có cùng trọng số nên chúng chia sẻ không gian có sẵn như nhau. Bạn có thể xác định nhiều trọng số, kích thước, khoảng đệm hoặc cách căn chỉnh để điều chỉnh bố cục theo nhu cầu của mình.
Dùng bố cục có thể cuộn
Một cách khác để cung cấp nội dung thích ứng là làm cho nội dung có thể cuộn được. Bạn có thể thực hiện điều này với thành phần kết hợp LazyColumn
. Thành phần kết hợp này cho phép bạn xác định một tập hợp các mục sẽ hiển thị bên trong một vùng chứa có thể cuộn trong tiện ích ứng dụng.
Các đoạn mã sau đây cho thấy những cách xác định mục bên trong LazyColumn
.
Bạn có thể cung cấp số lượng mặt hàng:
// Remember to import Glance Composables // import androidx.glance.appwidget.layout.LazyColumn LazyColumn { items(10) { index: Int -> Text( text = "Item $index", modifier = GlanceModifier.fillMaxWidth() ) } }
Cung cấp các mục riêng lẻ:
LazyColumn { item { Text("First Item") } item { Text("Second Item") } }
Cung cấp một danh sách hoặc mảng mặt hàng:
LazyColumn { items(peopleNameList) { name -> Text(name) } }
Bạn cũng có thể sử dụng kết hợp các ví dụ trên:
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") } }
Xin lưu ý rằng đoạn mã trước không chỉ định itemId
. Việc chỉ định itemId
sẽ giúp cải thiện hiệu suất và duy trì vị trí cuộn thông qua nội dung cập nhật danh sách và appWidget
từ Android 12 trở đi (ví dụ: khi thêm hoặc xoá các mục khỏi danh sách). Ví dụ sau cho thấy cách chỉ định một itemId
:
items(items = peopleList, key = { person -> person.id }) { person -> Text(person.name) }
Xác định SizeMode
Kích thước của AppWidget
có thể khác nhau tuỳ thuộc vào thiết bị, lựa chọn của người dùng hoặc trình chạy, vì vậy, bạn cần phải cung cấp bố cục linh hoạt như mô tả trên trang Cung cấp bố cục tiện ích linh hoạt. Xem nhanh sẽ đơn giản hoá việc này bằng định nghĩa SizeMode
và giá trị LocalSize
. Các phần sau đây mô tả 3 chế độ này.
SizeMode.Single
SizeMode.Single
là chế độ mặc định. Thuộc tính này cho biết chỉ một loại nội dung được cung cấp; tức là ngay cả khi kích thước có sẵn của AppWidget
thay đổi, kích thước nội dung vẫn không thay đổi.
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 // ... } }
Khi sử dụng chế độ này, hãy đảm bảo rằng:
- Giá trị siêu dữ liệu có kích thước tối thiểu và tối đa được xác định phù hợp dựa trên kích thước nội dung.
- Nội dung đủ linh hoạt trong phạm vi kích thước dự kiến.
Nói chung, bạn nên sử dụng chế độ này khi:
a) AppWidget
có kích thước cố định, hoặc
b) không thay đổi nội dung khi đổi kích thước.
SizeMode.Responsive
Chế độ này tương đương với việc cung cấp bố cục thích ứng, cho phép GlanceAppWidget
xác định một tập hợp bố cục thích ứng bị giới hạn bởi các kích thước cụ thể. Đối với mỗi kích thước đã xác định, nội dung sẽ được tạo và ánh xạ tới kích thước cụ thể khi AppWidget
được tạo hoặc cập nhật. Sau đó, hệ thống sẽ chọn kiểu khớp phù hợp nhất dựa trên kích thước có sẵn.
Ví dụ: trong đích đến AppWidget
, bạn có thể xác định ba kích thước và nội dung của kích thước đó:
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") } } } }
Trong ví dụ trước, phương thức provideContent
được gọi 3 lần và được liên kết tới kích thước đã xác định.
- Trong lệnh gọi đầu tiên, kích thước được đánh giá là
100x100
. Nội dung không chứa nút bổ sung, cũng như văn bản trên cùng và dưới cùng. - Trong lệnh gọi thứ hai, kích thước được đánh giá là
250x100
. Nội dung bao gồm nút bổ sung, nhưng không bao gồm văn bản trên cùng và dưới cùng. - Trong lệnh gọi thứ ba, kích thước được đánh giá là
250x250
. Nội dung bao gồm nút bổ sung và cả hai văn bản.
SizeMode.Responsive
là sự kết hợp của 2 chế độ khác và cho phép bạn xác định nội dung thích ứng trong các giới hạn được xác định trước. Nhìn chung, chế độ này hoạt động tốt hơn và cho phép chuyển đổi mượt mà hơn khi AppWidget
được đổi kích thước.
Bảng sau đây cho biết giá trị của kích thước, tuỳ thuộc vào kích thước có sẵn của SizeMode
và AppWidget
:
Kích thước có sẵn | 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 |
* Các giá trị chính xác chỉ dành cho mục đích minh hoạ. |
SizeMode.Exact
SizeMode.Exact
tương đương với việc cung cấp bố cục chính xác, yêu cầu nội dung GlanceAppWidget
mỗi khi kích thước AppWidget
có sẵn thay đổi (ví dụ: khi người dùng đổi kích thước AppWidget
trong màn hình chính).
Ví dụ: trong tiện ích đích, bạn có thể thêm một nút bổ sung nếu chiều rộng có sẵn lớn hơn một giá trị nhất định.
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") } } } } }
Chế độ này mang lại tính linh hoạt cao hơn so với các chế độ khác, nhưng vẫn có một số điểm cần lưu ý:
AppWidget
phải được tạo lại hoàn toàn mỗi khi kích thước thay đổi. Điều này có thể dẫn đến các vấn đề về hiệu suất và giao diện người dùng sẽ nhảy khi nội dung có sự phức tạp.- Kích thước có sẵn có thể khác nhau tuỳ theo cách triển khai của trình chạy. Ví dụ: nếu trình chạy không cung cấp danh sách kích thước, thì kích thước tối thiểu có thể sẽ được sử dụng.
- Trên các thiết bị chạy phiên bản trước Android 12, logic tính toán kích thước có thể không hoạt động trong mọi trường hợp.
Nhìn chung, bạn nên sử dụng chế độ này nếu không sử dụng được SizeMode.Responsive
(nghĩa là không thể sử dụng một tập nhỏ bố cục thích ứng).
Truy cập vào tài nguyên
Sử dụng LocalContext.current
để truy cập vào tài nguyên Android bất kỳ, như trong ví dụ sau:
LocalContext.current.getString(R.string.glance_title)
Bạn nên trực tiếp cung cấp mã nhận dạng tài nguyên để giảm kích thước của đối tượng RemoteViews
cuối cùng và để bật các tài nguyên động, chẳng hạn như màu động.
Các thành phần kết hợp và phương thức chấp nhận tài nguyên bằng cách sử dụng "trình cung cấp", chẳng hạn như ImageProvider
hoặc sử dụng phương thức nạp chồng như GlanceModifier.background(R.color.blue)
. Ví dụ:
Column( modifier = GlanceModifier.background(R.color.default_widget_background) ) { /**...*/ } Image( provider = ImageProvider(R.drawable.ic_logo), contentDescription = "My image", )
Thêm nút phức hợp
Các nút kết hợp đã được ra mắt trong Android 12. Xem nhanh hỗ trợ khả năng tương thích ngược cho các loại nút phức hợp sau đây:
Mỗi nút phức hợp này cho thấy một khung hiển thị có thể nhấp, biểu thị trạng thái "đã đánh dấu".
var isApplesChecked by remember { mutableStateOf(false) } var isEnabledSwitched by remember { mutableStateOf(false) } var isRadioChecked by remember { mutableStateOf(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" )
Khi trạng thái thay đổi, hàm lambda đã cung cấp sẽ được kích hoạt. Bạn có thể lưu trữ trạng thái kiểm tra như trong ví dụ sau:
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) } ) } }
Bạn cũng có thể cung cấp thuộc tính colors
cho CheckBox
, Switch
và RadioButton
để tuỳ chỉnh màu sắc:
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) ), )