Membuat Widget Aplikasi

Widget Aplikasi adalah tampilan aplikasi mini yang dapat disematkan dalam aplikasi lain (seperti Layar utama) dan menerima update berkala. Tampilan ini disebut sebagai Widget dalam antarmuka pengguna, dan Anda dapat memublikasikannya dengan penyedia Widget Aplikasi. Komponen aplikasi yang dapat menampung Widget Aplikasi lain disebut host Widget Aplikasi. Screenshot di bawah ini menunjukkan Widget Aplikasi Musik.

Dokumen ini menjelaskan cara memublikasikan Widget Aplikasi menggunakan penyedia Widget Aplikasi. Untuk pembahasan tentang cara membuat AppWidgetHost untuk menghosting widget aplikasi, lihat Host Widget Aplikasi.

Catatan: Untuk informasi tentang cara mendesain widget aplikasi, baca Ringkasan Widget Aplikasi.

Dasar-Dasar

Untuk membuat Widget Aplikasi, Anda memerlukan hal-hal berikut:

Objek AppWidgetProviderInfo
Menjelaskan metadata untuk Widget Aplikasi, seperti tata letak Widget Aplikasi, frekuensi update, dan class AppWidgetProvider. Hal ini harus ditentukan dalam XML.
Penerapan class AppWidgetProvider
Menentukan metode dasar yang memungkinkan Anda berinteraksi secara terprogram dengan Widget Aplikasi, berdasarkan peristiwa siaran. Melalui aplikasi ini, Anda akan menerima siaran saat Widget Aplikasi diupdate, diaktifkan, dinonaktifkan, dan dihapus.
Tata letak tampilan
Menentukan tata letak awal Widget Aplikasi, yang ditentukan dalam XML.

Sebagai tambahan, Anda dapat menerapkan Aktivitas konfigurasi Widget Aplikasi. Ini merupakan Activity opsional yang diluncurkan saat pengguna menambahkan Widget Aplikasi Anda dan memungkinkan pengguna memodifikasi setelan Widget Aplikasi pada waktu pembuatan.

Bagian berikut menjelaskan cara menyiapkan setiap komponen ini.

Mendeklarasikan Widget Aplikasi di Manifes

Pertama, deklarasikan class AppWidgetProvider dalam file AndroidManifest.xml aplikasi. Contoh:

    <receiver android:name="ExampleAppWidgetProvider" >
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        </intent-filter>
        <meta-data android:name="android.appwidget.provider"
                   android:resource="@xml/example_appwidget_info" />
    </receiver>
    

Elemen <receiver> memerlukan atribut android:name yang menentukan AppWidgetProvider yang digunakan oleh Widget Aplikasi.

Elemen <intent-filter> harus berisi elemen <action> dengan atribut android:name. Atribut ini menentukan bahwa AppWidgetProvider menerima siaran ACTION_APPWIDGET_UPDATE. Siaran ini merupakan satu-satunya yang harus Anda deklarasikan secara eksplisit. AppWidgetManager secara otomatis mengirimkan semua siaran Widget Aplikasi ke AppWidgetProvider sesuai kebutuhan.

Elemen <meta-data> menentukan resource AppWidgetProviderInfo dan memerlukan atribut berikut:

  • android:name - Menentukan nama metadata. Gunakan android.appwidget.provider untuk mengidentifikasi data sebagai deskriptor AppWidgetProviderInfo.
  • android:resource - Menentukan lokasi resource AppWidgetProviderInfo.

Menambahkan Metadata AppWidgetProviderInfo

AppWidgetProviderInfo menentukan kualitas penting Widget Aplikasi, seperti dimensi tata letak minimum, resource tata letak awal, seberapa sering update dilakukan untuk Widget Aplikasi, dan (secara opsional) peluncuran Aktivitas konfigurasi pada waktu pembuatan. Tentukan objek AppWidgetProviderInfo dalam resource XML menggunakan satu elemen <appwidget-provider> dan simpan ke folder res/xml/ project.

Contoh:

    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
        android:minWidth="40dp"
        android:minHeight="40dp"
        android:updatePeriodMillis="86400000"
        android:previewImage="@drawable/preview"
        android:initialLayout="@layout/example_appwidget"
        android:configure="com.example.android.ExampleAppWidgetConfigure"
        android:resizeMode="horizontal|vertical"
        android:widgetCategory="home_screen">
    </appwidget-provider>
    

Berikut adalah ringkasan atribut <appwidget-provider>:

  • Nilai untuk atribut minWidth dan minHeight menentukan jumlah ruang minimum yang digunakan Widget Aplikasi secara default. Layar utama default menentukan posisi Widget Aplikasi dalam jendela berdasarkan petak sel yang memiliki tinggi dan lebar yang ditentukan. Jika nilai untuk lebar atau tinggi minimum Widget Aplikasi tidak sesuai dengan dimensi sel, dimensi Widget Aplikasi akan dibulatkan ke atas ke ukuran sel terdekat.

    Lihat Panduan Desain Widget Aplikasi untuk mengetahui informasi selengkapnya tentang pengukuran Widget Aplikasi Anda.

    Catatan: Untuk menjadikan widget aplikasi portabel di seluruh perangkat, ukuran minimum widget aplikasi Anda tidak boleh melebihi sel 4 x 4.

  • Atribut minResizeWidth dan minResizeHeight menentukan ukuran minimum absolut Widget Aplikasi. Nilai ini harus menentukan ukuran yang akan menjadikan Widget Aplikasi tidak terbaca atau tidak dapat dipakai jika berada di bawahnya. Penggunaan atribut ini memungkinkan pengguna mengubah ukuran widget ke ukuran yang lebih kecil dari ukuran default widget yang ditentukan oleh atribut minWidth dan minHeight. Diperkenalkan di Android 3.1.

    Lihat Panduan Desain Widget Aplikasi untuk mengetahui informasi selengkapnya tentang pengukuran Widget Aplikasi Anda.

  • Atribut updatePeriodMillis menentukan seberapa sering framework Widget Aplikasi harus meminta update dari AppWidgetProvider dengan memanggil metode callback onUpdate(). Update yang sebenarnya belum tentu terjadi tepat waktu dengan nilai ini dan sebaiknya Anda mengupdate sesering mungkin, tetapi-tidak lebih dari satu kali dalam satu jam untuk menghemat baterai. Anda juga dapat mengizinkan pengguna menyesuaikan frekuensi di konfigurasi—beberapa orang mungkin ingin mengupdate simbol saham setiap 15 menit, atau mungkin hanya empat kali dalam satu hari.

    Catatan: Jika perangkat beralih ke mode tidur saat waktunya update (seperti yang ditetapkan oleh updatePeriodMillis), perangkat akan diaktifkan untuk menjalankan update. Jika Anda tidak mengupdate lebih dari satu kali dalam satu jam, hal ini mungkin tidak akan menyebabkan masalah yang signifikan pada masa pakai baterai Anda. Namun, jika Anda perlu mengupdate lebih sering dan/atau Anda tidak perlu mengupdate saat perangkat dalam mode tidur, Anda dapat menjalankan update berdasarkan alarm yang tidak akan mengaktifkan perangkat. Untuk melakukannya, setel alarm dengan intent yang akan diterima AppWidgetProvider Anda menggunakan AlarmManager. Setel jenis alarm ke ELAPSED_REALTIME atau RTC, yang hanya akan membunyikan alarm saat perangkat aktif. Kemudian, setel updatePeriodMillis ke nol ("0").

  • Atribut initialLayout mengarah ke resource tata letak yang menentukan tata letak Widget Aplikasi.
  • Atribut configure menentukan Activity yang akan diluncurkan saat pengguna menambahkan Widget Aplikasi, untuk mengonfigurasi properti Widget Aplikasi. Langkah ini bersifat opsional (baca Membuat Aktivitas Konfigurasi Widget Aplikasi di bawah).
  • Atribut previewImage menentukan pratinjau tampilan widget aplikasi setelah dikonfigurasi, yang akan dilihat oleh pengguna saat memilih widget aplikasi tersebut. Jika tidak disediakan, pengguna akan melihat ikon peluncur aplikasi Anda. Kolom ini sesuai dengan atribut android:previewImage dalam elemen <receiver> di file AndroidManifest.xml. Untuk pembahasan lebih lanjut tentang penggunaan previewImage, lihat Menyetel Gambar Pratinjau. Diperkenalkan di Android 3.0.
  • Atribut autoAdvanceViewId menentukan ID tampilan sub-tampilan widget aplikasi yang harus dimajukan secara otomatis oleh host widget. Diperkenalkan di Android 3.0.
  • Atribut resizeMode menentukan aturan yang menjadikan ukuran widget dapat diubah. Anda dapat menggunakan atribut ini untuk membuat widget layar utama dapat diubah ukurannya—secara horizontal, vertikal, atau pada kedua sumbu. Pengguna dapat menyentuh lama widget untuk menampilkan tuas pengubah ukuran, lalu menarik tuas horizontal dan/atau vertikal untuk mengubah ukuran pada petak tata letak. Nilai atribut resizeMode mencakup "horizontal", "vertikal", dan "tidak ada". Untuk mendeklarasikan widget dapat diubah ukurannya secara horizontal dan vertikal, berikan nilai "horizontal|vertical". Diperkenalkan di Android 3.1.
  • Atribut minResizeHeight menentukan tinggi minimum (dalam dps) yang menjadi acuan untuk mengubah ukuran widget. Kolom ini tidak berpengaruh jika lebih besar dari minHeight atau jika pengubahan ukuran secara vertikal tidak diaktifkan (lihat resizeMode). Diperkenalkan di Android 4.0.
  • Atribut minResizeWidth menentukan lebar minimum (dalam dps) yang menjadi acuan untuk mengubah ukuran widget. Kolom ini tidak berpengaruh jika lebih besar dari minWidth atau jika pengubahan ukuran secara horizontal tidak diaktifkan (lihat resizeMode). Diperkenalkan di Android 4.0.
  • Atribut widgetCategory mendeklarasikan apakah Widget Aplikasi Anda dapat ditampilkan di layar utama (home_screen), layar kunci (keyguard), atau keduanya. Hanya versi Android yang lebih rendah dari 5.0 yang mendukung widget layar kunci. Untuk Android 5.0 dan yang lebih tinggi, hanya home_screen yang valid.

