Auf dieser Seite wird beschrieben, wie Sie mit Glance und vorhandenen Glance-Komponenten Größen verwalten und flexible und responsive Layouts bereitstellen.
Verwende Box, Column und Row
Glance bietet drei Hauptlayouts:
Box: Platziert Elemente übereinander. Er wird in einenRelativeLayoutübersetzt.Column: Platziert Elemente nacheinander auf der vertikalen Achse. Sie wird in einLinearLayoutmit vertikaler Ausrichtung übersetzt.Row: Platziert Elemente nacheinander auf der horizontalen Achse. Sie wird in einLinearLayoutmit horizontaler Ausrichtung übersetzt.
Glance unterstützt Scaffold-Objekte. Platzieren Sie die Composables Column, Row und Box in einem bestimmten Scaffold-Objekt.
Mit jedem dieser Composables können Sie die vertikale und horizontale Ausrichtung des Inhalts sowie die Einschränkungen für Breite, Höhe, Gewicht oder Abstände mithilfe von Modifikatoren definieren. Außerdem kann jedes untergeordnete Element einen eigenen Modifikator definieren, um den Abstand und die Platzierung innerhalb des übergeordneten Elements zu ändern.
Im folgenden Beispiel wird gezeigt, wie Sie ein Row erstellen, das seine untergeordneten Elemente gleichmäßig horizontal verteilt, wie in Abbildung 1 zu sehen:
Row(modifier = GlanceModifier.fillMaxWidth().padding(16.dp)) { val modifier = GlanceModifier.defaultWeight() Text("first", modifier) Text("second", modifier) Text("third", modifier) }
Das Row-Element füllt die maximal verfügbare Breite aus. Da jedes untergeordnete Element dieselbe Gewichtung hat, wird der verfügbare Platz gleichmäßig aufgeteilt. Sie können verschiedene Gewichtungen, Größen, Abstände oder Ausrichtungen definieren, um Layouts an Ihre Anforderungen anzupassen.
Scrollbare Layouts verwenden
Eine weitere Möglichkeit, responsive Inhalte bereitzustellen, besteht darin, sie scrollbar zu machen. Dies ist mit der zusammensetzbaren Funktion LazyColumn möglich. Mit diesem Composable können Sie eine Reihe von Elementen definieren, die in einem scrollbaren Container im App-Widget angezeigt werden sollen.
Die folgenden Snippets zeigen verschiedene Möglichkeiten, Elemente innerhalb von LazyColumn zu definieren.
Sie können die Anzahl der Elemente angeben:
// Remember to import Glance Composables // import androidx.glance.appwidget.layout.LazyColumn LazyColumn { items(10) { index: Int -> Text( text = "Item $index", modifier = GlanceModifier.fillMaxWidth() ) } }
Einzelne Elemente angeben:
LazyColumn { item { Text("First Item") } item { Text("Second Item") } }
Geben Sie eine Liste oder ein Array von Elementen an:
LazyColumn { items(peopleNameList) { name -> Text(name) } }
Sie können auch eine Kombination der vorherigen Beispiele verwenden:
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") } }
Im vorherigen Snippet wird die itemId nicht angegeben. Durch die Angabe von itemId wird die Leistung verbessert und die Scrollposition bei Listen- und appWidget-Aktualisierungen ab Android 12 beibehalten (z. B. beim Hinzufügen oder Entfernen von Elementen aus der Liste). Im folgenden Beispiel wird gezeigt, wie Sie einen itemId angeben:
items( items = peopleList, itemId = { person -> person.id.hashCode().toLong() }) { person -> Text(person.name) }
Scrollen mit Einrasten
Snap-Scrolling ist eine Animation, mit der scrollbare Inhalte am oberen Rand des Widget-Containers einrasten können.
Damit Sie Snap-Scrolling implementieren können, müssen die folgenden Bedingungen erfüllt sein:
- Aktualisieren Sie die Glance-Abhängigkeit auf 1.3.0-alpha02 oder höher.
- Setze
compileSdkauf 37 oder höher, da das Einrasten beim Scrollen auf Geräten mit Android 17 und höher unterstützt wird. - Konfigurieren Sie Ihr
LazyColumnmitVerticalScrollMode. Wenn das Gerät Snap-Scrolling unterstützt, verwendeSnapScrollMatchHeight. Verwenden Sie andernfallsNormal
Wenn Sie Snap-Scrolling mit Bildern verwenden, lesen Sie den Abschnitt zum kanonischen Layout Bild im Anschnitt.
@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) ) } }
SizeMode definieren
Die Größe von AppWidget kann je nach Gerät, Nutzerauswahl oder Launcher variieren. Daher ist es wichtig, flexible Layouts bereitzustellen, wie auf der Seite Flexible Widget-Layouts bereitstellen beschrieben. Glance vereinfacht dies mit der Definition SizeMode und dem Wert LocalSize. In den folgenden Abschnitten werden die drei Modi beschrieben.
SizeMode.Single
SizeMode.Single ist der Standardmodus. Es gibt an, dass nur ein Inhaltstyp bereitgestellt wird. Das bedeutet, dass sich die Inhaltsgröße auch dann nicht ändert, wenn sich die verfügbare Größe von AppWidget ändert.
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 // ... } }
Wenn Sie diesen Modus nutzen, müssen folgende Voraussetzungen erfüllt sein:
- Die Metadatenwerte für die Mindest- und Höchstgröße sind entsprechend der Inhaltsgröße richtig definiert.
- Der Inhalt ist innerhalb des erwarteten Größenbereichs flexibel genug.
Im Allgemeinen sollten Sie diesen Modus in folgenden Fällen verwenden:
a) Die AppWidget hat eine feste Größe oder
b) ihr Inhalt ändert sich nicht, wenn die Größe angepasst wird.
SizeMode.Responsive
Dieser Modus entspricht der Bereitstellung responsiver Layouts, mit denen der GlanceAppWidget eine Reihe responsiver Layouts definieren kann, die durch bestimmte Größen begrenzt sind. Für jede definierte Größe wird der Inhalt erstellt und der jeweiligen Größe zugeordnet, wenn die AppWidget erstellt oder aktualisiert wird. Das System wählt dann die am besten passende Variante basierend auf der verfügbaren Größe aus.
In unserem Ziel-AppWidget können Sie beispielsweise drei Größen und deren Inhalte definieren:
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") } } } }
Im vorherigen Beispiel wird die Methode provideContent dreimal aufgerufen und der definierten Größe zugeordnet.
- Beim ersten Aufruf wird die Größe mit
100x100ausgewertet. Der Inhalt enthält weder die zusätzliche Schaltfläche noch den Text oben und unten. - Beim zweiten Aufruf wird die Größe mit
250x100ausgewertet. Der Inhalt enthält die zusätzliche Schaltfläche, aber nicht den oberen und unteren Text. - Im dritten Aufruf wird die Größe mit
250x250ausgewertet. Der Inhalt umfasst die zusätzliche Schaltfläche und beide Texte.
SizeMode.Responsive ist eine Kombination der anderen beiden Modi und ermöglicht es Ihnen, responsive Inhalte innerhalb vordefinierter Grenzen zu definieren. Im Allgemeinen bietet dieser Modus eine bessere Leistung und ermöglicht flüssigere Übergänge, wenn die Größe des AppWidget geändert wird.
In der folgenden Tabelle ist der Wert der Größe abhängig von SizeMode und der verfügbaren Größe von AppWidget aufgeführt:
| Verfügbare Größe | 105 × 110 | 203 × 112 | 72 x 72 | 203 x 150 |
|---|---|---|---|---|
SizeMode.Single |
110 x 110 | 110 x 110 | 110 x 110 | 110 x 110 |
SizeMode.Exact |
105 × 110 | 203 × 112 | 72 x 72 | 203 x 150 |
SizeMode.Responsive |
80 × 100 | 80 × 100 | 80 × 100 | 150 x 120 |
| * Die genauen Werte dienen nur zu Demozwecken. |
SizeMode.Exact
SizeMode.Exact entspricht der Bereitstellung genauer Layouts, bei der der GlanceAppWidget-Inhalt jedes Mal angefordert wird, wenn sich die verfügbare AppWidget-Größe ändert (z. B. wenn der Nutzer die Größe des AppWidget auf dem Startbildschirm ändert).
Im Ziel-Widget kann beispielsweise eine zusätzliche Schaltfläche hinzugefügt werden, wenn die verfügbare Breite einen bestimmten Wert überschreitet.
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") } } } } }
Dieser Modus bietet mehr Flexibilität als die anderen, hat aber einige Einschränkungen:
- Die
AppWidgetmuss bei jeder Größenänderung vollständig neu erstellt werden. Dies kann bei komplexen Inhalten zu Leistungsproblemen und Sprüngen in der Benutzeroberfläche führen. - Die verfügbare Größe kann je nach Implementierung des Launchers variieren. Wenn der Launcher beispielsweise die Liste der Größen nicht bereitstellt, wird die kleinstmögliche Größe verwendet.
- Auf Geräten mit einer Android-Version vor Android 12 funktioniert die Logik zur Größenberechnung möglicherweise nicht in allen Situationen.
Im Allgemeinen sollten Sie diesen Modus verwenden, wenn SizeMode.Responsive nicht verwendet werden kann, d. h. wenn eine kleine Anzahl responsiver Layouts nicht möglich ist.
Auf Ressourcen zugreifen
Verwende LocalContext.current, um auf eine beliebige Android-Ressource zuzugreifen, wie im folgenden Beispiel gezeigt:
LocalContext.current.getString(R.string.glance_title)
Wir empfehlen, Ressourcen-IDs direkt anzugeben, um die Größe des endgültigen RemoteViews-Objekts zu reduzieren und dynamische Ressourcen wie dynamische Farben zu aktivieren.
Composables und Methoden akzeptieren Ressourcen über einen „Anbieter“ wie ImageProvider oder über eine Überladungsmethode wie GlanceModifier.background(R.color.blue). Beispiel:
Column( modifier = GlanceModifier.background(R.color.default_widget_background) ) { /**...*/ } Image( provider = ImageProvider(R.drawable.ic_logo), contentDescription = "My image", )
Alias-Text
Glance 1.1.0 enthält eine API zum Festlegen von Textstilen. Legen Sie Textstile mit den Attributen fontSize, fontWeight oder fontFamily der Klasse „TextStyle“ fest.
fontFamily unterstützt alle Systemschriftarten, wie im folgenden Beispiel gezeigt. Benutzerdefinierte Schriftarten in Apps werden jedoch nicht unterstützt:
Text( style = TextStyle( fontWeight = FontWeight.Bold, fontSize = 18.sp, fontFamily = FontFamily.Monospace ), text = "Example Text" )
Zusammengesetzte Schaltflächen hinzufügen
Zusammengesetzte Schaltflächen wurden in Android 12 eingeführt. Glance unterstützt die Abwärtskompatibilität für die folgenden Arten von zusammengesetzten Schaltflächen:
Diese zusammengesetzten Schaltflächen zeigen jeweils eine anklickbare Ansicht an, die den Status „Ausgewählt“ darstellt.
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" )
Wenn sich der Status ändert, wird die bereitgestellte Lambda-Funktion ausgelöst. Sie können den Status „Ausgewählt“ speichern, wie im folgenden Beispiel gezeigt:
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) } ) } }
Sie können auch das Attribut colors für CheckBox, Switch und RadioButton angeben, um deren Farben anzupassen:
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) ), )
Zusätzliche Komponenten
Glance 1.1.0 enthält die Veröffentlichung zusätzlicher Komponenten, wie in der folgenden Tabelle beschrieben:
| Name | Bild | Referenzlink | Zusätzliche Hinweise |
|---|---|---|---|
| Schaltfläche mit Füllung |
|
Komponente | |
| Schaltflächen mit Umriss |
|
Komponente | |
| Symbol-Buttons |
|
Komponente | Primär / Sekundär / Nur Symbol |
| Titelleiste |
|
Komponente | |
| Scaffold | Scaffold und Titelleiste befinden sich in derselben Demo. |
Weitere Informationen zu den Designspezifikationen finden Sie in den Komponentendesigns in diesem Design-Kit auf Figma.
Weitere Informationen zu kanonischen Layouts finden Sie unter Kanonische Widget-Layouts.