Tworzenie widżetu aplikacji za pomocą aplikacji Glance

W sekcjach poniżej opisujemy, jak utworzyć podstawowy widżet aplikacji za pomocą Glance.

Deklarowanie AppWidget w pliku manifestu

Po wykonaniu czynności konfiguracyjnych zadeklaruj AppWidget i jego metadane w aplikacji.

  1. Rozszerz odbiornik AppWidget z GlanceAppWidgetReceiver:

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

  2. Zarejestruj dostawcę widżetu aplikacji w pliku AndroidManifest.xml i powiązanym pliku metadanych:

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

Dodawanie metadanych AppWidgetProviderInfo

Następnie postępuj zgodnie z instrukcjami w przewodniku Tworzenie widżetu, aby utworzyć i zdefiniować informacje o widżecie aplikacji w pliku @xml/my_app_widget_info.

Jedyna różnica w przypadku Glance polega na tym, że nie ma pliku XML initialLayout, ale musisz go zdefiniować. Możesz użyć wstępnie zdefiniowanego układu wczytywania dostępnego w bibliotece:

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

Deklarowanie pliku XML AppWidgetProviderInfo

Obiekt AppWidgetProviderInfo określa podstawowe cechy widżetu. Zdefiniuj AppWidgetProviderInfo w pliku zasobu metadanych XML (res/xml/my_app_widget_info.xml) w elemencie <appwidget-provider>:

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

Atrybuty rozmiaru widżetu

Domyślny ekran główny umieszcza widżety w swoim oknie na podstawie siatki komórek o określonej wysokości i szerokości. Większość ekranów głównych pozwala widżetom przyjmować tylko rozmiary będące wielokrotnością komórek siatki – na przykład 2 komórki w poziomie i 3 komórki w pionie.

Atrybuty rozmiaru widżetu pozwalają określić domyślny rozmiar widżetu oraz dolne i górne granice jego rozmiaru. W tym kontekście domyślny rozmiar widżetu to rozmiar, który widżet przyjmuje po pierwszym dodaniu do ekranu głównego.

W tabeli poniżej opisujemy atrybuty <appwidget-provider> dotyczące rozmiaru widżetu:

Atrybuty i opis
targetCellWidth i targetCellHeight (Android 12), minWidth i minHeight
  • Od Androida 12 atrybuty targetCellWidth i targetCellHeight określają domyślny rozmiar widżetu w komórkach siatki. Te atrybuty ignorowane w Androidzie 11 i starszych wersjach oraz mogą być ignorowane, jeśli ekran główny nie obsługuje układu opartego na siatce.
  • Atrybuty minWidth i minHeight określają domyślny rozmiar widżetu w dp. Jeśli wartości minimalnej szerokości lub wysokości widżetu nie pasują do wymiarów komórek, są zaokrąglane do najbliższego rozmiaru komórki.
Zalecamy określenie obu zestawów atrybutów – targetCellWidth i targetCellHeight oraz minWidth i minHeight – aby aplikacja mogła wrócić do używania minWidth i minHeight, jeśli urządzenie użytkownika nie obsługuje targetCellWidth i targetCellHeight. Jeśli są obsługiwane, atrybuty targetCellWidth i targetCellHeight mają pierwszeństwo przed atrybutami minWidth i minHeight.
minResizeWidth i minResizeHeight Określ minimalny rozmiar widżetu. Te wartości określają rozmiar, poniżej którego widżet jest nieczytelny lub w inny sposób nieużyteczny. Użycie tych atrybutów pozwala użytkownikowi zmienić rozmiar widżetu na mniejszy niż domyślny. Atrybut minResizeWidth jest ignorowany, jeśli jest większy niż minWidth lub jeśli nie jest włączona zmiana rozmiaru w poziomie. Zobacz resizeMode. Podobnie atrybut minResizeHeight jest ignorowany, jeśli jest większy niż minHeight lub jeśli nie jest włączona zmiana rozmiaru w pionie.
maxResizeWidth i maxResizeHeight Określ zalecany maksymalny rozmiar widżetu. Jeśli wartości nie są wielokrotnością wymiarów komórki siatki, są zaokrąglane do najbliższego rozmiaru komórki. Atrybut maxResizeWidth jest ignorowany, jeśli jest mniejszy niż minWidth lub jeśli nie jest włączona zmiana rozmiaru w poziomie. Zobacz resizeMode. Podobnie, atrybut maxResizeHeight jest ignorowany, jeśli jest mniejszy niż minHeight lub jeśli nie jest włączona zmiana rozmiaru w pionie. Wprowadzono w Androidzie 12.
resizeMode Określa reguły, według których można zmieniać rozmiar widżetu. Za pomocą tego atrybutu możesz sprawić, że rozmiar widżetów na ekranie głównym będzie można zmieniać w poziomie, w pionie, lub w obu kierunkach. Użytkownicy naciskają i przytrzymują widżet, aby wyświetlić jego uchwyty zmiany rozmiaru, następnie przeciągają uchwyty poziome lub pionowe, aby zmienić jego rozmiar w siatce układu. Wartości atrybutu resizeMode to horizontal, vertical i none. Aby zadeklarować widżet jako możliwy do zmiany rozmiaru w poziomie i w pionie, użyj horizontal|vertical.

