UI mit Glance erstellen

Auf dieser Seite wird beschrieben, wie Sie mit Glance Größen verwalten und flexible und responsive Layouts erstellen, indem Sie vorhandene Glance-Komponenten verwenden.

Verwende Box, Column und Row.

Glance bietet drei Hauptlayouts, die kombiniert werden können:

  • Box: Hiermit werden Elemente übereinander gelegt. Sie wird in einen RelativeLayout umgewandelt.

  • Column: Hiermit werden Elemente in der vertikalen Achse nacheinander angeordnet. Das entspricht einem LinearLayout mit vertikaler Ausrichtung.

  • Row: Hiermit werden Elemente in der horizontalen Achse hintereinander angeordnet. Das entspricht einem LinearLayout mit horizontaler Ausrichtung.

Glance unterstützt Scaffold-Objekte. Platzieren Sie Ihre Column-, Row- und Box-Kompositionen in einem bestimmten Scaffold-Objekt.

Bild eines Spalten-, Zeilen- und Boxlayouts.
Abbildung 1: Beispiele für Layouts mit Spalten, Zeilen und Boxen.

Bei jedem dieser Elemente können Sie die vertikale und horizontale Ausrichtung des Inhalts sowie die Einschränkungen für Breite, Höhe, Gewicht oder Abstand mithilfe von Modifikatoren festlegen. 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, bei dem die untergeordneten Elemente wie in Abbildung 1 horizontal gleichmäßig verteilt sind:

Row(modifier = GlanceModifier.fillMaxWidth().padding(16.dp)) {
    val modifier = GlanceModifier.defaultWeight()
    Text("first", modifier)
    Text("second", modifier)
    Text("third", modifier)
}

Das Row füllt die maximal verfügbare Breite aus. Da jedes Kind dasselbe Gewicht hat, teilen sie sich den verfügbaren Platz gleichmäßig auf. Sie können unterschiedliche Gewichtungen, Größen, Abstände oder Ausrichtungen festlegen, um Layouts an Ihre Anforderungen anzupassen.

Scrollbare Layouts verwenden

Eine weitere Möglichkeit, responsive Inhalte bereitzustellen, besteht darin, sie scrollbar zu machen. Das ist mit der LazyColumn-Komposition möglich. Mit diesem Composeable 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 in LazyColumn zu definieren.

Sie können die Anzahl der Artikel so angeben:

// Remember to import Glance Composables
// import androidx.glance.appwidget.layout.LazyColumn

LazyColumn {
    items(10) { index: Int ->
        Text(
            text = "Item $index",
            modifier = GlanceModifier.fillMaxWidth()
        )
    }
}

Geben Sie einzelne Elemente an:

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 ist das itemId nicht angegeben. Wenn Sie itemId angeben, lässt sich die Leistung verbessern und die Scrollposition bei Listen- und appWidget-Aktualisierungen ab Android 12 beibehalten (z. B. beim Hinzufügen oder Entfernen von Elementen aus der Liste). Das folgende Beispiel zeigt, wie eine itemId angegeben wird:

items(items = peopleList, key = { person -> person.id }) { person ->
    Text(person.name)
}

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. In Glance wird dies durch die SizeMode-Definition und den LocalSize-Wert vereinfacht. 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 nicht ändert, auch wenn sich die verfügbare AppWidget-Größe ä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
        // ...
    }
}

Beachten Sie bei der Verwendung dieses Modus Folgendes:

  • Die Metadatenwerte für die Mindest- und Maximalgröße sind korrekt anhand der Inhaltsgröße definiert.
  • Die Inhalte sind innerhalb des erwarteten Größenbereichs flexibel genug.

Im Allgemeinen sollten Sie diesen Modus verwenden, wenn:

a) Die AppWidget hat eine feste Größe oder b) der Inhalt ändert sich nicht, wenn die Größe geändert wird.

SizeMode.Responsive

Dieser Modus entspricht dem Anbieten responsiver Layouts. Damit kann die GlanceAppWidget eine Reihe responsiver Layouts mit bestimmten Größen definieren. Für jede definierte Größe werden die Inhalte erstellt und der jeweiligen Größe zugeordnet, wenn die AppWidget erstellt oder aktualisiert wird. Das System wählt dann anhand der verfügbaren Größe die am besten passende Option aus.

