App-Widget mit Glance erstellen

In den folgenden Abschnitten wird beschrieben, wie Sie mit Glance ein einfaches App-Widget erstellen.

AppWidget im Manifest deklarieren

Nachdem Sie die Einrichtungsschritte abgeschlossen haben, deklarieren Sie die AppWidget und die zugehörigen Metadaten in Ihrer App.

  1. Erweitern Sie den AppWidget-Empfänger von GlanceAppWidgetReceiver:

    class MyAppWidgetReceiver : GlanceAppWidgetReceiver() {
        override val glanceAppWidget: GlanceAppWidget = TODO("Create GlanceAppWidget")
    }

  2. Registrieren Sie den Anbieter des App-Widgets in Ihrer AndroidManifest.xml-Datei und der zugehörigen Metadatendatei:

        <receiver android:name=".glance.MyReceiver"
        android:exported="true">
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        </intent-filter>
        <meta-data
            android:name="android.appwidget.provider"
            android:resource="@xml/my_app_widget_info" />
    </receiver>
    

AppWidgetProviderInfo-Metadaten hinzufügen

Folge als Nächstes der Anleitung unter Widget erstellen, um die App-Widget-Informationen in der Datei @xml/my_app_widget_info zu erstellen und zu definieren.

Der einzige Unterschied bei Glance ist, dass es keine initialLayout-XML gibt, Sie aber eine definieren müssen. Sie können das vordefinierte Ladelayout aus der Bibliothek verwenden:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/glance_default_loading_layout">
</appwidget-provider>

AppWidgetProviderInfo-XML deklarieren

Das AppWidgetProviderInfo-Objekt definiert die wesentlichen Eigenschaften des Widgets. Definieren Sie die AppWidgetProviderInfo in Ihrer XML-Metadaten-Ressourcendatei (res/xml/my_app_widget_info.xml) innerhalb eines <appwidget-provider>-Elements:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="40dp"
    android:minHeight="40dp"
    android:targetCellWidth="1"
    android:targetCellHeight="1"
    android:maxResizeWidth="250dp"
    android:maxResizeHeight="120dp"
    android:updatePeriodMillis="86400000"
    android:description="@string/example_appwidget_description"
    android:previewLayout="@layout/example_appwidget_preview"
    android:initialLayout="@layout/glance_default_loading_layout"
    android:configure="com.example.android.ExampleAppWidgetConfigurationActivity"
    android:resizeMode="horizontal|vertical"
    android:widgetCategory="home_screen"
    android:widgetFeatures="reconfigurable|configuration_optional">
</appwidget-provider>

Attribute für die Widget-Größe

Auf dem Standard-Startbildschirm werden Widgets in einem Raster aus Zellen mit einer definierten Höhe und Breite positioniert. Auf den meisten Startbildschirmen können Widgets nur Größen annehmen, die ganzzahlige Vielfache der Rasterzellen sind, z. B. zwei Zellen horizontal und drei Zellen vertikal.

Mit den Attributen für die Widget-Größe kannst du eine Standardgröße für dein Widget festlegen und die Unter- und Obergrenze für die Größe des Widgets angeben. In diesem Kontext ist die Standardgröße eines Widgets die Größe, die das Widget hat, wenn es zum ersten Mal dem Startbildschirm hinzugefügt wird.

In der folgenden Tabelle werden die <appwidget-provider>-Attribute für die Widget-Größe beschrieben:

Attribute und Beschreibung
targetCellWidth und targetCellHeight (Android 12), minWidth und minHeight
  • Ab Android 12 geben die Attribute targetCellWidth und targetCellHeight die Standardgröße des Widgets in Rasterzellen an. Diese Attribute werden in Android 11 und niedriger ignoriert und können ignoriert werden, wenn der Startbildschirm kein rasterbasiertes Layout unterstützt.
  • Die Attribute minWidth und minHeight geben die Standardgröße des Widgets in dp an. Wenn die Werte für die Mindestbreite oder -höhe eines Widgets nicht mit den Abmessungen der Zellen übereinstimmen, werden die Werte auf die nächste Zellengröße aufgerundet.
