UI mit Glance erstellen

Auf dieser Seite wird beschrieben, wie mit „ Glance“ mithilfe vorhandener Glance-Komponenten Größen gehandhabt und flexible und responsive Layouts bereitgestellt werden.

Box, Column und Row verwenden

Glance hat drei zusammensetzbare Hauptlayouts:

  • Box: Platziert Elemente übereinander. Er wird in RelativeLayout übersetzt.

  • Column: Platziert Elemente hintereinander in der vertikalen Achse. Sie wird in ein LinearLayout mit vertikaler Ausrichtung übersetzt.

  • Row: Platziert Elemente hintereinander in der horizontalen Achse. Sie wird in ein LinearLayout-Element mit horizontaler Ausrichtung übersetzt.

Glance unterstützt Scaffold-Objekte. Platzieren Sie die zusammensetzbaren Funktionen Column, Row und Box in einem bestimmten Scaffold-Objekt.

Abbildung eines Layouts für Spalte, Zeile und Feld.
Abbildung 1. Beispiele für Layouts mit „Column“, „Row“ und „Box“.

Mit jeder dieser zusammensetzbaren Funktionen können Sie mithilfe von Modifikatoren die vertikale und horizontale Ausrichtung des Inhalts sowie die Einschränkungen für Breite, Höhe, Gewicht und Abstände definieren. Darüber hinaus kann jedes untergeordnete Element einen 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 dargestellt:

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

Row füllt die maximal verfügbare Breite aus. Da jedes untergeordnete Element dieselbe Gewichtung hat, teilen sie den verfügbaren Platz gleichmäßig. 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 dieser zusammensetzbaren Funktion können Sie eine Gruppe 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()
        )
    }
}

Stellen Sie einzelne Elemente bereit:

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 wurde itemId nicht angegeben. Wenn du itemId angibst, wird die Leistung verbessert und die Scrollposition durch die Listen- und appWidget-Updates ab Android 12 beibehalten (z. B. beim Hinzufügen oder Entfernen von Elementen aus der Liste). Das folgende Beispiel zeigt, wie ein itemId angegeben wird:

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

SizeMode definieren

AppWidget-Größen können sich je nach Gerät, Nutzerauswahl oder Launcher unterscheiden. Daher ist es wichtig, flexible Layouts bereitzustellen, wie auf der Seite Flexible Widget-Layouts bereitstellen beschrieben. Mit der Definition SizeMode und dem Wert LocalSize wird dies vereinfacht. In den folgenden Abschnitten werden die drei Modi beschrieben.

SizeMode.Single

SizeMode.Single ist der Standardmodus. Er gibt an, dass nur ein Inhaltstyp bereitgestellt wird. Das heißt, selbst wenn sich die verfügbare Größe von AppWidget ändert, bleibt die Inhaltsgröße unverä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
        // ...
    }
}

Achten Sie bei Verwendung dieses Modus auf Folgendes:

  • Die Metadatenwerte für die Mindest- und Maximalgröße sind auf der Grundlage der Inhaltsgröße korrekt definiert.
  • Der Inhalt ist innerhalb des erwarteten Größenbereichs ausreichend flexibel.

Im Allgemeinen sollten Sie diesen Modus in folgenden Fällen verwenden:

a) das AppWidget eine feste Größe hat oder b) der Inhalt bei einer Größenänderung nicht geändert wird.

SizeMode.Responsive

Dieser Modus entspricht der Bereitstellung responsiver Layouts, mit der mit dem GlanceAppWidget ein Satz responsiver Layouts definiert werden 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 anhand der verfügbaren Größe die am besten passende aus.

In unserem Ziel AppWidget können Sie beispielsweise drei Größen und deren 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 wurde die Methode provideContent dreimal aufgerufen und der definierten Größe zugeordnet.

  • Im ersten Aufruf wird die Größe als 100x100 ausgewertet. Weder die zusätzliche Schaltfläche noch den oberen und unteren Text sind enthalten.
  • Im zweiten Aufruf wird die Größe als 250x100 ausgewertet. Der Inhalt umfasst die zusätzliche Schaltfläche, aber nicht den oberen und unteren Text.
  • Im dritten Aufruf wird die Größe als 250x250 ausgewertet. 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. Dieser Modus funktioniert im Allgemeinen besser und ermöglicht flüssigere Übergänge, wenn die Größe von AppWidget geändert wird.

Die folgende Tabelle zeigt den Wert der Größe in Abhängigkeit von der verfügbaren Größe SizeMode und AppWidget:

Verfügbare Größe 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
* Die genauen Werte dienen nur zu Demonstrationszwecken.

SizeMode.Exact

SizeMode.Exact entspricht der Bereitstellung exakter Layouts, bei der der GlanceAppWidget-Inhalt jedes Mal angefordert wird, wenn sich die verfügbaren AppWidget-Größen ändern, z. B. wenn der Nutzer die Größe von AppWidget auf dem Startbildschirm ändert.

Im Ziel-Widget kann beispielsweise 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, bringt jedoch einige Einschränkungen mit sich:

  • Die AppWidget muss bei jeder Größenänderung vollständig neu erstellt werden. Dies kann bei komplexen Inhalten zu Leistungsproblemen und Sprüngen der Benutzeroberfläche führen.
  • Die verfügbare Größe kann je nach Implementierung des Launchers variieren. Wenn der Launcher beispielsweise keine Liste mit Größen bereitstellt, wird die Mindestgröße verwendet.
  • Auf Geräten mit einer älteren Version von Android 12 funktioniert die Logik zur Größenberechnung möglicherweise nicht in allen Situationen.

Im Allgemeinen solltest du diesen Modus verwenden, wenn SizeMode.Responsive nicht verwendet werden kann, d. h., wenn nur wenige responsive Layouts nicht möglich sind.

Auf Ressourcen zugreifen

Greifen Sie mit LocalContext.current auf eine beliebige Android-Ressource zu, 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 ermöglichen.

Composables und Methoden akzeptieren Ressourcen, die einen „Anbieter“ wie ImageProvider oder eine Überlastungsmethode wie GlanceModifier.background(R.color.blue) verwenden. Beispiel:

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

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

Text verarbeiten

Glance 1.1.0 enthält eine API zum Festlegen Ihrer Textstile. Legen Sie Textstile mit den Attributen fontSize, fontWeight oder fontFamily der TextStyle-Klasse 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"
)

Kombinierte Schaltflächen hinzufügen

Zusammengesetzte Schaltflächen wurden mit Android 12 eingeführt. Die Anzeige bietet Abwärtskompatibilität für die folgenden Arten von zusammengesetzten Schaltflächen:

Diese zusammengesetzten Schaltflächen zeigen jeweils eine anklickbare Ansicht für den Status „Aktiviert“ an.

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 wie im folgenden Beispiel speichern:

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

Du kannst auch das Attribut colors 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, die in der folgenden Tabelle beschrieben werden:

Name Bild Referenzlink Zusätzliche Anmerkungen
Gefüllter Knopf Alt-Text Komponente
Umrissschaltflächen Alt-Text Komponente
Symbolschaltflächen Alt-Text Komponente Primär / Sekundär / Nur Symbol
Titelleiste Alt-Text Komponente
Gerüst Gerüst und Titelleiste befinden sich in derselben Demo.

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