UI mit Glance erstellen

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 einen RelativeLayout übersetzt.

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

  • Row: Platziert Elemente nacheinander auf der horizontalen Achse. Sie wird in ein LinearLayout mit horizontaler Ausrichtung übersetzt.

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

Ein Layout mit Spalten, Zeilen und Feldern.
Abbildung 1. Beispiele für Layouts mit „Column“, „Row“ und „Box“.

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.

Video 1: Links ist ein Listenelement zu sehen, das beim Scrollen nicht einrastet, rechts rastet es ein.


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 compileSdk auf 37 oder höher, da das Einrasten beim Scrollen auf Geräten mit Android 17 und höher unterstützt wird.
  • Konfigurieren Sie Ihr LazyColumn mit VerticalScrollMode. Wenn das Gerät Snap-Scrolling unterstützt, verwende SnapScrollMatchHeight. Verwenden Sie andernfalls Normal

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 100x100 ausgewertet. Der Inhalt enthält weder die zusätzliche Schaltfläche noch den Text oben und unten.
  • Beim zweiten Aufruf wird die Größe mit 250x100 ausgewertet. Der Inhalt enthält die zusätzliche Schaltfläche, aber nicht den oberen und unteren Text.
  • Im dritten Aufruf wird die Größe mit 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. 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 AppWidget muss 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 alt_text Komponente
Schaltflächen mit Umriss alt_text Komponente
Symbol-Buttons alt_text Komponente Primär / Sekundär / Nur Symbol
Titelleiste alt_text 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.