Membuat widget aplikasi dengan Glance

Bagian berikut menjelaskan cara membuat widget aplikasi dasar dengan Glance.

Mendeklarasikan AppWidget dalam Manifes

Setelah menyelesaikan langkah-langkah penyiapan, deklarasikan AppWidget dan metadatanya di aplikasi Anda.

  1. Perluas penerima AppWidget dari GlanceAppWidgetReceiver:

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

  2. Daftarkan penyedia widget aplikasi di file AndroidManifest.xml dan file metadata terkait:

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

Menambahkan metadata AppWidgetProviderInfo

Selanjutnya, ikuti panduan Membuat widget untuk membuat dan menentukan info widget aplikasi dalam file @xml/my_app_widget_info.

Satu-satunya perbedaan untuk Glance adalah tidak ada XML initialLayout, tetapi Anda harus menentukannya. Anda dapat menggunakan tata letak pemuatan standar yang disediakan di library:

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

Mendeklarasikan XML AppWidgetProviderInfo

Objek AppWidgetProviderInfo menentukan kualitas penting widget Anda. Tentukan AppWidgetProviderInfo dalam file resource metadata XML (res/xml/my_app_widget_info.xml) di dalam elemen <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>

Atribut ukuran widget

Layar utama default memosisikan widget di jendelanya berdasarkan petak sel yang memiliki tinggi dan lebar yang ditentukan. Sebagian besar layar utama hanya memungkinkan widget mengambil ukuran yang merupakan kelipatan bilangan bulat dari sel petak—misalnya, dua sel secara horizontal dan tiga sel secara vertikal.

Atribut ukuran widget memungkinkan Anda menentukan ukuran default untuk widget dan memberikan batas bawah dan atas pada ukuran widget. Dalam konteks ini, ukuran default widget adalah ukuran yang digunakan widget saat pertama kali ditambahkan ke layar utama.

Tabel berikut menjelaskan atribut <appwidget-provider> yang berkaitan dengan ukuran widget:

Atribut dan deskripsi
targetCellWidth dan targetCellHeight (Android 12), minWidth dan minHeight
  • Mulai Android 12, atribut targetCellWidth dan targetCellHeight menentukan ukuran default widget dalam hal sel petak. Atribut ini diabaikan di Android 11 dan yang lebih rendah, serta dapat diabaikan jika layar utama tidak mendukung tata letak berbasis petak.
  • Atribut minWidth dan minHeight menentukan ukuran default widget dalam dp. Jika nilai untuk lebar atau tinggi minimum widget tidak cocok dengan dimensi sel, nilai tersebut akan dibulatkan ke atas ke ukuran sel terdekat.
Sebaiknya tentukan kedua set atribut—targetCellWidth dan targetCellHeight, serta minWidth dan minHeight—sehingga aplikasi Anda dapat kembali menggunakan minWidth dan minHeight jika perangkat pengguna tidak mendukung targetCellWidth dan targetCellHeight. Jika didukung, atribut targetCellWidth dan targetCellHeight lebih diprioritaskan daripada atribut minWidth dan minHeight.
minResizeWidth dan minResizeHeight Tentukan ukuran minimum absolut widget. Nilai ini menentukan ukuran yang membuat widget tidak dapat dibaca atau tidak dapat digunakan. Dengan menggunakan atribut ini, pengguna dapat mengubah ukuran widget menjadi lebih kecil dari ukuran widget default. Atribut minResizeWidth diabaikan jika lebih besar dari minWidth atau jika pengubahan ukuran horizontal tidak diaktifkan. Lihat resizeMode. Demikian pula, atribut minResizeHeight akan diabaikan jika lebih besar dari minHeight atau jika pengubahan ukuran vertikal tidak diaktifkan.
maxResizeWidth dan maxResizeHeight Tentukan ukuran maksimum yang direkomendasikan untuk widget. Jika nilai bukan kelipatan dimensi sel petak, nilai akan dibulatkan ke atas ke ukuran sel terdekat. Atribut maxResizeWidth diabaikan jika lebih kecil dari minWidth atau jika pengubahan ukuran horizontal tidak diaktifkan. Lihat resizeMode. Demikian pula, atribut maxResizeHeight akan diabaikan jika lebih kecil dari minHeight atau jika pengubahan ukuran vertikal tidak diaktifkan. Diperkenalkan di Android 12.
resizeMode Menentukan aturan yang dapat digunakan untuk mengubah ukuran widget. Anda dapat menggunakan atribut ini untuk membuat widget layar utama dapat diubah ukurannya secara horizontal, vertikal, atau pada kedua sumbu. Pengguna menyentuh lama widget untuk menampilkan handel pengubah ukuran, lalu menarik handel horizontal atau vertikal untuk mengubah ukurannya pada petak tata letak. Nilai untuk atribut resizeMode mencakup horizontal, vertical, dan none. Untuk mendeklarasikan widget sebagai dapat diubah ukurannya secara horizontal dan vertikal, gunakan horizontal|vertical.