Lihat class AppWidgetProviderInfo untuk mengetahui informasi selengkapnya tentang atribut yang diterima oleh elemen <appwidget-provider>.

Membuat Tata Letak Widget Aplikasi

Anda harus menentukan tata letak awal untuk Widget Aplikasi Anda dalam XML dan menyimpannya dalam direktori projek res/layout/. Anda dapat mendesain Widget Aplikasi menggunakan objek View yang ditampilkan di bawah, tetapi sebelum memulai mendesain Widget Aplikasi, baca dan pahami Panduan Desain Widget Aplikasi.

Pembuatan tata letak Widget Aplikasi cukup sederhana jika Anda memahami Tata Letak. Namun, perlu diketahui bahwa tata letak Widget Aplikasi didasarkan pada RemoteViews, yang tidak mendukung setiap jenis tata letak atau widget tampilan.

Objek RemoteViews (dan Widget Aplikasi) dapat mendukung class tata letak berikut ini:

Class widget berikut juga didukung:

Turunan dari semua class tersebut tidak didukung.

RemoteViews juga mendukung ViewStub, yang merupakan Tampilan tidak terlihat berukuran nol yang dapat digunakan untuk memperluas resource tata letak sesuai keinginan pada waktu proses.

Menambahkan margin ke Widget Aplikasi

Biasanya, widget tidak boleh diperluas ke tepi layar dan tidak boleh secara visual digantikan dengan widget lain, sehingga Anda perlu menambahkan margin pada semua sisi di sekitar bingkai widget.

Mulai versi Android 4.0, widget aplikasi akan secara otomatis mendapat padding di antara bingkai widget dan kotak pembatas widget aplikasi untuk memberikan perataan yang lebih baik dengan ikon dan widget lain pada layar utama pengguna. Untuk memanfaatkan perilaku yang sangat direkomendasikan ini, setel targetSdkVersion aplikasi ke 14 atau lebih besar.

Anda dapat dengan mudah menulis tata letak yang memiliki margin kustom yang diterapkan untuk versi sebelumnya, dan tidak memiliki margin tambahan untuk Android 4.0 atau lebih besar.

  1. Setel aplikasi targetSdkVersion Anda ke 14 atau lebih besar.
  2. Buat tata letak seperti yang ditunjukkan di bawah ini, yang mengacu pada resource dimensi untuk marginnya.
        <FrameLayout
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:padding="@dimen/widget_margin">
    
          <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal"
            android:background="@drawable/my_widget_background">
            …
          </LinearLayout>
    
        </FrameLayout>
        
  3. Buat dua resource dimensi, satu di res/values/ untuk memberikan margin kustom sebelum Android 4.0, dan satu di res/values-v14/ untuk tidak memberikan padding tambahan bagi widget Android 4.0:

    res/values/dimens.xml:

    <dimen name="widget_margin">8dp</dimen>

    res/values-v14/dimens.xml:

    <dimen name="widget_margin">0dp</dimen>

Opsi lainnya adalah dengan hanya membuat margin tambahan ke aset latar belakang 9-patch secara default, dan menyediakan 9-patch yang berbeda tanpa margin untuk API level 14 atau yang lebih baru.

Menggunakan Class AppWidgetProvider

Class AppWidgetProvider memperluas BroadcastReceiver sebagai class praktis untuk menangani siaran Widget Aplikasi. AppWidgetProvider hanya menerima siaran peristiwa yang relevan dengan Widget Aplikasi, seperti saat Widget Aplikasi diupdate, dihapus, diaktifkan, dan dinonaktifkan. Saat peristiwa siaran ini terjadi, AppWidgetProvider menerima panggilan metode berikut:

onUpdate()
Metode ini dipanggil untuk mengupdate Widget Aplikasi pada interval yang ditentukan oleh atribut updatePeriodMillis dalam AppWidgetProviderInfo (lihat Menambahkan Metadata AppWidgetProviderInfo di atas). Metode ini juga dipanggil saat pengguna menambahkan Widget Aplikasi agar dapat melakukan penyiapan penting, seperti menentukan pengendali peristiwa untuk Tampilan dan membuat Service sementara, jika perlu. Namun jika Anda telah mendeklarasikan Aktivitas konfigurasi, metode ini tidak akan dipanggil saat pengguna menambahkan Widget Aplikasi, tetapi akan dipanggil untuk update berikutnya. Aktivitas konfigurasi bertanggung jawab untuk melakukan update pertama saat konfigurasi selesai. (Lihat Membuat Aktivitas Konfigurasi Widget Aplikasi di bawah.)
onAppWidgetOptionsChanged()
Callback ini dipanggil saat widget pertama kali ditempatkan dan setiap kali ukuran widget diubah. Anda dapat menggunakan callback ini untuk menampilkan atau menyembunyikan konten berdasarkan rentang ukuran widget. Anda mendapatkan rentang ukuran dengan memanggil getAppWidgetOptions(), yang akan menampilkan Bundle yang menyertakan hal-hal berikut:

