Tworzenie widżetu aplikacji za pomocą aplikacji Glance

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

Zadeklaruj AppWidget w pliku manifestu

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

  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 artykule Tworzenie widżetu, aby utworzyć i zdefiniować informacje o widżecie aplikacji w pliku @xml/my_app_widget_info.

Jedyna różnica w przypadku funkcji 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 definiuje podstawowe właściwości widżetu. Zdefiniuj AppWidgetProviderInfo w pliku zasobów metadanych XML (res/xml/my_app_widget_info.xml) wewnątrz elementu <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 oknie na podstawie siatki komórek o określonej wysokości i szerokości. Większość ekranów głównych pozwala widżetom przyjmować rozmiary, które są całkowitymi wielokrotnościami komórek siatki, np. 2 komórki w poziomie i 3 komórki w pionie.

Atrybuty rozmiaru widżetu pozwalają określić domyślny rozmiar widżetu oraz dolną i górną granicę 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 opisano atrybuty <appwidget-provider> dotyczące rozmiaru widżetu:

Atrybuty i opis
targetCellWidth i targetCellHeight (Android 12), minWidth i minHeight
  • Począwszy od Androida 12 atrybuty targetCellWidth i targetCellHeight określają domyślny rozmiar widżetu w komórkach siatki. Te atrybuty są 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 odpowiadają wymiarom komórek, są one zaokrąglane w górę 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 używać atrybutów minWidth i minHeight, jeśli urządzenie użytkownika nie obsługuje atrybutów 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 bezwzględny minimalny rozmiar widżetu. Te wartości określają rozmiar, poniżej którego widżet jest nieczytelny lub w inny sposób bezuż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 zmiana rozmiaru w pionie nie jest włączona.
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 w górę 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 zmiana rozmiaru w pionie nie jest włączona. Funkcja wprowadzona w Androidzie 12.
resizeMode Określa reguły, według których można zmieniać rozmiar widżetu. Możesz użyć tego atrybutu, aby umożliwić zmianę rozmiaru widżetów na ekranie głównym w poziomie, w pionie lub w obu osiach. Użytkownicy naciskają i przytrzymują widżet, aby wyświetlić uchwyty zmiany rozmiaru, a następnie przeciągają uchwyty poziome lub pionowe, aby zmienić jego rozmiar w układzie siatki. Wartości atrybutu resizeMode to horizontal, vertical i none. Aby zadeklarować, że rozmiar widżetu można zmieniać w poziomie i w pionie, użyj horizontal|vertical.

Przykład

Aby zilustrować, jak atrybuty z powyższej tabeli wpływają na rozmiar widżetu, przyjmijmy te specyfikacje:

  • Komórka siatki ma 30 dp szerokości i 50 dp wysokości.
  • Dostępna jest ta specyfikacja 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 2 x 2. Rozmiar widżetu można zmniejszyć do 2 x 1 lub zwiększyć do 4 x 3.

Android 11 lub starszy:

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 3 x 2. Rozmiar widżetu można zmniejszyć do 2 x 1 lub zwiększyć do pełnego ekranu.

Dodatkowe atrybuty widżetu

W tabeli poniżej opisano atrybuty <appwidget-provider> dotyczące właściwości innych niż rozmiar widżetu.

Atrybuty i opis
updatePeriodMillis Określa, jak często platforma widżetów żąda aktualizacji od GlanceAppWidgetReceiver, wywołując metodę wywołania zwrotnego onUpdate(). Zalecamy jak najrzadsze aktualizowanie – nie częściej niż raz na godzinę – aby oszczędzać baterię. Szczegółowe informacje znajdziesz w sekcji Kiedy aktualizować widżety w artykule o zarządzaniu stanem Glance.
initialLayout Wskazuje zasób layoutu, który definiuje układ wczytywania widżetu przed wyrenderowaniem 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 uruchamia się, gdy użytkownik dodaje widżet. Zapoznaj się z przewodnikiem Umożliwianie użytkownikom konfigurowania widżetów aplikacji.
description Określa opis widżetu, który ma się wyświetlać w selektorze widżetów. Wprowadzono w Androidzie 12.
previewLayout (Android 12) i previewImage (Android 11 i starsze)
  • 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 odpowiadające układowi 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 wartości, aby aplikacja działała prawidłowo na starszych platformach. Na nowszych platformach (Android 15 lub nowszy) możesz definiować podglądy generowane na żywo w Kotlinie za pomocą `GlanceAppWidget.providePreview`. Zobacz przewodnik po generowanych podglądach.
autoAdvanceViewId Określa identyfikator widoku podrzędnego widżetu, który jest automatycznie przełączany 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 przypadku Androida 5.0 lub nowszego prawidłowa jest tylko wartość home_screen.
widgetFeatures Deklaruje 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.

Zdefiniuj GlanceAppWidget

  1. Utwórz nową klasę, która rozszerza klasę GlanceAppWidget i zastępuje metodę provideGlance. To jest metoda, za pomocą której możesz wczytać dane potrzebne do renderowania widżetu:

    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 pliku glanceAppWidget na urządzeniu GlanceAppWidgetReceiver:

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

Masz teraz skonfigurowany AppWidget za pomocą funkcji Glance.

