Na tej stronie opisaliśmy, jak obsługiwać rozmiary i zapewniać elastyczne i responsywne układy za pomocą Glance, korzystając z dotychczasowych komponentów Glance.
Użyj właściwości Box
, Column
i Row
.
Glance ma 3 główne układy:
Box
: umieszcza elementy jeden na drugim. Jest toRelativeLayout
.Column
: elementy są umieszczane jeden po drugim na osi pionowej. Przekształca się wLinearLayout
o orientacji pionowej.Row
: elementy są umieszczane jeden po drugim na osi poziomej. Przekształca się on wLinearLayout
w orientacji poziomej.
Glance obsługuje obiekty Scaffold
. Umieść komponenty Column
, Row
i Box
w danym obiekcie Scaffold
.
Każdy z tych komponentów umożliwia definiowanie wyrównania pionowego i poziomego treści oraz ograniczeń szerokości, wysokości, wagi lub wypełniania za pomocą modyfikatorów. Ponadto każdy element potomny może zdefiniować swój modyfikator, aby zmienić odstęp i położenie w elemencie nadrzędnym.
Z tego przykładu dowiesz się, jak utworzyć element Row
, który równomiernie rozmieści swoje elementy potomne w poziomie (patrz rysunek 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żde dziecko ma taką samą wagę, mają równy dostęp do dostępnej przestrzeni. Aby dostosować układy do swoich potrzeb, możesz zdefiniować różne wagi, rozmiary, odstępy lub wyrównania.
Używanie układów z możliwością przewijania
Innym sposobem na wyświetlanie responsywnych treści jest umożliwienie ich przewijania. Jest to możliwe dzięki komponentowi LazyColumn
. Ta kompozycja umożliwia zdefiniowanie zestawu elementów, które mają być wyświetlane w kontenerze, którego zawartość można przewijać, w widżecie aplikacji.
Poniższe fragmenty kodu pokazują różne sposoby definiowania elementów w elemencie 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() ) } }
Dostarczaj 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 kodu nie określa wartości itemId
. Określanie pozycji itemId
pomaga poprawić wydajność i utrzymać pozycję przewijania podczas aktualizacji listy i appWidget
od Androida 12 (np. podczas dodawania lub usuwania elementów z listy). Poniższy przykład pokazuje, jak określić itemId
:
items(items = peopleList, key = { person -> person.id }) { person -> Text(person.name) }
Zdefiniuj SizeMode
Rozmiary AppWidget
mogą się różnić w zależności od urządzenia, wyboru użytkownika lub programu uruchamiania, dlatego ważne jest, aby udostępniać elastyczne układy zgodnie z opisem na stronie Umieszczanie elastycznych układów widżetów. Skróty upraszczają to dzięki definicji SizeMode
i wartości LocalSize
. W sekcjach poniżej opisujemy 3 tryby.
SizeMode.Single
SizeMode.Single
to domyślny tryb. Wskazuje, że jest dostępny tylko jeden typ treści. Oznacza to, że nawet jeśli rozmiar AppWidget
ulegnie zmianie, rozmiar treści pozostanie bez zmian.
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:
- Minimalne i maksymalne rozmiary wartości metadanych są odpowiednio zdefiniowane na podstawie rozmiaru treści.
- Treści są wystarczająco elastyczne w oczekiwanym zakresie rozmiarów.
Ogólnie rzecz biorąc, należy używać tego trybu, 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 pozwala GlanceAppWidget
zdefiniować zestaw układów responsywnych ograniczonych określonymi rozmiarami. W przypadku każdego zdefiniowanego rozmiaru treści są tworzone i mapowane na konkretny rozmiar podczas tworzenia lub aktualizowania AppWidget
. Następnie system wybierze najbardziej pasujący na podstawie dostępnego rozmiaru.
Na przykład w miejscu docelowym AppWidget
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 przypisana do zdefiniowanego rozmiaru.
- W pierwszym wywołaniu rozmiar ma wartość
100x100
. Treść nie zawiera dodatkowego przycisku ani tekstów u góry i dołu. - W drugim wywołaniu rozmiar ma wartość
250x100
. Treść obejmuje dodatkowy przycisk, ale nie teksty u góry i na dole. - W 3 wywołaniu rozmiar jest określany jako
250x250
. Treści obejmują dodatkowy przycisk i oba teksty.
SizeMode.Responsive
to połączenie 2 innych trybów, które umożliwia definiowanie treści responsywnych w określonych granicach. Ogólnie ten tryb zapewnia lepszą wydajność i płynniejsze przejścia przy zmianie rozmiaru AppWidget
.
W tabeli poniżej znajdziesz wartość 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ą podane tylko w celu demonstracyjnym. |
SizeMode.Exact
SizeMode.Exact
jest równoważne dostarczaniu dokładnych układów, które powoduje żądanie treści GlanceAppWidget
za każdym razem, gdy zmienia się dostępna wielkość AppWidget
(np. gdy użytkownik zmienia rozmiar AppWidget
na ekranie głównym).
Na przykład w widżecie miejsca docelowego 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 ograniczeń:
- Za każdym razem, gdy zmienia się rozmiar,
AppWidget
musi zostać całkowicie odtworzona. Może to powodować problemy z wydajnością i przeskakiwanie interfejsu, gdy treści są złożone. - Dostępny rozmiar może się różnić w zależności od implementacji programu uruchamiającego. Jeśli na przykład w launcherze nie ma listy rozmiarów, używany jest najmniejszy możliwy rozmiar.
- Na urządzeniach z Androidem 11 i starszym logika obliczania rozmiaru może nie działać we wszystkich sytuacjach.
Z tego trybu należy korzystać, jeśli nie można użyć SizeMode.Responsive
(czyli nie można utworzyć małego zestawu elastycznych układów).
Dostęp do zasobów
Aby uzyskać dostęp do dowolnego zasobu Androida, użyj LocalContext.current
, jak w tym przykładzie:
LocalContext.current.getString(R.string.glance_title)
Zalecamy podanie identyfikatorów zasobów bezpośrednio, aby zmniejszyć rozmiar końcowego obiektu RemoteViews
i umożliwić korzystanie z zasobów dynamicznych, takich jak dynamiczne kolory.
Elementy składane i metody przyjmują zasoby za pomocą „dostawcy”, takiego jak ImageProvider
, lub za pomocą metody przeciążenia, takiej jak 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. Ustaw style tekstu za pomocą atrybutów fontSize
, fontWeight
lub fontFamily
klasy TextStyle.
fontFamily
obsługuje wszystkie czcionki systemowe, jak 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 złożonych przycisków
W Androidzie 12 wprowadzono złożone przyciski. Glance obsługuje zgodność wsteczną w przypadku tych typów złożonych przycisków:
Te złożone przyciski wyświetlają widok, który można kliknąć i który reprezentuje 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" )
Gdy stan się zmieni, zostanie uruchomiony podany zasób Lambda. Stan sprawdzania możesz zapisać w ten 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) } ) } }
Aby dostosować kolory, możesz też ustawić atrybut colors
na CheckBox
, Switch
lub RadioButton
:
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
Wersja Glance 1.1.0 zawiera dodatkowe komponenty opisane w tabeli poniżej:
Nazwa | Obraz | Link referencyjny | Uwagi dodatkowe |
---|---|---|---|
Wypełniony przycisk | Składnik | ||
Przyciski z konturem | Składnik | ||
Ikony przycisków | Składnik | Główna / dodatkowa / tylko ikona | |
Pasek tytułu | Składnik | ||
Rusztowanie | Szablon i pasek tytułu znajdują się w tym samym pokazie. |
Więcej informacji o szczegółach projektu znajdziesz w tym składzie projektowym w programie Figma.
Więcej informacji o kanonicznej wersji układu znajdziesz w artykule Kanoniczna wersja układu widżetu.