Callback ini diperkenalkan pada API Level 16 (Android 4.1). Jika menerapkan callback ini, pastikan aplikasi Anda tidak bergantung pada callback ini karena tidak akan dipanggil pada perangkat lama.
onDeleted(Context, int[])
Callback ini dipanggil setiap kali Widget Aplikasi dihapus dari host Widget Aplikasi.
onEnabled(Context)
Callback ini dipanggil saat instance Widget Aplikasi dibuat untuk pertama kalinya. Misalnya, jika pengguna menambahkan dua instance Widget Aplikasi, callback ini hanya dipanggil pada pembuatan pertama. Jika Anda perlu membuka database baru atau menjalankan penyiapan lain yang hanya perlu dilakukan sekali untuk semua instance Widget Aplikasi, ini merupakan tempat yang tepat untuk melakukannya.
onDisabled(Context)
Callback ini dipanggil saat instance terakhir Widget Aplikasi Anda dihapus dari host Widget Aplikasi. Pada tahap ini, Anda harus menghapus semua pekerjaan yang dilakukan di onEnabled(Context), seperti menghapus database sementara.
onReceive(Context, Intent)
Callback ini dipanggil untuk setiap siaran dan sebelum masing-masing metode callback di atas. Biasanya, Anda tidak perlu menerapkan metode ini karena penerapan AppWidgetProvider default akan memfilter semua siaran Widget Aplikasi dan memanggil metode di atas yang sesuai.

Anda harus mendeklarasikan penerapan class AppWidgetProvider sebagai penerima siaran menggunakan elemen <receiver> dalam AndroidManifest (lihat Mendeklarasikan Widget Aplikasi di Manifes di atas).

Callback AppWidgetProvider yang terpenting adalah onUpdate() karena dipanggil saat masing-masing Widget Aplikasi ditambahkan ke host (kecuali jika Anda menggunakan Aktivitas konfigurasi). Jika Widget Aplikasi Anda menerima peristiwa interaksi pengguna, Anda perlu mendaftarkan pengendali peristiwa dalam callback ini. Jika Widget Aplikasi tidak membuat file atau database sementara, atau menjalankan pekerjaan lain yang perlu dihapus, onUpdate() mungkin merupakan satu-satunya metode callback yang perlu ditentukan. Misalnya, jika Anda ingin Widget Aplikasi memiliki tombol yang meluncurkan Aktivitas saat diklik, Anda dapat menggunakan penerapan AppWidgetProvider berikut:

Kotlin

    class ExampleAppWidgetProvider : AppWidgetProvider() {

        override fun onUpdate(
                context: Context,
                appWidgetManager: AppWidgetManager,
                appWidgetIds: IntArray
        ) {
            // Perform this loop procedure for each App Widget that belongs to this provider
            appWidgetIds.forEach { appWidgetId ->
                // Create an Intent to launch ExampleActivity
                val pendingIntent: PendingIntent = Intent(context, ExampleActivity::class.java)
                        .let { intent ->
                            PendingIntent.getActivity(context, 0, intent, 0)
                        }

                // Get the layout for the App Widget and attach an on-click listener
                // to the button
                val views: RemoteViews = RemoteViews(
                        context.packageName,
                        R.layout.appwidget_provider_layout
                ).apply {
                    setOnClickPendingIntent(R.id.button, pendingIntent)
                }

                // Tell the AppWidgetManager to perform an update on the current app widget
                appWidgetManager.updateAppWidget(appWidgetId, views)
            }
        }
    }
    

Java

    public class ExampleAppWidgetProvider extends AppWidgetProvider {

        public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
            final int N = appWidgetIds.length;

            // Perform this loop procedure for each App Widget that belongs to this provider
            for (int i=0; i<N; i++) {
                int appWidgetId = appWidgetIds[i];

                // Create an Intent to launch ExampleActivity
                Intent intent = new Intent(context, ExampleActivity.class);
                PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);

                // Get the layout for the App Widget and attach an on-click listener
                // to the button
                RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider_layout);
                views.setOnClickPendingIntent(R.id.button, pendingIntent);

                // Tell the AppWidgetManager to perform an update on the current app widget
                appWidgetManager.updateAppWidget(appWidgetId, views);
            }
        }
    }
    

AppWidgetProvider ini hanya menentukan metode onUpdate() untuk menentukan PendingIntent yang meluncurkan Activity dan melampirkannya pada tombol Widget Aplikasi dengan setOnClickPendingIntent(int, PendingIntent). Perlu diketahui bahwa hal tersebut mencakup loop yang melakukan iterasi melalui setiap entri dalam appWidgetIds, yang merupakan array ID yang mengidentifikasi setiap Widget Aplikasi yang dibuat oleh penyedia ini. Dengan begitu, jika pengguna membuat lebih dari satu instance Widget Aplikasi, semua instance akan diupdate secara bersamaan. Namun, hanya satu jadwal updatePeriodMillis yang akan dikelola untuk semua instance Widget Aplikasi. Misalnya, jika jadwal update ditentukan menjadi setiap dua jam sekali, dan instance kedua Widget Aplikasi ditambahkan satu jam setelah yang pertama, keduanya akan diupdate pada periode yang ditentukan oleh update pertama, dan periode update kedua akan diabaikan (keduanya akan diupdate setiap dua jam, bukan setiap jam).

Catatan: Karena AppWidgetProvider merupakan ekstensi dari BroadcastReceiver, proses Anda tidak dijamin tetap berjalan setelah metode callback kembali (lihat BroadcastReceiver untuk informasi tentang siklus hidup siaran). Jika proses penyiapan Widget Aplikasi membutuhkan beberapa detik (mungkin saat menjalankan permintaan web) dan Anda perlu melanjutkan proses, pertimbangkan untuk memulai Service dalam metode onUpdate(). Dari dalam Layanan, Anda dapat menjalankan update sendiri pada Widget Aplikasi tanpa perlu khawatir AppWidgetProvider akan tertutup karena error Aplikasi Tidak Merespons (ANR). Lihat Contoh AppWidgetProvider dari Wiktionary untuk melihat contoh Widget Aplikasi yang menjalankan Service.

Lihat juga contoh class ExampleAppWidgetProvider.java.

Menerima Intent siaran Widget Aplikasi

AppWidgetProvider hanyalah class praktis. Jika ingin menerima siaran Widget Aplikasi secara langsung, Anda dapat menerapkan BroadcastReceiver sendiri atau mengganti callback onReceive(Context, Intent). Berikut adalah intent yang perlu Anda perhatikan:

Memasang Pin pada Widget Aplikasi

Pada perangkat yang menjalankan Android 8.0 (API level 26) dan yang lebih tinggi, peluncur yang memungkinkan Anda membuat pintasan yang dipasangi pin juga memungkinkan Anda memasang pin pada widget aplikasi ke peluncur. Seperti pintasan yang dipasangi pin, widget yang dipasangi pin memberi pengguna akses ke tugas tertentu di aplikasi Anda.