Przykład

Aby zilustrować, jak atrybuty w tabeli powyżej wpływają na rozmiar widżetu, załóżmy, że obowiązują te specyfikacje:

  • Komórka siatki ma 30 dp szerokości i 50 dp wysokości.
  • Podano tę specyfikację atrybutu:
<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" />

Od Androida 12:

Użyj atrybutów targetCellWidth i targetCellHeight jako domyślnego rozmiaru widżetu.

Domyślny rozmiar widżetu to 2x2. Rozmiar widżetu można zmniejszyć do 2x1 lub zwiększyć do 4x3.

Android 11 i starsze wersje:

Użyj atrybutów minWidth i minHeight, aby obliczyć domyślny rozmiar widżetu.

Domyślna szerokość = Math.ceil(80 / 30) = 3

Domyślna wysokość = Math.ceil(80 / 50) = 2

Domyślny rozmiar widżetu to 3x2. Rozmiar widżetu można zmniejszyć do 2x1 lub zwiększyć do pełnego ekranu.

Dodatkowe atrybuty widżetu

W tabeli poniżej opisujemy atrybuty <appwidget-provider> dotyczące cech innych niż rozmiar widżetu.

Atrybuty i opis
updatePeriodMillis Określa, jak często framework widżetu wysyła żądanie aktualizacji do GlanceAppWidgetReceiver wywołując metodę wywołania zwrotnego onUpdate(). Aby oszczędzać baterię, zalecamy aktualizowanie tak rzadko, jak to możliwe – nie częściej niż raz na godzinę. Więcej informacji znajdziesz w sekcji Kiedy aktualizować widżety w artykule Zarządzanie stanem w Glance.
initialLayout Wskazuje zasób układu, który określa układ wczytywania widżetu przed renderowaniem kompozycji interfejsu Glance. Możesz użyć wstępnie zdefiniowanego układu wczytywania dostępnego w bibliotece: @layout/glance_default_loading_layout.
configure Określa aktywność konfiguracji, która jest uruchamiana, gdy użytkownik dodaje widżet. Zapoznaj się z przewodnikiem Umożliwianie użytkownikom konfigurowania widżetów aplikacji.
description Określa opis, który ma być wyświetlany w selektorze widżetów. Wprowadzono w Androidzie 12.
previewLayout (Android 12) i previewImage (Android 11 i starsze wersje)
  • Od Androida 12 atrybut previewLayout określa skalowalny podgląd, który udostępniasz jako układ XML ustawiony na domyślny rozmiar widżetu. Najlepiej, aby wskazywał on statyczne mapowanie XML pasujące do układu projektu.
  • W Androidzie 11 lub starszym atrybut previewImage określa statyczny obraz zrzutu ekranu przedstawiający wygląd widżetu, który pojawia się w selektorze widżetów.
Zalecamy określenie obu tych atrybutów, aby aplikacja mogła bezproblemowo działać na starszych platformach. W przypadku nowszych platform (Android 15 i nowsze) możesz zdefiniować podglądy generowane na żywo w Kotlinie za pomocą `GlanceAppWidget.providePreview`. Zapoznaj się z przewodnikiem Podglądy generowane.
autoAdvanceViewId Określa identyfikator widoku podrzędnego widżetu, który jest automatycznie przesuwany przez hosta widżetu.
widgetCategory Określa, czy widżet może być wyświetlany na ekranie głównym (home_screen), ekranie blokady (keyguard) czy na obu tych ekranach. W Androidzie 5.0 i nowszych wersjach prawidłowa jest tylko wartość home_screen.
widgetFeatures Określa funkcje obsługiwane przez widżet. Jeśli na przykład konfiguracja widżetu jest opcjonalna, określ zarówno configuration_optional, jak i reconfigurable.

Definiowanie GlanceAppWidget

  1. Utwórz nową klasę, która rozszerza GlanceAppWidget i zastępuje metodę provideGlance. Jest to metoda, w której możesz wczytywać dane potrzebne do renderowania widżetu:

    znajdziesz w artykule Korzystanie z coroutines w celu zapewnienia bezpieczeństwa wątku głównego.

    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. Utwórz instancję w glanceAppWidget w GlanceAppWidgetReceiver:

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

Właśnie skonfigurowano AppWidget za pomocą Glance.

Używanie klasy GlanceAppWidgetReceiver do obsługi transmisji widżetów

Klasa GlanceAppWidgetReceiver koordynuje transmisje widżetów i aktualizacje stanu platformy , rozszerzając podstawową klasę AppWidgetProvider. Otrzymuje zdarzenia platformy, gdy widżet jest aktualizowany, usuwany, włączany lub wyłączany, i przekształca je w żądania cyklu życia Compose.