Wir empfehlen, beide Attributgruppen anzugeben: targetCellWidth und targetCellHeight sowie minWidth und minHeight. So kann Ihre App auf minWidth und minHeight zurückgreifen, wenn das Gerät des Nutzers targetCellWidth und targetCellHeight nicht unterstützt. Falls unterstützt, haben die Attribute targetCellWidth und targetCellHeight Vorrang vor den Attributen minWidth und minHeight.
minResizeWidth und minResizeHeight Geben Sie die absolute Mindestgröße des Widgets an. Diese Werte geben die Größe an, unter der das Widget unleserlich oder anderweitig unbrauchbar ist. Mit diesen Attributen kann der Nutzer die Größe des Widgets auf eine Größe ändern, die kleiner als die Standardgröße des Widgets ist. Das Attribut minResizeWidth wird ignoriert, wenn es größer als minWidth ist oder wenn die horizontale Größenanpassung nicht aktiviert ist. Siehe resizeMode. Ebenso wird das Attribut minResizeHeight ignoriert, wenn es größer als minHeight ist oder wenn die vertikale Größenanpassung nicht aktiviert ist.
maxResizeWidth und maxResizeHeight Geben Sie die empfohlene maximale Größe des Widgets an. Wenn die Werte kein Vielfaches der Rasterzellenabmessungen sind, werden sie auf die nächste Zellengröße aufgerundet. Das Attribut maxResizeWidth wird ignoriert, wenn es kleiner als minWidth ist oder wenn die horizontale Größenanpassung nicht aktiviert ist. Siehe resizeMode. Ebenso wird das Attribut maxResizeHeight ignoriert, wenn es kleiner als minHeight ist oder wenn die vertikale Größenänderung nicht aktiviert ist. Eingeführt in Android 12.
resizeMode Gibt die Regeln an, nach denen die Größe eines Widgets angepasst werden kann. Mit diesem Attribut kannst du festlegen, dass die Größe von Startbildschirm-Widgets horizontal, vertikal oder auf beiden Achsen geändert werden kann. Nutzer halten ein Widget gedrückt, um die Ziehpunkte zur Größenanpassung einzublenden, und ziehen dann die horizontalen oder vertikalen Ziehpunkte, um die Größe im Layout-Raster zu ändern. Werte für das Attribut resizeMode sind horizontal, vertical und none. Wenn Sie ein Widget als horizontal und vertikal skalierbar deklarieren möchten, verwenden Sie horizontal|vertical.

Beispiel

Um zu veranschaulichen, wie sich die Attribute in der vorherigen Tabelle auf die Widget-Größe auswirken, nehmen wir die folgenden Spezifikationen an:

  • Eine Rasterzelle ist 30 dp breit und 50 dp hoch.
  • Die folgende Attributspezifikation wird bereitgestellt:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="80dp"
    android:minHeight="80dp"
    android:targetCellWidth="2"
    android:targetCellHeight="2"
    android:minResizeWidth="40dp"
    android:minResizeHeight="40dp"
    android:maxResizeWidth="120dp"
    android:maxResizeHeight="120dp"
    android:resizeMode="horizontal|vertical" />

Ab Android 12:

Verwenden Sie die Attribute targetCellWidth und targetCellHeight als Standardgröße des Widgets.

Die Standardgröße des Widgets ist 2 × 2. Die Größe des Widgets kann auf 2 × 1 verkleinert oder auf 4 × 3 vergrößert werden.

Android 11 und niedriger:

Verwenden Sie die Attribute minWidth und minHeight, um die Standardgröße des Widgets zu berechnen.

Die Standardbreite = Math.ceil(80 / 30) = 3

Die Standardhöhe ist Math.ceil(80 / 50) = 2.

Die Standardgröße des Widgets ist 3 × 2. Die Größe des Widgets kann auf 2 × 1 verkleinert oder auf Vollbild vergrößert werden.

Zusätzliche Widget-Attribute

In der folgenden Tabelle werden die <appwidget-provider>-Attribute beschrieben, die sich auf andere Eigenschaften als die Widget-Größe beziehen.