Di aplikasi, Anda dapat mengajukan permintaan pada sistem untuk memasang pin pada widget ke peluncur yang didukung dengan melakukan langkah-langkah berikut:

  1. Buat widget di file manifes aplikasi Anda seperti yang ditunjukkan dalam cuplikan berikut:
        <manifest>
        ...
          <application>
            ...
            <receiver android:name="MyAppWidgetProvider">
                <intent-filter>
                    <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                </intent-filter>
                <meta-data android:name="android.appwidget.provider"
                           android:resource="@xml/my_appwidget_info" />
            </receiver>
          </application>
        </manifest>
        
  2. Panggil metode requestPinAppWidget(), seperti yang ditunjukkan dalam cuplikan kode berikut:

    Kotlin

        val appWidgetManager: AppWidgetManager = context.getSystemService(AppWidgetManager::class.java)
        val myProvider = ComponentName(context, MyAppWidgetProvider::class.java)
    
        val successCallback: PendingIntent? = if (appWidgetManager.isRequestPinAppWidgetSupported) {
            // Create the PendingIntent object only if your app needs to be notified
            // that the user allowed the widget to be pinned. Note that, if the pinning
            // operation fails, your app isn't notified.
            Intent(...).let { intent ->
                // Configure the intent so that your app's broadcast receiver gets
                // the callback successfully. This callback receives the ID of the
                // newly-pinned widget (EXTRA_APPWIDGET_ID).
                PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
            }
        } else {
            null
        }
    
        successCallback?.also { pendingIntent ->
            appWidgetManager.requestPinAppWidget(myProvider, null, pendingIntent)
        }
        

    Java

        AppWidgetManager appWidgetManager =
                context.getSystemService(AppWidgetManager.class);
        ComponentName myProvider =
                new ComponentName(context, MyAppWidgetProvider.class);
    
        if (appWidgetManager.isRequestPinAppWidgetSupported()) {
            // Create the PendingIntent object only if your app needs to be notified
            // that the user allowed the widget to be pinned. Note that, if the pinning
            // operation fails, your app isn't notified.
            Intent pinnedWidgetCallbackIntent = new Intent( ... );
    
            // Configure the intent so that your app's broadcast receiver gets
            // the callback successfully. This callback receives the ID of the
            // newly-pinned widget (EXTRA_APPWIDGET_ID).
            PendingIntent successCallback = PendingIntent.getBroadcast(context, 0,
                    pinnedWidgetCallbackIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    
            appWidgetManager.requestPinAppWidget(myProvider, null, successCallback);
        }
        

Catatan: Jika aplikasi Anda tidak perlu diberi tahu tentang apakah sistem berhasil memasang pin pada widget ke peluncur yang didukung, Anda dapat meneruskan null sebagai argumen ketiga ke requestPinAppWidget().

Memuat Aktivitas Konfigurasi Widget Aplikasi

Jika Anda ingin pengguna mengonfigurasi setelan saat mereka menambahkan Widget Aplikasi baru, Anda dapat membuat Aktivitas konfigurasi Widget Aplikasi. Activity ini akan diluncurkan secara otomatis oleh host Widget Aplikasi dan memungkinkan pengguna mengonfigurasi setelan yang tersedia untuk Widget Aplikasi pada waktu pembuatan, seperti warna, ukuran, periode update, atau setelan fungsi lainnya.

Aktivitas konfigurasi seharusnya dideklarasikan sebagai Aktivitas normal di file manifes Android. Namun, Aktivitas tersebut akan diluncurkan oleh host Widget Aplikasi dengan tindakan ACTION_APPWIDGET_CONFIGURE, sehingga perlu menerima Intent ini. Contoh:

    <activity android:name=".ExampleAppWidgetConfigure">
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
        </intent-filter>
    </activity>
    

Selain itu, Aktivitas harus dideklarasikan di file XML AppWidgetProviderInfo dengan atribut android:configure (lihat Menambahkan Metadata AppWidgetProviderInfo di atas). Misalnya, Aktivitas konfigurasi dapat dideklarasikan seperti berikut:

    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
        ...
        android:configure="com.example.android.ExampleAppWidgetConfigure"
        ... >
    </appwidget-provider>
    

Perhatikan bahwa Aktivitas dideklarasikan dengan namespace yang sepenuhnya memenuhi syarat, karena akan direferensikan dari luar cakupan paket Anda.

Hanya ini yang Anda perlukan untuk memulai Aktivitas konfigurasi. Sekarang yang Anda perlukan adalah Aktivitas sebenarnya. Namun, ada dua hal penting yang perlu diingat saat Anda menerapkan Aktivitas:

  • Host Widget Aplikasi memanggil Aktivitas konfigurasi dan Aktivitas konfigurasi harus selalu menampilkan hasil. Hasil akan berisi ID Widget Aplikasi yang diteruskan oleh Intent yang meluncurkan Aktivitas (yang disimpan dalam tambahan Intent sebagai EXTRA_APPWIDGET_ID).
  • Metode onUpdate() tidak akan dipanggil saat Widget Aplikasi dibuat (sistem tidak akan mengirim siaran ACTION_APPWIDGET_UPDATE saat Aktivitas konfigurasi diluncurkan). Aktivitas konfigurasi bertanggung jawab untuk meminta update dari AppWidgetManager saat Widget Aplikasi pertama kali dibuat. Namun, onUpdate() akan dipanggil untuk update berikutnya—hanya dilewatkan pada update pertama.

Lihat cuplikan kode di bagian berikut untuk contoh cara menampilkan hasil konfigurasi dan mengupdate Widget Aplikasi.

Mengupdate Widget Aplikasi dari Aktivitas konfigurasi

Saat Widget Aplikasi menggunakan Aktivitas konfigurasi, tanggung jawab Aktivitas adalah mengupdate Widget Aplikasi saat konfigurasi selesai. Anda dapat melakukannya dengan meminta update secara langsung dari AppWidgetManager.

Berikut adalah ringkasan prosedur untuk mengupdate Widget Aplikasi dengan benar dan menutup Aktivitas konfigurasi:

  1. Pertama, dapatkan ID Widget Aplikasi dari Intent yang meluncurkan Aktivitas:

    Kotlin

        appWidgetId = intent?.extras?.getInt(
                AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID
        ) ?: AppWidgetManager.INVALID_APPWIDGET_ID
        

    Java

        Intent intent = getIntent();
        Bundle extras = intent.getExtras();
        if (extras != null) {
            appWidgetId = extras.getInt(
                    AppWidgetManager.EXTRA_APPWIDGET_ID,
                    AppWidgetManager.INVALID_APPWIDGET_ID);
        }
        
  2. Jalankan konfigurasi Widget Aplikasi.
  3. Setelah konfigurasi selesai, dapatkan instance AppWidgetManager dengan memanggil getInstance(Context):

    Kotlin

        val appWidgetManager: AppWidgetManager = AppWidgetManager.getInstance(context)
        

    Java

        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
        
  4. Update Widget Aplikasi menggunakan tata letak RemoteViews dengan memanggil updateAppWidget(int, RemoteViews):

    Kotlin

        RemoteViews(context.packageName, R.layout.example_appwidget).also { views->
            appWidgetManager.updateAppWidget(appWidgetId, views)
        }
        

    Java

        RemoteViews views = new RemoteViews(context.getPackageName(),
        R.layout.example_appwidget);
        appWidgetManager.updateAppWidget(appWidgetId, views);
        
  5. Terakhir, buat Intent hasil, lalu setel dengan hasil Aktivitas, dan selesaikan Aktivitas:

    Kotlin

        val resultValue = Intent().apply {
            putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
        }
        setResult(Activity.RESULT_OK, resultValue)
        finish()
        

    Java

        Intent resultValue = new Intent();
        resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
        setResult(RESULT_OK, resultValue);
        finish();
        

Tips: Saat Aktivitas konfigurasi Anda pertama kali dibuka, setel hasil Aktivitas ke RESULT_CANCELED, bersama dengan EXTRA_APPWIDGET_ID, seperti yang ditunjukkan dalam langkah ke-5 di atas. Dengan begitu, jika pengguna keluar dari Aktivitas sebelum berakhir, host Widget Aplikasi mendapatkan notifikasi bahwa konfigurasi telah dibatalkan dan Widget Aplikasi tidak akan ditambahkan.

Lihat contoh class ExampleAppWidgetConfigure.java dalam ApiDemos sebagai contoh.

Menyetel Gambar Pratinjau

Android 3.0 memperkenalkan kolom previewImage, yang menentukan pratinjau tampilan widget aplikasi tersebut. Pratinjau ini ditampilkan kepada pengguna dari alat pilih widget. Jika kolom tidak didukung, ikon widget aplikasi akan digunakan untuk pratinjau.

Berikut adalah cara menetapkan setelan ini di XML:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
      ...
      android:previewImage="@drawable/preview">
    </appwidget-provider>

Untuk membantu membuat gambar pratinjau untuk widget aplikasi Anda (yang akan ditetapkan di kolom previewImage), Android Emulator dilengkapi dengan aplikasi yang bernama "Widget Preview." Untuk membuat gambar pratinjau, luncurkan aplikasi ini, pilih widget aplikasi untuk aplikasi Anda, lalu atur tampilan gambar pratinjau yang diinginkan, kemudian simpan dan tempatkan pada resource aplikasi yang dapat digambar.

Menggunakan Widget Aplikasi dengan Koleksi.

Android 3.0 memperkenalkan widget aplikasi dengan koleksi. Jenis Widget Aplikasi ini menggunakan RemoteViewsService untuk menampilkan koleksi yang didukung oleh data jarak jauh, seperti dari penyedia konten. Data yang disediakan oleh RemoteViewsService ditampilkan pada widget aplikasi menggunakan salah satu jenis tampilan berikut, yang akan disebut sebagai "tampilan koleksi":

ListView
Tampilan yang menunjukkan item dalam daftar scroll vertikal. Misalnya, lihat widget aplikasi Gmail.
GridView
Tampilan yang menunjukkan item dalam petak scroll dua dimensi. Misalnya, lihat widget aplikasi Bookmark.
StackView
Tampilan kartu yang ditumpuk (kurang lebih seperti rolodex), tempat pengguna dapat menjentikkan kartu depan ke atas/bawah untuk melihat kartu sebelum/selanjutnya berturut-turut. Contohnya seperti widget aplikasi YouTube dan Buku.
AdapterViewFlipper
ViewAnimator sederhana yang didukung adaptor yang memberi animasi di antara dua tampilan atau lebih. Hanya satu turunan yang akan ditampilkan pada satu waktu.

Seperti yang dijelaskan di atas, tampilan koleksi ini menampilkan koleksi yang didukung oleh data jarak jauh. Hal ini berarti tampilan tersebut menggunakan Adapter untuk mengikat antarmuka pengguna ke data. Adapter mengikat masing-masing item dari kumpulan data ke masing-masing objek View. Karena tampilan koleksi ini didukung oleh adaptor, framework Android harus menyertakan arsitektur tambahan untuk mendukung penggunaannya dalam widget aplikasi. Dalam konteks widget aplikasi, Adapter digantikan oleh RemoteViewsFactory, yang merupakan wrapper tipis di seluruh antarmuka Adapter. Saat item tertentu diminta dari koleksi, RemoteViewsFactory membuat dan menampilkan item ke koleksi sebagai objek RemoteViews. Untuk menyertakan tampilan koleksi ke widget aplikasi, Anda harus menerapkan RemoteViewsService dan RemoteViewsFactory.

RemoteViewsService adalah layanan yang memungkinkan adaptor jarak jauh meminta objek RemoteViews. RemoteViewsFactory adalah antarmuka untuk adaptor di antara tampilan koleksi (seperti ListView, GridView, dan lainnya) dan data yang mendasari untuk tampilan tersebut. Dari contoh StackWidget, berikut adalah contoh kode boilerplate yang dapat digunakan untuk menerapkan layanan dan antarmuka ini:

Kotlin

    class StackWidgetService : RemoteViewsService() {

        override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
            return StackRemoteViewsFactory(this.applicationContext, intent)
        }
    }

    class StackRemoteViewsFactory(
            private val context: Context,
            intent: Intent
    ) : RemoteViewsService.RemoteViewsFactory {

    //... include adapter-like methods here. See the StackView Widget sample.

    }
    