Contoh

Untuk menggambarkan pengaruh atribut dalam tabel sebelumnya terhadap ukuran widget, asumsikan spesifikasi berikut:

  • Sel petak memiliki lebar 30 dp dan tinggi 50 dp.
  • Spesifikasi atribut berikut disediakan:
<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" />

Mulai Android 12:

Gunakan atribut targetCellWidth dan targetCellHeight sebagai ukuran default widget.

Ukuran widget adalah 2x2 secara default. Widget dapat diubah ukurannya menjadi 2x1 atau hingga 4x3.

Android 11 dan yang lebih lama:

Gunakan atribut minWidth dan minHeight untuk menghitung ukuran default widget.

Lebar default = Math.ceil(80 / 30) = 3

Tinggi default = Math.ceil(80 / 50) = 2

Ukuran widget adalah 3x2 secara default. Widget dapat diubah ukurannya menjadi 2x1 atau hingga layar penuh.

Atribut widget tambahan

Tabel berikut menjelaskan atribut <appwidget-provider> yang berkaitan dengan kualitas selain ukuran widget.

Atribut dan deskripsi
updatePeriodMillis Menentukan seberapa sering framework widget meminta update dari GlanceAppWidgetReceiver dengan memanggil metode callback onUpdate(). Sebaiknya update sesering mungkin—tidak lebih dari sekali dalam satu jam—untuk menghemat baterai. Untuk mengetahui detailnya, lihat bagian Kapan harus mengupdate widget dalam pengelolaan status Glance.
initialLayout Menunjuk ke resource tata letak yang menentukan tata letak pemuatan widget sebelum komposisi UI Glance dirender. Anda dapat menggunakan tata letak pemuatan standar yang disediakan di library: @layout/glance_default_loading_layout.
configure Menentukan aktivitas konfigurasi yang diluncurkan saat pengguna menambahkan widget. Lihat panduan Memungkinkan pengguna mengonfigurasi widget aplikasi.
description Menentukan deskripsi untuk alat pilih widget yang akan ditampilkan untuk widget Anda. Diperkenalkan di Android 12.
previewLayout (Android 12) dan previewImage (Android 11 dan yang lebih lama)
  • Mulai Android 12, atribut previewLayout menentukan pratinjau skalabel, yang Anda berikan sebagai tata letak XML yang ditetapkan ke ukuran default widget. Idealnya, ini mengarah ke pemetaan XML statis yang cocok dengan tata letak desain Anda.
  • Di Android 11 atau yang lebih rendah, atribut previewImage menentukan screenshot drawable gambar statis dari tampilan widget, yang muncul di alat pilih widget.