Attribute und Beschreibung
updatePeriodMillis Definiert, wie oft das Widget-Framework ein Update vom GlanceAppWidgetReceiver anfordert, indem die Callback-Methode onUpdate() aufgerufen wird. Wir empfehlen, die Daten so selten wie möglich zu aktualisieren – nicht öfter als einmal pro Stunde –, um den Akku zu schonen. Weitere Informationen finden Sie im Abschnitt Wann Widgets aktualisiert werden sollten unter „Glance-Zustandsverwaltung“.
initialLayout Verweist auf die Layoutressource, die das Ladelayout des Widgets definiert, bevor die Glance-UI-Kompositionen gerendert werden. Sie können das vordefinierte Ladelayout aus der Bibliothek verwenden: @layout/glance_default_loading_layout.
configure Definiert die Konfigurationsaktivität, die gestartet wird, wenn der Nutzer das Widget hinzufügt. Weitere Informationen finden Sie im Leitfaden Nutzern die Konfiguration von App-Widgets ermöglichen.
description Gibt die Beschreibung an, die in der Widget-Auswahl für Ihr Widget angezeigt werden soll. Eingeführt in Android 12.
previewLayout (Android 12) und previewImage (Android 11 und niedriger)
  • Ab Android 12 gibt das Attribut previewLayout eine skalierbare Vorschau an, die du als XML-Layout in der Standardgröße des Widgets bereitstellst. Im Idealfall verweist dies auf eine statische XML-Zuordnung, die Ihrem Designlayout entspricht.
  • In Android 11 oder niedriger gibt das Attribut previewImage einen Screenshot des Widgets als statisches Bild an, der in der Widget-Auswahl angezeigt wird.
Wir empfehlen, beide anzugeben, damit deine App auf älteren Plattformen ordnungsgemäß funktioniert. Auf neueren Plattformen (Android 15 und höher) können Sie mit `GlanceAppWidget.providePreview` in Kotlin live generierte Vorschauen definieren. Weitere Informationen finden Sie im Leitfaden zu generierten Vorschauen.
autoAdvanceViewId Gibt die Ansichts-ID der Widget-Unteransicht an, die vom Host des Widgets automatisch weitergeschaltet wird.
widgetCategory Gibt an, ob das Widget auf dem Startbildschirm (home_screen), dem Sperrbildschirm (keyguard) oder auf beiden angezeigt werden kann. Für Android 5.0 und höher ist nur home_screen gültig.
widgetFeatures Deklariert die vom Widget unterstützten Funktionen. Wenn die Konfiguration Ihres Widgets beispielsweise optional ist, geben Sie sowohl configuration_optional als auch reconfigurable an.

„GlanceAppWidget“ definieren

  1. Erstellen Sie eine neue Klasse, die von GlanceAppWidget erweitert wird und die Methode provideGlance überschreibt. Mit dieser Methode können Sie Daten laden, die zum Rendern des Widgets erforderlich sind:

    class MyAppWidget : GlanceAppWidget() {
    
        override suspend fun provideGlance(context: Context, id: GlanceId) {
    
            // In this method, load data needed to render the AppWidget.
            // Use `withContext` to switch to another thread for long running
            // operations.
    
            provideContent {
                // create your AppWidget here
                Text("Hello World")
            }
        }
    }

  2. Instanziieren Sie sie in der glanceAppWidget auf Ihrem GlanceAppWidgetReceiver:

    class MyAppWidgetReceiver : GlanceAppWidgetReceiver() {
    
        // Let MyAppWidgetReceiver know which GlanceAppWidget to use
        override val glanceAppWidget: GlanceAppWidget = MyAppWidget()
    }

Sie haben jetzt ein AppWidget mit Glance konfiguriert.

Widget-Bildspeicher über Größen-Buckets hinweg verwalten