Java

    public class StackWidgetService extends RemoteViewsService {
        @Override
        public RemoteViewsFactory onGetViewFactory(Intent intent) {
            return new StackRemoteViewsFactory(this.getApplicationContext(), intent);
        }
    }

    class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {

    //... include adapter-like methods here. See the StackView Widget sample.

    }
    

Contoh aplikasi

Nukilan kode dalam bagian ini diambil dari contoh StackWidget:

Contoh ini terdiri dari tumpukan 10 tampilan yang menampilkan nilai "0!" melalui "9!". Contoh widget aplikasi memiliki perilaku utama berikut:

  • Pengguna dapat melempar tampilan atas secara vertikal di widget aplikasi untuk membuka tampilan selanjutnya atau sebelumnya. Ini adalah perilaku StackView bawaan.
  • Tanpa adanya interaksi pengguna, widget aplikasi akan secara otomatis membuka tampilan berikutnya secara berurutan, seperti slide show. Perilaku ini disebabkan oleh setelan android:autoAdvanceViewId="@id/stack_view" dalam file res/xml/stackwidgetinfo.xml. Setelan ini berlaku untuk ID tampilan, yang pada kasus ini merupakan ID tampilan dari tampilan bertumpuk.
  • Jika pengguna menyentuh tampilan atas, widget aplikasi menampilkan pesan Toast "Tampilan tersentuh n," dengan n adalah indeks (posisi) tampilan tersentuh. Untuk pembahasan lebih lanjut tentang cara penerapannya, lihat Menambahkan perilaku ke masing-masing item.

Menerapkan widget aplikasi dengan koleksi

Untuk menerapkan widget aplikasi dengan koleksi, ikuti langkah-langkah dasar yang sama seperti yang Anda gunakan untuk menerapkan widget aplikasi lain. Bagian berikut menjelaskan langkah tambahan yang perlu dilakukan untuk menerapkan widget aplikasi dengan koleksi.

Manifes untuk widget aplikasi dengan koleksi

Selain persyaratan yang tercantum dalam Mendeklarasikan widget aplikasi di Manifes, untuk memungkinkan widget aplikasi dengan koleksi mengikat ke RemoteViewsService, Anda harus mendeklarasikan layanan dalam file manifes Anda dengan BIND_REMOTEVIEWS izin. Tindakan ini akan mencegah aplikasi mengakses data widget aplikasi Anda secara bebas. Misalnya, saat membuat Widget Aplikasi yang menggunakan RemoteViewsService untuk mengisi tampilan koleksi, entri manifes akan terlihat seperti ini:

<service android:name="MyWidgetService"
    ...
    android:permission="android.permission.BIND_REMOTEVIEWS" />

Baris android:name="MyWidgetService" mengacu pada subclass RemoteViewsService Anda.

Tata letak untuk widget aplikasi dengan koleksi

Persyaratan utama untuk file XML tata letak widget aplikasi Anda adalah berisi salah satu tampilan koleksi: ListView, GridView, StackView, atau AdapterViewFlipper. Berikut adalah widget_layout.xml untuk contoh StackWidget:

<?xml version="1.0" encoding="utf-8"?>

    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <StackView xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/stack_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:loopViews="true" />
        <TextView xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/empty_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:background="@drawable/widget_item_background"
            android:textColor="#ffffff"
            android:textStyle="bold"
            android:text="@string/empty_view_text"
            android:textSize="20sp" />
    </FrameLayout>

