Na tej stronie opisujemy, jak obsługiwać rozmiary oraz tworzyć elastyczne i elastyczne układy za pomocą Glance.
Użyj aplikacji Box
, Column
i Row
W Glance dostępne są 3 główne układy kompozycyjne:
Box
: umieszcza elementy nad drugim. Tłumaczy naRelativeLayout
.Column
: umieszcza elementy jeden po drugim na osi pionowej. Wyświetla się w poluLinearLayout
w orientacji pionowej.Row
: umieszcza elementy jeden po drugim na osi poziomej. Wyświetla się w poluLinearLayout
w orientacji poziomej.
Każdy z tych elementów kompozycyjnych umożliwia definiowanie wyrównania w pionie i poziomie treści oraz ograniczeń szerokości, wysokości, wagi i dopełnienia za pomocą modyfikatorów. Dodatkowo każdy element podrzędny może zdefiniować swój modyfikator zmieniający spację i położenie w obrębie elementu nadrzędnego.
Poniższy przykład pokazuje, jak utworzyć element Row
, który równomiernie rozkłada elementy podrzędne w poziomie, tak jak to widać na rysunku 1:
Row(modifier = GlanceModifier.fillMaxWidth().padding(16.dp)) { val modifier = GlanceModifier.defaultWeight() Text("first", modifier) Text("second", modifier) Text("third", modifier) }
Row
wypełnia maksymalną dostępną szerokość, a ponieważ każdy element podrzędny ma taką samą wagę, dzieli je równomiernie na dostępne miejsce. Możesz zdefiniować różne wagi, rozmiary, dopełnienia lub wyrównania, aby dostosować układy do swoich potrzeb.
Używanie układów z możliwością przewijania
Kolejnym sposobem zapewniania elastycznej zawartości jest umożliwienie jej przewijania. Jest to możliwe dzięki elementowi kompozycyjnemu LazyColumn
. Ta funkcja kompozycyjna pozwala zdefiniować zbiór elementów wyświetlanych w kontenerze z możliwością przewijania w widżecie aplikacji.
Poniższe fragmenty kodu pokazują różne sposoby definiowania elementów w obrębie znaczników LazyColumn
.
Możesz podać liczbę elementów:
// Remember to import Glance Composables // import androidx.glance.appwidget.layout.LazyColumn LazyColumn { items(10) { index: Int -> Text( text = "Item $index", modifier = GlanceModifier.fillMaxWidth() ) } }
Podaj poszczególne elementy:
LazyColumn { item { Text("First Item") } item { Text("Second Item") } }
Podaj listę lub tablicę elementów:
LazyColumn { items(peopleNameList) { name -> Text(name) } }
Możesz też użyć kombinacji powyższych przykładów:
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") } }
Pamiętaj, że poprzedni fragment nie zawiera tych danych: itemId
. Określenie itemId
pomaga zwiększyć wydajność i utrzymać pozycję przewijania na liście oraz aktualizacje appWidget
od Androida 12 i nowszych (np. przy dodawaniu elementów do listy lub ich usuwaniu). Ten przykład pokazuje, jak określić itemId
:
items(items = peopleList, key = { person -> person.id }) { person -> Text(person.name) }
Zdefiniuj pole SizeMode
Rozmiary AppWidget
mogą się różnić w zależności od urządzenia, wyboru użytkownika lub programu uruchamiającego, dlatego ważne jest zapewnienie elastycznych układów opisanych na stronie Udostępnianie elastycznych układów widżetów. Funkcja Glance upraszcza tę sprawę, korzystając z definicji SizeMode
i wartości LocalSize
. W sekcjach poniżej opisujemy 3 tryby.
SizeMode.Single
Trybem domyślnym jest SizeMode.Single
. Oznacza to, że dostarczany jest tylko 1 typ treści. Oznacza to, że nawet jeśli zmieni się rozmiar dostępnego elementu AppWidget
, rozmiar treści się nie zmieni.
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 // ... } }
Podczas korzystania z tego trybu upewnij się, że:
- Minimalny i maksymalny wartości metadanych rozmiaru są prawidłowo zdefiniowane na podstawie rozmiaru treści.
- Treść jest wystarczająco elastyczna w oczekiwanym zakresie rozmiarów.
Ogólnie tego trybu należy używać, gdy:
a) element AppWidget
ma stały rozmiar lub b) nie zmienia swojej zawartości po zmianie rozmiaru.
SizeMode.Responsive
Ten tryb jest odpowiednikiem udostępniania układów elastycznych, które umożliwiają GlanceAppWidget
definiowanie zestawu układów elastycznych ograniczonych przez określone rozmiary. W przypadku każdego zdefiniowanego rozmiaru treść jest tworzona i mapowana na konkretny rozmiar podczas tworzenia lub aktualizacji elementu AppWidget
. a następnie system wybierze najlepiej dopasowane na podstawie dostępnego rozmiaru.
Na przykład w elemencie docelowym AppWidget
możesz zdefiniować 3 rozmiary i jego zawartość:
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") } } } }
W poprzednim przykładzie metoda provideContent
jest wywoływana 3 razy i mapowana na zdefiniowany rozmiar.
- W pierwszym wywołaniu rozmiar przyjmuje wartość
100x100
. Nie zawiera dodatkowego przycisku ani tekstu na górze i na dole. - W drugim wywołaniu rozmiar przyjmuje wartość
250x100
. Treść zawiera dodatkowy przycisk, ale nie zawiera tekstu na górze ani na dole. - W trzecim wywołaniu rozmiar przyjmuje wartość
250x250
. Zawiera ona dodatkowy przycisk i oba teksty.
SizeMode.Responsive
to połączenie dwóch pozostałych trybów i umożliwia definiowanie treści elastycznych w zdefiniowanych wstępnie granicach. Ogólnie ten tryb działa lepiej i umożliwia płynniejsze przejścia po zmianie rozmiaru elementu AppWidget
.
W tabeli poniżej znajdziesz wartości rozmiaru w zależności od dostępnego rozmiaru SizeMode
i AppWidget
:
Dostępny rozmiar | 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 |
* Dokładne wartości służą tylko do celów demonstracyjnych. |
SizeMode.Exact
SizeMode.Exact
jest odpowiednikiem udostępniania dokładnych układów, które powoduje żądanie treści GlanceAppWidget
za każdym razem, gdy zmieni się dostępny rozmiar AppWidget
(np. gdy użytkownik zmieni rozmiar elementu AppWidget
na ekranie głównym).
Na przykład w widżecie docelowym można dodać dodatkowy przycisk, jeśli dostępna szerokość jest większa od określonej wartości.
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") } } } } }
Ten tryb zapewnia większą elastyczność niż inne, ale ma kilka ograniczeń:
- Przy każdej zmianie rozmiaru trzeba ponownie utworzyć atrybut
AppWidget
. Może to powodować problemy z wydajnością i przeskakiwanie interfejsu, gdy treść jest złożona. - Dostępny rozmiar może się różnić w zależności od implementacji programu uruchamiającego. Jeśli na przykład program uruchamiający nie podaje listy rozmiarów, używany jest minimalny możliwy rozmiar.
- Na urządzeniach starszych niż Android 12 logika obliczania rozmiaru może nie działać w niektórych sytuacjach.
Ogólnie używaj tego trybu, jeśli nie można użyć SizeMode.Responsive
(czyli nie da się użyć małego zestawu układów elastycznych).
Dostęp do zasobów
Użyj metody LocalContext.current
, aby uzyskać dostęp do dowolnych zasobów Androida, jak w tym przykładzie:
LocalContext.current.getString(R.string.glance_title)
Zalecamy bezpośrednie podawanie identyfikatorów zasobów. Pozwoli to zmniejszyć rozmiar końcowego obiektu RemoteViews
i włączyć zasoby dynamiczne, takie jak kolory dynamiczne.
Obiekty kompozycyjne i metody akceptują zasoby, korzystając z funkcji dostawcy, np. ImageProvider
, lub metody przeciążenia, takiej jak GlanceModifier.background(R.color.blue)
. Na przykład:
Column( modifier = GlanceModifier.background(R.color.default_widget_background) ) { /**...*/ } Image( provider = ImageProvider(R.drawable.ic_logo), contentDescription = "My image", )
Dodaj przyciski złożone
Przyciski złożone zostały wprowadzone w Androidzie 12. Funkcja Glance obsługuje wsteczną zgodność tych typów przycisków złożonych:
Każdy z tych złożonych przycisków wyświetla klikalny widok, który przedstawia stan „zaznaczony”.
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" )
Po zmianie stanu wywoływana jest podana funkcja lambda. Stan kontroli możesz zapisać w następujący sposób:
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) } ) } }
Możesz też dodać atrybut colors
do elementów CheckBox
, Switch
i RadioButton
, aby dostosować ich kolory:
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) ), )