Wenn in Ihrem Widget Bitmaps oder content://-Bild-URIs angezeigt werden, sollten Sie den gesamten serialisierten RemoteViews-Bildspeicher innerhalb des Systemlimits (6 * width * height Byte, erzwungen mit einer IllegalArgumentException unter Android 17 und höher) halten. Gehen Sie dazu so vor:

  • Gemeinsame Bitmaps vor provideContent decodieren:Wenn Ihr Widget SizeMode.Responsive oder SizeMode.Exact verwendet, führt Glance den provideContent-Lambda-Ausdruck einmal für jeden DpSize-Bucket aus. Beim Decodieren eines Bitmap in provideContent wird für jeden Bucket eine separate Bitmap-Instanz zugewiesen, wodurch sich die Speichernutzung in RemoteViews.mBitmapCache vervielfacht. Decodieren Sie herunterskalierte Bitmaps (Bitmap.Config.RGB_565 für opake Bilder) einmal in provideGlance vor provideContent, damit alle Buckets eine einzige Bitmap-Referenz gemeinsam nutzen.
  • Verwenden Sie den passenden ImageProvider-Import: Jetpack Glance bietet zwei verschiedene ImageProvider-Überladungen in zwei Paketen:

    • Verwenden Sie import androidx.glance.ImageProvider, wenn Sie eine speicherinterne ImageProvider(bitmap: Bitmap) oder eine zeichenfähige Ressource ImageProvider(resId: Int) übergeben.
    • Verwenden Sie import androidx.glance.appwidget.ImageProvider, wenn Sie einen URI ImageProvider(uri: Uri) übergeben. Wenn im Cache gespeicherte Dateien über einen FileProvider-URI bereitgestellt werden, gewähren Sie jedem installierten Intent.CATEGORY_HOME-Launcher-Paket Intent.FLAG_GRANT_READ_URI_PERMISSION, damit der Host das Bild rendern kann.

Widget-Broadcasts mit der Klasse „GlanceAppWidgetReceiver“ verarbeiten

Das GlanceAppWidgetReceiver-Koordinaten-Widget überträgt und aktualisiert den Plattformstatus, indem es die zugrunde liegende AppWidgetProvider erweitert. Sie empfängt Plattformereignisse, wenn Ihr Widget aktualisiert, gelöscht, aktiviert oder deaktiviert wird, und wandelt sie in Compose-Lebenszyklusanfragen um.

Widget im Manifest deklarieren

Deklarieren Sie die Unterklasse Ihrer GlanceAppWidgetReceiver-Klasse als Übertragungsempfänger in Ihrer AndroidManifest.xml-Datei:

<receiver android:name="MyReceiver"
          android:exported="false">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <meta-data android:name="android.appwidget.provider"
               android:resource="@xml/my_app_widget_info" />
</receiver>

Für das <receiver>-Element ist das android:name-Attribut erforderlich, das die Empfängerklasse angibt. Der Empfänger muss die Broadcast-Aktion ACTION_APPWIDGET_UPDATE im <intent-filter> akzeptieren.

Das <meta-data>-Element muss seinen Namen als android.appwidget.provider identifizieren und das android:resource-Attribut muss auf Ihre AppWidgetProviderInfo-XML-Metadatenressource (@xml/my_app_widget_info) verweisen.

Klasse „GlanceAppWidgetReceiver“ implementieren

In Glance erweitern Sie GlanceAppWidgetReceiver anstelle von AppWidgetProvider direkt. Implementiere sie, indem du deinen Empfänger mit deiner GlanceAppWidget-Instanz verknüpfst. Die primären Callbacks in GlanceAppWidgetReceiver funktionieren so:

  • onUpdate(): Wird von Glance automatisch überschrieben, um Aktualisierungen der Komposition auszuführen. Wenn Sie onUpdate manuell überschreiben, müssen Sie super.onUpdate aufrufen, damit Glance Threads für die Zusammensetzung starten kann.
  • onAppWidgetOptionsChanged(): Wird aufgerufen, wenn das Widget zum ersten Mal platziert oder seine Größe geändert wird. Glance liest die Optionen für die Bundle-Elemente im Hintergrund, sodass sich das Layout nahtlos an die Laufzeitdimensionen anpasst.
  • onDeleted(Context, IntArray): Wird aufgerufen, wenn eine bestimmte Widget-Instanz vom Nutzer gelöscht wird.
  • onEnabled(Context): Wird ausgelöst, wenn die erste Instanz Ihres Widgets erfolgreich erstellt wurde. Hervorragend für globale Migrationen geeignet.
  • onDisabled(Context): Wird aufgerufen, wenn die letzte aktive Instanz des Anbieters entfernt wird.
  • onReceive(Context, Intent): Fängt jede Übertragung der Plattform vor bestimmten Callback-Methoden ab. Sie müssen dafür sorgen, dass jede von Ihnen geschriebene benutzerdefinierte Empfängerlogik super.onReceive(context, intent) aufruft. Sie dürfen goAsync niemals selbst aufrufen, da Glance die Arbeit automatisch asynchron weiterleitet.

Widget-Broadcast-Intents empfangen