Deklarowanie widżetu w pliku manifestu

Zadeklaruj podklasę GlanceAppWidgetReceiver jako odbiornik transmisji w pliku AndroidManifest.xml:

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

Element <receiver> wymaga atrybutu android:name, który określa klasę odbiornika. Odbiornik musi akceptować działanie transmisji ACTION_APPWIDGET_UPDATE w elemencie <intent-filter>.

Element <meta-data> musi mieć nazwę android.appwidget.provider, a atrybut android:resource musi wskazywać zasób metadanych XML AppWidgetProviderInfo (@xml/my_app_widget_info).

Implementowanie klasy GlanceAppWidgetReceiver

W Glance rozszerzasz GlanceAppWidgetReceiver zamiast bezpośrednio AppWidgetProvider. Zaimplementuj ją, łącząc odbiornik z instancją GlanceAppWidget. Podstawowe wywołania zwrotne dostępne w GlanceAppWidgetReceiver działają w ten sposób:

  • onUpdate(): automatycznie zastępowana przez Glance w celu wykonywania aktualizacji kompozycji. Jeśli ręcznie zastąbiesz onUpdate, musisz wywołać super.onUpdate, aby Glance mogła pomyślnie uruchomić wątki kompozycji.
  • onAppWidgetOptionsChanged(): wywoływana, gdy widżet jest umieszczany lub zmieniany. Glance odczytuje elementy pakietu opcji w tle, dzięki czemu układ płynnie dostosowuje się do wymiarów w czasie działania.
  • onDeleted(Context, IntArray): wywoływana, gdy użytkownik usunie konkretną instancję widżetu.
  • onEnabled(Context): wywoływana, gdy pierwsza instancja widżetu zostanie utworzona. Doskonała do przeprowadzania migracji globalnych.
  • onDisabled(Context): wywoływana, gdy ostatnia aktywna instancja dostawcy zostanie usunięta.
  • onReceive(Context, Intent): przechwytuje każdą transmisję platformy przed wywołaniem konkretnych metod wywołania zwrotnego. Musisz się upewnić, że każda napisana przez Ciebie niestandardowa logika odbiornika wywołuje super.onReceive(context, intent) i nigdy nie wywołuje goAsync , ponieważ Glance automatycznie kieruje pracę asynchronicznie.

Otrzymywanie intencji transmisji widżetów

W tle GlanceAppWidgetReceiver filtruje i obsługuje te podstawowe intencje transmisji widżetów platformy:

Tworzenie interfejsu

Ten fragment kodu pokazuje, jak utworzyć interfejs:

/* 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>()
                )
            }
        }
    }
}

Przykładowy kod powyżej wykonuje te czynności:

  • W elemencie Column najwyższego poziomu elementy są umieszczane pionowo jeden po drugim.
  • Element Column rozszerza swój rozmiar, aby dopasować się do dostępnego miejsca (za pomocą GlanceModifier), i wyrównuje zawartość do góry (verticalAlignment) oraz wyśrodkowuje ją w poziomie (horizontalAlignment).
  • Zawartość elementu Column jest definiowana za pomocą lambdy. Kolejność ma znaczenie.
    • Pierwszym elementem w Column jest komponent Text z dopełnieniem 12.dp.
    • Drugim elementem jest Row, w którym elementy są umieszczane poziomo jeden po drugim, z 2 elementami Buttons wyśrodkowanymi w poziomie (horizontalAlignment). Ostateczny wygląd zależy od dostępnego miejsca. Ten obraz przedstawia przykład tego, jak to może wyglądać:
destination_widget
Rysunek 1. Przykładowy interfejs

Możesz zmienić wartości wyrównania lub zastosować inne wartości modyfikatora (np. dopełnienie), aby zmienić położenie i rozmiar komponentów. Pełną listę komponentów, parametrów i dostępnych modyfikatorów dla każdej klasy znajdziesz w referencyjnej dokumentacji.

Implementowanie zaokrąglonych narożników

Android 12 wprowadza parametry systemowe, które umożliwiają dynamiczne dostosowywanie promieni narożników widżetów aplikacji:

  • system_app_widget_background_radius: określa promień narożnika kontenera tła widżetu (nigdy nie większy niż 28 dp).
  • Promień wewnętrzny: aby zapobiec przycinaniu treści, oblicz proporcjonalny promień dla treści wewnętrznej na podstawie konturu tła systemu: systemRadiusValue - widgetPadding

W Glance możesz dynamicznie stosować właściwości rozmiaru promienia narożnika w kompozycji za pomocą GlanceModifier.cornerRadius(android.R.dimen.system_app_widget_background_radius).

Aby zapewnić zgodność wsteczną na urządzeniach z Androidem 11 (poziom API 30) lub starszym, zaimplementuj niestandardowe atrybuty i niestandardowe rezerwy zasobów motywu:

  • /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>