Zarządzanie pamięcią obrazów widżetów w różnych przedziałach rozmiarów

Gdy widżet wyświetla bitmapy lub identyfikatory URI obrazów content://, pamiętaj, aby łączna ilość pamięci na zserializowane obrazy RemoteViews nie przekraczała limitu systemowego (6 * width * height b, wymuszanego przez IllegalArgumentException na Androidzie 17 i nowszych). Aby to zrobić:

  • Dekodowanie udostępnionych bitmap przed provideContent: jeśli widżet używa SizeMode.Responsive lub SizeMode.Exact, Glance uruchamia funkcję lambda provideContent raz dla każdego zasobnika DpSize. Dekodowanie Bitmap wewnątrz provideContent przydziela oddzielną instancję Bitmap dla każdego zasobnika, co zwiększa wykorzystanie pamięci w RemoteViews.mBitmapCache. Dekoduj bitmapy o zmniejszonym próbkowaniu (Bitmap.Config.RGB_565 w przypadku obrazów nieprzezroczystych) raz w provideGlance przed provideContent, aby wszystkie zasobniki korzystały z jednego odwołania Bitmap.
  • Użyj odpowiedniego importu ImageProvider: Jetpack Glance udostępnia 2 różne przeciążenia ImageProvider w 2 pakietach:

    • Użyj import androidx.glance.ImageProvider, gdy przekazujesz zasób ImageProvider(bitmap: Bitmap) w pamięci lub obiekt rysowalny ImageProvider(resId: Int).
    • Podczas przekazywania identyfikatora URI używaj import androidx.glance.appwidget.ImageProviderImageProvider(uri: Uri). Podczas udostępniania plików z pamięci podręcznej za pomocą identyfikatora URI FileProvider przyznaj uprawnienie Intent.FLAG_GRANT_READ_URI_PERMISSION każdemu zainstalowanemu pakietowi programu uruchamiającego Intent.CATEGORY_HOME, aby host mógł wyrenderować obraz.

Obsługa komunikatów widżetu za pomocą klasy GlanceAppWidgetReceiver

Widżet współrzędnych GlanceAppWidgetReceiver transmituje i aktualizuje stan platformy, rozszerzając bazowy element AppWidgetProvider. Odbiera 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ę klasy GlanceAppWidgetReceiver jako odbiornik komunikatów 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. Odbiorca musi zaakceptować działanie rozgłaszania ACTION_APPWIDGET_UPDATE w <intent-filter>.

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

Implementowanie klasy GlanceAppWidgetReceiver

W Glance rozszerzasz GlanceAppWidgetReceiver, a nie bezpośrednio AppWidgetProvider. Zaimplementuj go, łącząc odbiornik z instancją GlanceAppWidget. Główne wywołania zwrotne dostępne w GlanceAppWidgetReceiver działają w następujący sposób:

  • onUpdate(): automatycznie zastępowany przez Glance w celu wykonywania aktualizacji kompozycji. Jeśli ręcznie zastąpisz onUpdate, musisz wywołać super.onUpdate, aby umożliwić Glance uruchamianie wątków kompozycji.
  • onAppWidgetOptionsChanged(): wywoływana, gdy widżet jest umieszczany po raz pierwszy lub gdy zmienia się jego rozmiar. Glance odczytuje elementy pakietu opcji, dzięki czemu układ płynnie dostosowuje się do wymiarów środowiska wykonawczego.
  • onDeleted(Context, IntArray): wywoływana, gdy użytkownik usunie konkretną instancję widżetu.
  • onEnabled(Context): wywoływane, gdy zostanie utworzone pierwsze wystąpienie widżetu. Doskonale nadaje się do przeprowadzania globalnych migracji.
  • onDisabled(Context): wywoływana, gdy ostatnia aktywna instancja dostawcy zostanie usunięta.
  • onReceive(Context, Intent): przechwytuje każdą transmisję platformy przed określonymi metodami wywołania zwrotnego. Musisz upewnić się, że każda niestandardowa logika odbiornika, którą napiszesz, wywołuje super.onReceive(context, intent) i nigdy nie wywołuje goAsync, ponieważ Glance automatycznie kieruje pracę asynchronicznie.

Odbieranie intencji rozgłaszania widżetu

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

Tworzenie interfejsu

Poniższy 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>()
                )
            }
        }
    }
}

Powyższy przykładowy kod wykonuje takie operacje:

  • W elemencie najwyższego poziomu Column elementy są umieszczane pionowo jeden po drugim.
  • 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).
  • Treść elementu Column jest definiowana za pomocą funkcji lambda. Kolejność ma znaczenie.
    • Pierwszym elementem w Column jest komponent Text z dopełnieniem 12.dp.
    • Drugi element to Row, w którym elementy są umieszczone poziomo jeden po drugim, z dwoma elementami Buttons wyśrodkowanymi w poziomie (horizontalAlignment). Ostateczny wygląd zależy od dostępnego miejsca. Poniższy obraz pokazuje, jak może to 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 dokumentacji referencyjnej.

Zaimplementuj zaokrąglone rogi

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ń treści wewnętrznej na podstawie obrysu tła systemowego: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 atrybuty niestandardowe i zasoby zastępcze motywu niestandardowego:

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