Perlu diingat bahwa tampilan kosong harus berhubungan dengan tampilan koleksi yang memiliki tampilan kosong yang mewakili status kosong.

Selain file tata letak untuk seluruh widget aplikasi, Anda harus membuat file tata letak lain yang menentukan tata letak setiap item dalam koleksi (misalnya, tata letak untuk setiap buku dalam koleksi buku). Contoh StackWidget hanya memiliki satu file tata letak, widget_item.xml, karena semua item menggunakan tata letak yang sama.

Class AppWidgetProvider untuk widget aplikasi dengan koleksi

Seperti widget aplikasi biasa, sebagian besar kode dalam subclass AppWidgetProvider biasanya muncul di onUpdate(). Perbedaan besar pada penerapan untuk onUpdate() saat membuat widget aplikasi dengan koleksi adalah Anda harus memanggil setRemoteAdapter(). Tindakan ini akan memberi tahu tampilan koleksi tempat untuk mendapatkan datanya. Kemudian, RemoteViewsService dapat menampilkan penerapan RemoteViewsFactory Anda, dan widget dapat menyajikan data yang sesuai. Saat memanggil metode ini, Anda harus meneruskan intent yang mengarah ke penerapan RemoteViewsService dan ID widget aplikasi yang menentukan widget aplikasi yang akan diupdate.

Misalnya, berikut adalah cara contoh StackWidget menerapkan metode callback onUpdate() untuk menetapkan RemoteViewsService sebagai adaptor jarak jauh untuk koleksi widget aplikasi:

Kotlin

    override fun onUpdate(
            context: Context,
            appWidgetManager: AppWidgetManager,
            appWidgetIds: IntArray
    ) {
        // update each of the app widgets with the remote adapter
        appWidgetIds.forEach { appWidgetId ->

            // Set up the intent that starts the StackViewService, which will
            // provide the views for this collection.
            val intent = Intent(context, StackWidgetService::class.java).apply {
                // Add the app widget ID to the intent extras.
                putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
                data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
            }
            // Instantiate the RemoteViews object for the app widget layout.
            val rv = RemoteViews(context.packageName, R.layout.widget_layout).apply {
                // Set up the RemoteViews object to use a RemoteViews adapter.
                // This adapter connects
                // to a RemoteViewsService  through the specified intent.
                // This is how you populate the data.
                setRemoteAdapter(R.id.stack_view, intent)

                // The empty view is displayed when the collection has no items.
                // It should be in the same layout used to instantiate the RemoteViews
                // object above.
                setEmptyView(R.id.stack_view, R.id.empty_view)
            }

            //
            // Do additional processing specific to this app widget...
            //

            appWidgetManager.updateAppWidget(appWidgetId, rv)
        }
        super.onUpdate(context, appWidgetManager, appWidgetIds)
    }
    

Java

    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
    int[] appWidgetIds) {
        // update each of the app widgets with the remote adapter
        for (int i = 0; i < appWidgetIds.length; ++i) {

            // Set up the intent that starts the StackViewService, which will
            // provide the views for this collection.
            Intent intent = new Intent(context, StackWidgetService.class);
            // Add the app widget ID to the intent extras.
            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
            intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
            // Instantiate the RemoteViews object for the app widget layout.
            RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
            // Set up the RemoteViews object to use a RemoteViews adapter.
            // This adapter connects
            // to a RemoteViewsService  through the specified intent.
            // This is how you populate the data.
            rv.setRemoteAdapter(R.id.stack_view, intent);

            // The empty view is displayed when the collection has no items.
            // It should be in the same layout used to instantiate the RemoteViews
            // object above.
            rv.setEmptyView(R.id.stack_view, R.id.empty_view);

            //
            // Do additional processing specific to this app widget...
            //

            appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
        }
        super.onUpdate(context, appWidgetManager, appWidgetIds);
    }
    

Class RemoteViewsService

Mempertahankan data

Seperti yang dijelaskan di atas, subclass RemoteViewsService Anda menyediakan RemoteViewsFactory yang digunakan untuk mengisi tampilan koleksi jarak jauh.

Secara khusus, Anda perlu melakukan langkah-langkah berikut:

  1. Subclass RemoteViewsService. RemoteViewsService adalah layanan yang dapat digunakan adaptor jarak jauh untuk membuat permintaan RemoteViews.
  2. Dalam subclass RemoteViewsService, sertakan class yang menerapkan antarmuka RemoteViewsFactory. RemoteViewsFactory adalah antarmuka untuk adaptor antar tampilan koleksi jarak (seperti ListView, GridView, dan lainnya) dan data yang mendasari untuk tampilan tersebut. Penerapan Anda bertujuan untuk membuat objek RemoteViews bagi setiap item dalam set data. Antarmuka ini adalah wrapper tipis di seluruh Adapter.

Anda tidak dapat bergantung pada satu instance layanan, atau data apa pun yang ditampung, untuk dipertahankan. Oleh karena itu, sebaiknya Anda tidak menyimpan data dalam RemoteViewsService (kecuali jika data bersifat statis). Jika ingin mempertahankan data widget aplikasi, cara terbaik adalah menggunakan ContentProvider yang datanya bertahan melalui siklus hidup proses.

Konten utama penerapan RemoteViewsService adalah RemoteViewsFactory, yang dijelaskan di bawah ini.

Antarmuka RemoteViewFactory

Class kustom Anda yang menerapkan antarmuka RemoteViewsFactory memberikan data kepada widget aplikasi untuk item dalam koleksinya. Untuk melakukannya, class ini menggabungkan file tata letak XML item widget aplikasi Anda dengan sumber data. Sumber data ini dapat berupa apa saja, dari database hingga array sederhana. Dalam contoh StackWidget, sumber data merupakan array WidgetItems. RemoteViewsFactory berfungsi sebagai adaptor untuk menyatukan data pada tampilan koleksi jarak jauh.

Dua metode terpenting yang perlu diterapkan untuk subclass RemoteViewsFactory adalah onCreate() dan getViewAt() .

Sistem memanggil onCreate() saat membuat pabrik untuk pertama kalinya. Pada tahap ini, Anda perlu menyiapkan koneksi dan/atau kursor untuk sumber data. Misalnya, contoh StackWidget menggunakan onCreate() untuk menginisialisasi array objek WidgetItem. Saat widget aplikasi aktif, sistem mengakses objek ini menggunakan posisi indeks objek dalam array dan teks di dalamnya akan ditampilkan.

Berikut adalah nukilan dari penerapan RemoteViewsFactory contoh StackWidget yang menunjukkan sebagian metode onCreate():

Kotlin

    private const val REMOTE_VIEW_COUNT: Int = 10

    class StackRemoteViewsFactory(
            private val context: Context
    ) : RemoteViewsService.RemoteViewsFactory {

        private lateinit var widgetItems: List<WidgetItem>

        override fun onCreate() {
            // In onCreate() you setup any connections / cursors to your data source. Heavy lifting,
            // for example downloading or creating content etc, should be deferred to onDataSetChanged()
            // or getViewAt(). Taking more than 20 seconds in this call will result in an ANR.
            widgetItems = List(REMOTE_VIEW_COUNT) { index -> WidgetItem("$index!") }
            ...
        }
        ...
    }
    