Im Hintergrund filtert und verarbeitet GlanceAppWidgetReceiver die folgenden grundlegenden Broadcast-Intents für Plattform-Widgets:

UI erstellen

Das folgende Snippet zeigt, wie die Benutzeroberfläche erstellt wird:

/* Import Glance Composables
 In the event there is a name clash with the Compose classes of the same name,
 you may rename the imports per https://kotlinlang.org/docs/packages.html#imports
 using the `as` keyword.

import androidx.glance.Button
import androidx.glance.layout.Column
import androidx.glance.layout.Row
import androidx.glance.text.Text
*/
class MyAppWidget : GlanceAppWidget() {

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        // Load data needed to render the AppWidget.
        // Use `withContext` to switch to another thread for long running
        // operations.

        provideContent {
            // create your AppWidget here
            MyContent()
        }
    }

    @Composable
    private fun MyContent() {
        Column(
            modifier = GlanceModifier.fillMaxSize(),
            verticalAlignment = Alignment.Top,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text(text = "Where to?", modifier = GlanceModifier.padding(12.dp))
            Row(horizontalAlignment = Alignment.CenterHorizontally) {
                Button(
                    text = "Home",
                    onClick = actionStartActivity<MyActivity>()
                )
                Button(
                    text = "Work",
                    onClick = actionStartActivity<MyActivity>()
                )
            }
        }
    }
}

Das vorherige Codebeispiel tut Folgendes:

  • In der obersten Ebene Column werden die Elemente vertikal nacheinander platziert.
  • Die Größe des Column wird an den verfügbaren Platz angepasst (über GlanceModifier). Der Inhalt wird oben (verticalAlignment) und horizontal zentriert (horizontalAlignment) ausgerichtet.
  • Der Inhalt von Column wird mit dem Lambda definiert. Die Reihenfolge ist wichtig.
    • Das erste Element im Column ist eine Text-Komponente mit einem Padding von 12.dp.
    • Das zweite Element ist ein Row, bei dem Elemente horizontal nacheinander platziert werden, wobei zwei Buttons horizontal zentriert sind (horizontalAlignment). Die endgültige Anzeige hängt vom verfügbaren Platz ab. Das folgende Bild ist ein Beispiel dafür, wie das aussehen könnte:
destination_widget
Abbildung 1. Beispiel für eine Benutzeroberfläche.

Sie können die Ausrichtungswerte ändern oder andere Modifikatorwerte (z. B. Padding) anwenden, um die Platzierung und Größe der Komponenten zu ändern. Eine vollständige Liste der Komponenten, Parameter und verfügbaren Modifikatoren für jede Klasse finden Sie in der Referenzdokumentation.

Abgerundete Ecken implementieren

In Android 12 werden Systemparameter eingeführt, mit denen sich die Eckenradien von App-Widgets dynamisch anpassen lassen:

  • system_app_widget_background_radius: Gibt den Eckenradius des Widget-Hintergrundcontainers an (nie größer als 28 dp).
  • Innerer Radius:Damit Inhalte nicht abgeschnitten werden, berechne einen proportionalen Radius für deine inneren Inhalte basierend auf dem Hintergrundumriss des Systems: systemRadiusValue - widgetPadding

In Glance können Sie mit GlanceModifier.cornerRadius(android.R.dimen.system_app_widget_background_radius) dynamisch Größenattribute für den Eckenradius in der Komposition anwenden.

Implementieren Sie für die Abwärtskompatibilität auf Geräten mit Android 11 (API-Level 30) oder niedriger benutzerdefinierte Attribute und Fallbacks für benutzerdefinierte Designressourcen:

  • /values/attrs.xml

    <resources>
    <attr name="backgroundRadius" format="dimension" />
    </resources>
    
  • /values/styles.xml

    <resources>
    <style name="MyWidgetTheme">
      <item name="backgroundRadius">@dimen/my_background_radius_dimen</item>
    </style>
    </resources>
    
  • /values-31/styles.xml

    <resources>
    <style name="MyWidgetTheme" parent="@android:style/Theme.DeviceDefault.DayNight">
      <item name="backgroundRadius">@android:dimen/system_app_widget_background_radius</item>
    </style>
    </resources>
    
  • /drawable/my_widget_background.xml

    <shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <corners android:radius="?attr/backgroundRadius" />
    </shape>