Für das Ziel AppWidget können Sie beispielsweise drei Größen und den Inhalt 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 zu 100x100 ausgewertet. Der Inhalt enthält weder die zusätzliche Schaltfläche noch den Text oben und unten.
  • Beim zweiten Aufruf wird der Wert 250x100 zurückgegeben. Der Inhalt enthält die zusätzliche Schaltfläche, aber nicht den Text oben und unten.
  • Beim dritten Aufruf ergibt die Größe 250x250. Der Inhalt umfasst die zusätzliche Schaltfläche und beide Texte.

SizeMode.Responsive ist eine Kombination der beiden anderen Modi und ermöglicht es Ihnen, responsive Inhalte innerhalb vordefinierter Grenzen zu definieren. Im Allgemeinen ist dieser Modus leistungsfähiger und ermöglicht flüssigere Übergänge, wenn die Größe der AppWidget geändert wird.

In der folgenden Tabelle sehen Sie den Wert der Größe, abhängig von der verfügbaren Größe von SizeMode und AppWidget:

Verfügbare Größe 105 × 110 203 × 112 72 × 72 203 × 150
SizeMode.Single 110 × 110 110 × 110 110 × 110 110 × 110
SizeMode.Exact 105 × 110 203 × 112 72 × 72 203 × 150
SizeMode.Responsive 80 × 100 80 × 100 80 × 100 150 × 120
* Die genauen Werte dienen nur zu Demonstrationszwecken.

SizeMode.Exact

SizeMode.Exact entspricht dem Angeben genauer Layouts, bei dem die GlanceAppWidget-Inhalte jedes Mal angefordert werden, wenn sich die verfügbare AppWidget-Größe ändert (z. B. wenn der Nutzer die Größe der AppWidget auf dem Startbildschirm ändert).

So kann beispielsweise im Ziel-Widget eine zusätzliche Schaltfläche hinzugefügt werden, wenn die verfügbare Breite größer als ein bestimmter Wert ist.

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 Nachteile:

  • Die AppWidget muss bei jeder Größenänderung vollständig neu erstellt werden. Das kann bei komplexen Inhalten zu Leistungsproblemen und UI-Sprüngen führen.
  • Die verfügbare Größe kann je nach Implementierung des Launchers variieren. Wenn der Launcher beispielsweise keine Liste der Größen anbietet, wird die kleinstmögliche Größe verwendet.
  • Auf Geräten mit einer älteren Android-Version funktioniert die Logik zur Größeberechnung möglicherweise nicht in allen Fällen.

Im Allgemeinen sollten Sie diesen Modus verwenden, wenn SizeMode.Responsive nicht verwendet werden kann, d. h. wenn nur eine kleine Anzahl responsiver Layouts möglich ist.

Auf Ressourcen zugreifen

Verwenden Sie 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.

In Composables und Methoden werden Ressourcen über einen „Anbieter“ wie ImageProvider oder über eine Überladung wie GlanceModifier.background(R.color.blue) akzeptiert. Beispiel:

Column(
    modifier = GlanceModifier.background(R.color.default_widget_background)
) { /**...*/ }

Image(
    provider = ImageProvider(R.drawable.ic_logo),
    contentDescription = "My image",
)

Text bearbeiten

Glance 1.1.0 enthält eine API zum Festlegen von Textstilen. Legen Sie Textstile mit den Attributen fontSize, fontWeight oder fontFamily der TextStyle-Klasse fest.

fontFamily unterstützt alle Systemschriften, 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 enthalten jeweils eine anklickbare Ansicht, die den Status „angeklickt“ darstellt.

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"
)

Wenn sich der Status ändert, wird das angegebene Lambda ausgelöst. Sie können den Prüfstatus 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 das colors-Attribut auch für CheckBox, Switch und RadioButton angeben, um die 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 Anmerkungen
Gefüllte Schaltfläche alt_text Komponente
Schaltflächen mit Umriss alt_text Komponente
Symbolschaltflächen alt_text Komponente Primär / Sekundär / Nur Symbol
Titelleiste alt_text Komponente
Gerüst Scaffold und Titelleiste befinden sich in derselben Demo.

Weitere Informationen zu Designdetails finden Sie in den Komponentendesigns in diesem Designkit in Figma.

Weitere Informationen zu kanonischen Layouts finden Sie unter Kanonische Widget-Layouts.