Java

    class StackRemoteViewsFactory implements
    RemoteViewsService.RemoteViewsFactory {
        private static final int count = 10;
        private List<WidgetItem> widgetItems = new ArrayList<WidgetItem>();
        private Context context;
        private int appWidgetId;

        public StackRemoteViewsFactory(Context context, Intent intent) {
            this.context = context;
            appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                    AppWidgetManager.INVALID_APPWIDGET_ID);
        }

        public void onCreate() {
            // In onCreate() you setup any connections / cursors to your data source. Heavy lifting,
            // for example downloading or creating content etc, should be deferred to onDataSetChanged()
            // or getViewAt(). Taking more than 20 seconds in this call will result in an ANR.
            for (int i = 0; i < count; i++) {
                widgetItems.add(new WidgetItem(i + "!"));
            }
            ...
        }
    ...
    

Metode RemoteViewsFactory getViewAt() menampilkan objek RemoteViews yang sesuai dengan data pada position khusus dalam set data. Berikut adalah nukilan dari penerapan RemoteViewsFactory contoh StackWidget:

Kotlin

    override fun getViewAt(position: Int): RemoteViews {
        // Construct a remote views item based on the app widget item XML file,
        // and set the text based on the position.
        return RemoteViews(context.packageName, R.layout.widget_item).apply {
            setTextViewText(R.id.widget_item, widgetItems[position].text)
        }
    }
    

Java

    public RemoteViews getViewAt(int position) {

        // Construct a remote views item based on the app widget item XML file,
        // and set the text based on the position.
        RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_item);
        rv.setTextViewText(R.id.widget_item, widgetItems.get(position).text);

        ...
        // Return the remote views object.
        return rv;
    }
    

Menambahkan perilaku pada masing-masing item

Bagian di atas menunjukkan cara mengikat data ke koleksi widget aplikasi. Namun, bagaimana jika Anda ingin menambahkan perilaku dinamis ke masing-masing item dalam tampilan koleksi?

Seperti yang dijelaskan dalam Menggunakan Class AppWidgetProvider , biasanya Anda menggunakan setOnClickPendingIntent() untuk menyetel perilaku klik objek—seperti untuk membuat tombol meluncurkan Activity. Namun, cara ini tidak diizinkan untuk tampilan turunan dalam masing-masing item koleksi (untuk memperjelas, Anda dapat menggunakan setOnClickPendingIntent() untuk menyiapkan tombol global dalam widget aplikasi Gmail yang meluncurkan aplikasinya, tetapi tidak pada masing-masing item daftar). Untuk menambahkan perilaku klik pada masing-masing item dalam koleksi, Anda dapat menggunakan setOnClickFillInIntent(). Dengan kata lain, Anda harus menyiapkan tata letak intent yang tertunda untuk tampilan koleksi, lalu menyetel intent pengganti pada setiap item dalam koleksi melalui RemoteViewsFactory.

Bagian ini menggunakan contoh StackWidget untuk menjelaskan cara menambahkan perilaku pada masing-masing item. Dalam contoh StackWidget, jika pengguna menyentuh tampilan atas, widget aplikasi menampilkan pesan Toast "Tampilan tersentuh n," dengan n adalah indeks (posisi) tampilan tersentuh. Begini caranya:

  • StackWidgetProvider (subclass AppWidgetProvider) membuat intent tertunda yang memiliki tindakan kustom bernama TOAST_ACTION.
  • Saat pengguna menyentuh tampilan, intent akan diaktifkan dan mulai menyiarkan TOAST_ACTION.
  • Siaran ini ditangkap oleh metode onReceive() dari StackWidgetProvider, dan widget aplikasi menampilkan pesan Toast untuk tampilan tersentuh. Data untuk item koleksi diberikan oleh RemoteViewsFactory, melalui RemoteViewsService.

Catatan: Contoh StackWidget menggunakan siaran, tetapi biasanya widget aplikasi hanya akan meluncurkan aktivitas dalam skenario seperti ini.

Menyiapkan template intent yang tertunda

StackWidgetProvider (subclass AppWidgetProvider) menyiapkan intent yang tertunda. Masing-masing item koleksi tidak dapat menyiapkan intent tertunda sendiri. Sebaliknya, koleksi secara keseluruhan dapat menyiapkan template intent yang tertunda, dan masing-masing item menyetel intent pengganti untuk membuat perilaku unik per item.

Class ini juga menerima siaran yang dikirim saat pengguna menyentuh tampilan. Hal ini memproses peristiwa ini pada onReceive() metodenya. Jika tindakan intent adalah TOAST_ACTION, widget aplikasi menampilkan pesan Toast untuk tampilan saat ini.

Kotlin

    const val TOAST_ACTION = "com.example.android.stackwidget.TOAST_ACTION"
    const val EXTRA_ITEM = "com.example.android.stackwidget.EXTRA_ITEM"

    class StackWidgetProvider : AppWidgetProvider() {

        ...

        // Called when the BroadcastReceiver receives an Intent broadcast.
        // Checks to see whether the intent's action is TOAST_ACTION. If it is, the app widget
        // displays a Toast message for the current item.
        override fun onReceive(context: Context, intent: Intent) {
            val mgr: AppWidgetManager = AppWidgetManager.getInstance(context)
            if (intent.action == TOAST_ACTION) {
                val appWidgetId: Int = intent.getIntExtra(
                        AppWidgetManager.EXTRA_APPWIDGET_ID,
                        AppWidgetManager.INVALID_APPWIDGET_ID
                )
                val viewIndex: Int = intent.getIntExtra(EXTRA_ITEM, 0)
                Toast.makeText(context, "Touched view $viewIndex", Toast.LENGTH_SHORT).show()
            }
            super.onReceive(context, intent)
        }

        override fun onUpdate(
                context: Context,
                appWidgetManager: AppWidgetManager,
                appWidgetIds: IntArray
        ) {
            // update each of the app widgets with the remote adapter
            appWidgetIds.forEach { appWidgetId ->

                // Sets up the intent that points to the StackViewService that will
                // provide the views for this collection.
                val intent = Intent(context, StackWidgetService::class.java).apply {
                    putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
                    // When intents are compared, the extras are ignored, so we need to embed the extras
                    // into the data so that the extras will not be ignored.
                    data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
                }
                val rv = RemoteViews(context.packageName, R.layout.widget_layout).apply {
                    setRemoteAdapter(R.id.stack_view, intent)

                    // The empty view is displayed when the collection has no items. It should be a
                    // sibling of the collection view.
                    setEmptyView(R.id.stack_view, R.id.empty_view)
                }

                // This section makes it possible for items to have individualized behavior.
                // It does this by setting up a pending intent template. Individuals items of a
                // collection cannot set up their own pending intents. Instead, the collection as a
                // whole sets up a pending intent template, and the individual items set a fillInIntent
                // to create unique behavior on an item-by-item basis.
                val toastPendingIntent: PendingIntent = Intent(
                        context,
                        StackWidgetProvider::class.java
                ).run {
                    // Set the action for the intent.
                    // When the user touches a particular view, it will have the effect of
                    // broadcasting TOAST_ACTION.
                    action = TOAST_ACTION
                    putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
                    data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))

                    PendingIntent.getBroadcast(context, 0, this, PendingIntent.FLAG_UPDATE_CURRENT)
                }
                rv.setPendingIntentTemplate(R.id.stack_view, toastPendingIntent)

                appWidgetManager.updateAppWidget(appWidgetId, rv)
            }
            super.onUpdate(context, appWidgetManager, appWidgetIds)
        }
    }
    