Sebaiknya tentukan keduanya agar aplikasi Anda dapat beralih dengan lancar di platform lama. Untuk platform yang lebih baru (Android 15+), Anda dapat menentukan pratinjau yang dihasilkan secara langsung di Kotlin menggunakan `GlanceAppWidget.providePreview`. Lihat panduan Pratinjau yang Dihasilkan.
autoAdvanceViewId Menentukan ID tampilan sub-tampilan widget yang dimajukan secara otomatis oleh host widget.
widgetCategory Menyatakan apakah widget Anda dapat ditampilkan di layar utama (home_screen), layar kunci (keyguard), atau keduanya. Untuk Android 5.0 dan yang lebih baru, hanya home_screen yang valid.
widgetFeatures Mendeklarasikan fitur yang didukung oleh widget. Misalnya, jika konfigurasi widget Anda bersifat opsional, tentukan configuration_optional dan reconfigurable.

Tentukan GlanceAppWidget

  1. Buat class baru yang memperluas dari GlanceAppWidget dan mengganti metode provideGlance. Ini adalah metode yang dapat Anda gunakan untuk memuat data yang diperlukan untuk merender widget:

    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. Buat instance-nya di glanceAppWidget pada GlanceAppWidgetReceiver Anda:

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

Anda kini telah mengonfigurasi AppWidget menggunakan Glance.

Mengelola memori gambar widget di berbagai bucket ukuran

Saat widget Anda menampilkan bitmap atau URI gambar content://, pertahankan total memori gambar RemoteViews yang diserialisasi dalam batas sistem (6 * width * height byte, diterapkan dengan IllegalArgumentException di Android 17 dan yang lebih tinggi) dengan melakukan hal berikut:

  • Dekode bitmap bersama sebelum provideContent: Jika widget Anda menggunakan SizeMode.Responsive atau SizeMode.Exact, Glance akan menjalankan lambda provideContent sekali untuk setiap bucket DpSize. Mendekode Bitmap di dalam provideContent akan mengalokasikan instance Bitmap terpisah untuk setiap bucket, sehingga melipatgandakan penggunaan memori di RemoteViews.mBitmapCache. Dekode bitmap yang di-downsample (Bitmap.Config.RGB_565 untuk gambar buram) sekali di dalam provideGlance sebelum provideContent sehingga semua bucket berbagi satu referensi Bitmap.
  • Gunakan impor ImageProvider yang cocok: Jetpack Glance menyediakan dua overload ImageProvider yang berbeda di dua paket:

    • Gunakan import androidx.glance.ImageProvider saat meneruskan ImageProvider(bitmap: Bitmap) dalam memori atau resource drawable ImageProvider(resId: Int).
    • Gunakan import androidx.glance.appwidget.ImageProvider saat meneruskan URI ImageProvider(uri: Uri). Saat menyajikan file yang di-cache melalui URI FileProvider, berikan Intent.FLAG_GRANT_READ_URI_PERMISSION ke setiap paket peluncur Intent.CATEGORY_HOME yang diinstal agar host dapat merender gambar.

Menggunakan class GlanceAppWidgetReceiver untuk menangani siaran widget

Widget koordinat GlanceAppWidgetReceiver menyiarkan dan mengupdate status platform dengan memperluas AppWidgetProvider yang mendasarinya. Class ini menerima peristiwa platform saat widget Anda diupdate, dihapus, diaktifkan, atau dinonaktifkan, dan menerjemahkannya ke dalam permintaan siklus proses Compose.

Mendeklarasikan widget dalam manifes

Deklarasikan subclass class GlanceAppWidgetReceiver Anda sebagai penerima siaran dalam file 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>

Elemen <receiver> memerlukan atribut android:name, yang menentukan class penerima. Penerima harus menerima tindakan siaran ACTION_APPWIDGET_UPDATE di dalam <intent-filter>.

Elemen <meta-data> harus mengidentifikasi namanya sebagai android.appwidget.provider, dan atribut android:resource harus mengarah ke resource metadata XML AppWidgetProviderInfo (@xml/my_app_widget_info).

