Na tej stronie opisaliśmy, jak obsługiwać rozmiary i tworzyć elastyczne układy responsywne w Glance za pomocą istniejących komponentów Glance.
Używanie Box, Column i Row
Glance ma 3 główne układy z możliwością komponowania:
Box: umieszcza elementy jeden na drugim. Tłumaczy się naRelativeLayout.Column: umieszcza elementy jeden po drugim w osi pionowej. Tłumaczy się naLinearLayoutz orientacją pionową.Row: umieszcza elementy jeden po drugim w osi poziomej. Tłumaczy się naLinearLayoutz orientacją poziomą.
Glance obsługuje Scaffold obiekty. Umieść elementy z możliwością komponowania Column, Row i Box w danym obiekcie Scaffold.
Każdy z tych elementów z możliwością komponowania umożliwia zdefiniowanie wyrównania pionowego i poziomego jego treści oraz ograniczeń szerokości, wysokości, wagi lub dopełnienia za pomocą modyfikatorów. Ponadto każdy element podrzędny może zdefiniować swój modyfikator, aby zmienić odstępy i położenie w elemencie nadrzędnym.
Poniższy przykład pokazuje, jak utworzyć element Row, który równomiernie rozkłada elementy podrzędne w poziomie, jak widać na rysunku 1:
Row(modifier = GlanceModifier.fillMaxWidth().padding(16.dp)) { val modifier = GlanceModifier.defaultWeight() Text("first", modifier) Text("second", modifier) Text("third", modifier) }
Element Row wypełnia maksymalną dostępną szerokość, a ponieważ każdy element podrzędny ma taką samą wagę, równomiernie dzieli dostępną przestrzeń. 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
Innym sposobem na zapewnienie responsywności treści jest umożliwienie jej przewijania. Jest to możliwe dzięki elementowi z możliwością komponowania LazyColumn. Ten element z możliwością komponowania umożliwia zdefiniowanie zestawu elementów, które mają być wyświetlane w kontenerze z możliwością przewijania w widżecie aplikacji.
Poniższe fragmenty kodu pokazują różne sposoby definiowania elementów 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 poprzednich 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") } }
Zwróć uwagę, że poprzedni fragment kodu nie określa itemId. Określenie itemId pomaga zwiększyć wydajność i zachować pozycję przewijania podczas aktualizacji listy i appWidget w Androidzie 12 i nowszych wersjach (np. podczas dodawania lub usuwania elementów z listy). Poniższy przykład pokazuje, jak określić itemId:
items( items = peopleList, itemId = { person -> person.id.hashCode().toLong() }) { person -> Text(person.name) }
Przewijanie z przyciąganiem
Przewijanie z przyciąganiem to animacja, która umożliwia przyciąganie treści z możliwością przewijania do górnej części kontenera widżetu.
Aby zaimplementować przewijanie z przyciąganiem, musisz spełnić te warunki:
- Zaktualizuj zależność Glance do wersji 1.3.0-alpha02 lub nowszej.
- Ustaw
compileSdkna 37 lub nowszy, ponieważ przewijanie z przyciąganiem jest obsługiwane na urządzeniach z Androidem 17 i nowszym. - Skonfiguruj
LazyColumnza pomocąVerticalScrollMode. Jeśli urządzenie obsługuje przewijanie z przyciąganiem, użyjSnapScrollMatchHeight. W przeciwnym razie użyjNormal.
Jeśli używasz przewijania z przyciąganiem w przypadku obrazów, zapoznaj się z kanonicznym układem obrazu na całą szerokość.
@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) ) } }
Definiowanie SizeMode
AppWidget rozmiary mogą się różnić w zależności od urządzenia, wyboru użytkownika lub programu uruchamiającego,
dlatego ważne jest, aby zapewnić elastyczne układy, jak opisano na stronie Zapewnianie
elastycznych układów widżetów. Glance upraszcza to dzięki definicji SizeMode i wartości LocalSize. W kolejnych sekcjach opisujemy 3 tryby.
SizeMode.Single
SizeMode.Single to tryb domyślny. Wskazuje, że podano tylko 1 typ treści. Oznacza to, że nawet jeśli zmieni się dostępny rozmiar AppWidget, rozmiar treści nie ulegnie zmianie.
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 // ... } }
W tym trybie upewnij się, że:
- wartości metadanych minimalnego i maksymalnego rozmiaru są prawidłowo zdefiniowane na podstawie rozmiaru treści;
- treść jest wystarczająco elastyczna w oczekiwanym zakresie rozmiarów.
Ogólnie rzecz biorąc, tego trybu należy używać, gdy:
a) AppWidget ma stały rozmiar lub b) nie zmienia treści po zmianie rozmiaru.
SizeMode.Responsive
Ten tryb jest odpowiednikiem zapewniania układów responsywnych, co umożliwia
GlanceAppWidget zdefiniowanie zestawu układów responsywnych ograniczonych określonymi
rozmiarami. W przypadku każdego zdefiniowanego rozmiaru treść jest tworzona i przypisywana do określonego rozmiaru podczas tworzenia lub aktualizowania AppWidget. System wybiera wtedy najlepiej dopasowany rozmiar na podstawie dostępnego rozmiaru.
Na przykład w naszym AppWidget docelowym możesz zdefiniować 3 rozmiary i ich 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 przypisywana do zdefiniowanego rozmiaru.
- Przy pierwszym wywołaniu rozmiar jest oceniany na
100x100. Treść nie zawiera dodatkowego przycisku ani tekstu u góry i u dołu. - Przy drugim wywołaniu rozmiar jest oceniany na
250x100. Treść zawiera dodatkowy przycisk, ale nie zawiera tekstu u góry i u dołu. - Przy trzecim wywołaniu rozmiar jest oceniany na
250x250. Treść zawiera dodatkowy przycisk oraz oba teksty.
SizeMode.Responsive to połączenie 2 pozostałych trybów, które umożliwia zdefiniowanie treści responsywnych w ramach predefiniowanych granic. Ogólnie rzecz biorąc, ten tryb działa lepiej i umożliwia płynniejsze przejścia podczas zmiany rozmiaru AppWidget.
W tabeli poniżej przedstawiono wartość rozmiaru w zależności od SizeMode i dostępnego rozmiaru 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ą podane tylko na potrzeby demonstracji. |
SizeMode.Exact
SizeMode.Exact jest odpowiednikiem zapewniania dokładnych układów, które
żądają treści GlanceAppWidget za każdym razem, gdy zmieni się dostępny rozmiar AppWidget (np. gdy użytkownik zmieni rozmiar 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 niż określona wartość.
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 wad:
AppWidgetmusi być całkowicie odtworzony za każdym razem, gdy zmieni się jego rozmiar. W przypadku złożonych treści może to powodować problemy z wydajnością i przeskoki interfejsu.- 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 udostępnia listy rozmiarów, używany jest najmniejszy możliwy rozmiar.
- Na urządzeniach z Androidem starszym niż 12 logika obliczania rozmiaru może nie działać we wszystkich sytuacjach.
Ogólnie rzecz biorąc, tego trybu należy używać, jeśli nie można użyć SizeMode.Responsive (czyli gdy mały zestaw układów responsywnych nie jest możliwy).
Dostęp do zasobów
Aby uzyskać dostęp do dowolnego zasobu Androida, użyj LocalContext.current, jak pokazano w tym przykładzie:
LocalContext.current.getString(R.string.glance_title)
Zalecamy podawanie identyfikatorów zasobów bezpośrednio, aby zmniejszyć rozmiar końcowego
RemoteViews obiektu i włączyć zasoby dynamiczne, takie jak dynamiczne
kolory.
Elementy z możliwością komponowania i metody akceptują zasoby za pomocą „dostawcy”, np. ImageProvider, lub za pomocą metody przeciążonej, np. GlanceModifier.background(R.color.blue). Przykład:
Column( modifier = GlanceModifier.background(R.color.default_widget_background) ) { /**...*/ } Image( provider = ImageProvider(R.drawable.ic_logo), contentDescription = "My image", )
Obsługa tekstu
Glance 1.1.0 zawiera interfejs API do ustawiania stylów tekstu. Style tekstu ustawiaj za pomocą atrybutów fontSize, fontWeight lub fontFamily klasy TextStyle.
fontFamily obsługuje wszystkie czcionki systemowe, jak pokazano w tym przykładzie, ale czcionki niestandardowe w aplikacjach nie są obsługiwane:
Text( style = TextStyle( fontWeight = FontWeight.Bold, fontSize = 18.sp, fontFamily = FontFamily.Monospace ), text = "Example Text" )
Dodawanie przycisków złożonych
Przyciski złożone zostały wprowadzone w Androidzie 12. Glance obsługuje zgodność wsteczną w przypadku tych typów przycisków złożonych:
Każdy z tych przycisków złożonych wyświetla widok z możliwością kliknięcia, który reprezentuje stan „zaznaczony”.
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" )
Gdy stan się zmieni, zostanie wywołana podana lambda. Stan zaznaczenia możesz zapisać, jak pokazano w tym przykładzie:
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ż podać atrybut colors dla 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) ), )
Dodatkowe komponenty
Glance 1.1.0 zawiera dodatkowe komponenty, jak opisano w tabeli poniżej:
| Nazwa | Obraz | Link odsyłający | Uwagi dodatkowe |
|---|---|---|---|
| Wypełniony przycisk |
|
Komponent | |
| Przyciski z konturem |
|
Komponent | |
| Przyciski ikon |
|
Komponent | Podstawowy / Dodatkowy / Tylko ikona |
| Pasek tytułu |
|
Komponent | |
| Scaffold | Scaffold i pasek tytułu są w tej samej wersji demonstracyjnej. |
Więcej informacji o szczegółach projektu znajdziesz w projektach komponentów w tym zestawie projektów w Figma.
Więcej informacji o układach kanonicznych znajdziesz w artykule Kanoniczne układy widżetów.