Java

    public class StackWidgetProvider extends AppWidgetProvider {
        public static final String TOAST_ACTION = "com.example.android.stackwidget.TOAST_ACTION";
        public static final String EXTRA_ITEM = "com.example.android.stackwidget.EXTRA_ITEM";

        ...

        // Called when the BroadcastReceiver receives an Intent broadcast.
        // Checks to see whether the intent's action is TOAST_ACTION. If it is, the app widget
        // displays a Toast message for the current item.
        @Override
        public void onReceive(Context context, Intent intent) {
            AppWidgetManager mgr = AppWidgetManager.getInstance(context);
            if (intent.getAction().equals(TOAST_ACTION)) {
                int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                    AppWidgetManager.INVALID_APPWIDGET_ID);
                int viewIndex = intent.getIntExtra(EXTRA_ITEM, 0);
                Toast.makeText(context, "Touched view " + viewIndex, Toast.LENGTH_SHORT).show();
            }
            super.onReceive(context, intent);
        }

        @Override
        public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
            // update each of the app widgets with the remote adapter
            for (int i = 0; i < appWidgetIds.length; ++i) {

                // Sets up the intent that points to the StackViewService that will
                // provide the views for this collection.
                Intent intent = new Intent(context, StackWidgetService.class);
                intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
                // When intents are compared, the extras are ignored, so we need to embed the extras
                // into the data so that the extras will not be ignored.
                intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
                RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
                rv.setRemoteAdapter(appWidgetIds[i], R.id.stack_view, intent);

                // The empty view is displayed when the collection has no items. It should be a sibling
                // of the collection view.
                rv.setEmptyView(R.id.stack_view, R.id.empty_view);

                // This section makes it possible for items to have individualized behavior.
                // It does this by setting up a pending intent template. Individuals items of a collection
                // cannot set up their own pending intents. Instead, the collection as a whole sets
                // up a pending intent template, and the individual items set a fillInIntent
                // to create unique behavior on an item-by-item basis.
                Intent toastIntent = new Intent(context, StackWidgetProvider.class);
                // Set the action for the intent.
                // When the user touches a particular view, it will have the effect of
                // broadcasting TOAST_ACTION.
                toastIntent.setAction(StackWidgetProvider.TOAST_ACTION);
                toastIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
                intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
                PendingIntent toastPendingIntent = PendingIntent.getBroadcast(context, 0, toastIntent,
                    PendingIntent.FLAG_UPDATE_CURRENT);
                rv.setPendingIntentTemplate(R.id.stack_view, toastPendingIntent);

                appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
            }
        super.onUpdate(context, appWidgetManager, appWidgetIds);
        }
    }
    
Menyetel Intent pengganti

RemoteViewsFactory harus menyetel intent pengganti pada setiap item dalam koleksi. Hal ini memungkinkan untuk memisahkan masing-masing tindakan klik dari item tersebut. Kemudian, intent pengganti digabungkan dengan template PendingIntent untuk menentukan intent akhir yang akan dijalankan saat item diklik.

Kotlin

    private const val REMOTE_VIEW_COUNT: Int = 10

    class StackRemoteViewsFactory(
            private val context: Context,
            intent: Intent
    ) : RemoteViewsService.RemoteViewsFactory {

        private lateinit var widgetItems: List<WidgetItem>
        private val appWidgetId: Int = intent.getIntExtra(
                AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID
        )

        override fun onCreate() {
            // In onCreate() you setup any connections / cursors to your data source. Heavy lifting,
            // for example downloading or creating content etc, should be deferred to onDataSetChanged()
            // or getViewAt(). Taking more than 20 seconds in this call will result in an ANR.
            widgetItems = List(REMOTE_VIEW_COUNT) { index -> WidgetItem("$index!") }
            ...
        }
        ...

        override fun getViewAt(position: Int): RemoteViews {
            // Construct a remote views item based on the app widget item XML file,
            // and set the text based on the position.
            return RemoteViews(context.packageName, R.layout.widget_item).apply {
                setTextViewText(R.id.widget_item, widgetItems[position].text)

                // Next, set a fill-intent, which will be used to fill in the pending intent template
                // that is set on the collection view in StackWidgetProvider.
                val fillInIntent = Intent().apply {
                    Bundle().also { extras ->
                        extras.putInt(EXTRA_ITEM, position)
                        putExtras(extras)
                    }
                }
                // Make it possible to distinguish the individual on-click
                // action of a given item
                setOnClickFillInIntent(R.id.widget_item, fillInIntent)
                ...
            }
        }
        ...
    }
    

Java

    public class StackWidgetService extends RemoteViewsService {
        @Override
        public RemoteViewsFactory onGetViewFactory(Intent intent) {
            return new StackRemoteViewsFactory(this.getApplicationContext(), intent);
        }
    }

    class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
        private static final int count = 10;
        private List<WidgetItem> widgetItems = new ArrayList<WidgetItem>();
        private Context context;
        private int appWidgetId;

        public StackRemoteViewsFactory(Context context, Intent intent) {
            this.context = context;
            appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                    AppWidgetManager.INVALID_APPWIDGET_ID);
        }

        // Initialize the data set.
            public void onCreate() {
                // In onCreate() you set up any connections / cursors to your data source. Heavy lifting,
                // for example downloading or creating content etc, should be deferred to onDataSetChanged()
                // or getViewAt(). Taking more than 20 seconds in this call will result in an ANR.
                for (int i = 0; i < count; i++) {
                    widgetItems.add(new WidgetItem(i + "!"));
                }
               ...
            }
            ...

            // Given the position (index) of a WidgetItem in the array, use the item's text value in
            // combination with the app widget item XML file to construct a RemoteViews object.
            public RemoteViews getViewAt(int position) {
                // position will always range from 0 to getCount() - 1.

                // Construct a RemoteViews item based on the app widget item XML file, and set the
                // text based on the position.
                RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_item);
                rv.setTextViewText(R.id.widget_item, widgetItems.get(position).text);

                // Next, set a fill-intent, which will be used to fill in the pending intent template
                // that is set on the collection view in StackWidgetProvider.
                Bundle extras = new Bundle();
                extras.putInt(StackWidgetProvider.EXTRA_ITEM, position);
                Intent fillInIntent = new Intent();
                fillInIntent.putExtras(extras);
                // Make it possible to distinguish the individual on-click
                // action of a given item
                rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent);

                ...

                // Return the RemoteViews object.
                return rv;
            }
        ...
        }
    

Terus Memperbarui Data Koleksi

Gambar berikut menunjukkan alur yang terjadi dalam widget aplikasi yang menggunakan koleksi saat update dilakukan. Hal ini menunjukkan cara kode widget aplikasi berinteraksi dengan RemoteViewsFactory, dan cara Anda dapat memicu update:

Salah satu fitur widget aplikasi yang menggunakan koleksi adalah opsi untuk menyediakan konten terbaru bagi pengguna. Misalnya, perhatikan widget aplikasi Gmail Android 3.0, yang memberikan ringkasan kotak masuk kepada pengguna. Untuk melakukannya, Anda harus dapat memicu RemoteViewsFactory dan tampilan koleksi untuk mendapatkan dan menampilkan data baru. Anda dapat melakukannya dengan panggilan AppWidgetManager notifyAppWidgetViewDataChanged(). Panggilan ini menampilkan callback pada metode onDataSetChanged() dari RemoteViewsFactory, yang memberi Anda peluang untuk mendapatkan data baru. Perlu diingat bahwa Anda dapat menjalankan operasi pemrosesan intensif secara sinkron dalam callback onDataSetChanged(). Dipastikan bahwa panggilan ini akan selesai sebelum metadata atau data tampilan diambil dari RemoteViewsFactory. Selain itu, Anda dapat menjalankan operasi pemrosesan intensif dalam metode getViewAt(). Jika panggilan ini perlu waktu lama, tampilan yang dimuat (yang ditetapkan oleh metode getLoadingView() dari RemoteViewsFactory) akan ditampilkan dalam posisi yang sesuai dengan tampilan koleksi hingga kembali.