Mengimplementasikan class GlanceAppWidgetReceiver

Di Glance, Anda memperluas GlanceAppWidgetReceiver, bukan AppWidgetProvider secara langsung. Terapkan dengan menautkan penerima ke instance GlanceAppWidget Anda. Callback utama yang tersedia di GlanceAppWidgetReceiver beroperasi sebagai berikut:

  • onUpdate(): Diganti secara otomatis oleh Glance untuk menjalankan update komposisi. Jika Anda mengganti onUpdate secara manual, Anda harus memanggil super.onUpdate agar Glance berhasil meluncurkan thread komposisi.
  • onAppWidgetOptionsChanged(): Dipanggil saat widget pertama kali ditempatkan atau diubah ukurannya. Glance membaca item paket opsi di balik layar sehingga tata letak Anda menyesuaikan dengan lancar berdasarkan dimensi runtime.
  • onDeleted(Context, IntArray): Dipanggil setiap kali instance widget tertentu dihapus oleh pengguna.
  • onEnabled(Context): Dipicu saat instance pertama widget Anda berhasil dibuat. Sangat cocok untuk menjalankan migrasi global.
  • onDisabled(Context): Dipanggil saat instance aktif terakhir dari penyedia dihapus.
  • onReceive(Context, Intent): Mengintersepsi setiap siaran platform sebelum metode callback tertentu. Anda harus memastikan bahwa logika penerima kustom yang Anda tulis memanggil super.onReceive(context, intent) dan tidak boleh memanggil goAsync sendiri karena Glance otomatis merutekan pekerjaan secara asinkron.

Menerima intent siaran widget

Di balik layar, GlanceAppWidgetReceiver memfilter dan menangani intent siaran widget platform dasar berikut:

Membuat UI

Cuplikan berikut menunjukkan cara membuat UI:

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

Contoh kode sebelumnya melakukan hal berikut:

  • Di Column tingkat teratas, item ditempatkan secara vertikal satu per satu.
  • Column memperluas ukurannya agar sesuai dengan ruang yang tersedia (melalui GlanceModifier dan menyelaraskan kontennya ke atas (verticalAlignment) dan menempatkannya di tengah secara horizontal (horizontalAlignment).
  • Konten Column ditentukan menggunakan lambda. Urutan sangat penting.
    • Item pertama dalam Column adalah komponen Text dengan padding 12.dp.
    • Item kedua adalah Row, tempat item ditempatkan secara horizontal satu per satu, dengan dua Buttons dipusatkan secara horizontal (horizontalAlignment). Tampilan akhir bergantung pada ruang yang tersedia. Gambar berikut adalah contoh tampilannya:
destination_widget
Gambar 1. Contoh UI.

Anda dapat mengubah nilai perataan atau menerapkan nilai pengubah yang berbeda (seperti padding) untuk mengubah penempatan dan ukuran komponen. Lihat dokumentasi referensi untuk mengetahui daftar lengkap komponen, parameter, dan pengubah yang tersedia untuk setiap class.

Menerapkan sudut bulat

Android 12 memperkenalkan parameter sistem untuk menyesuaikan radius sudut widget aplikasi Anda secara dinamis:

  • system_app_widget_background_radius: Menentukan radius sudut penampung latar belakang widget (tidak pernah lebih besar dari 28 dp).
  • Radius dalam: Untuk mencegah pemangkasan konten, hitung radius proporsional untuk konten dalam berdasarkan garis batas latar belakang sistem: systemRadiusValue - widgetPadding

Di Glance, Anda dapat menerapkan properti ukuran radius sudut secara dinamis dalam komposisi menggunakan GlanceModifier.cornerRadius(android.R.dimen.system_app_widget_background_radius).

Untuk kompatibilitas mundur pada perangkat yang menjalankan Android 11 (level API 30) atau yang lebih rendah, terapkan atribut kustom dan penggantian resource tema kustom:

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