Menyediakan tata letak widget yang fleksibel

Halaman ini menjelaskan perbaikan untuk ukuran widget dan fleksibilitas yang lebih besar diperkenalkan di Android 12 (level API 31). Artikel ini juga merinci cara menentukan ukuran untuk widget Anda.

Menggunakan API yang dioptimalkan untuk ukuran dan tata letak widget

Mulai Android 12 (level API 31), Anda dapat memberikan ukuran yang lebih disempurnakan dan tata letak yang fleksibel dengan melakukan hal berikut, seperti dijelaskan dalam bagian yang mengikuti:

  1. Tentukan batasan ukuran widget tambahan.

  2. Menyediakan tata letak responsif atau tata letak yang tepat tata letak.

Pada versi Android sebelumnya, kita bisa mendapatkan rentang ukuran menggunakan OPTION_APPWIDGET_MIN_WIDTH, OPTION_APPWIDGET_MIN_HEIGHT, OPTION_APPWIDGET_MAX_WIDTH, dan OPTION_APPWIDGET_MAX_HEIGHT tambahan, lalu memperkirakan ukuran widget, tetapi logika tersebut tidak berfungsi pada semua situasi. Untuk widget yang menargetkan Android 12 atau yang lebih baru, sebaiknya memberikan respons responsif atau persis tata letak.

Menentukan batasan ukuran widget tambahan

Android 12 menambahkan API yang memungkinkan Anda memastikan widget berukuran lebih andal di berbagai perangkat dengan berbagai ukuran layar.

Selain minWidth yang ada, minHeight, minResizeWidth, dan minResizeHeight atribut, gunakan atribut baru appwidget-provider berikut:

  • targetCellWidth dan targetCellHeight: menentukan ukuran target widget dalam hal sel petak peluncur. Jika ditentukan, atribut ini digunakan sebagai ganti minWidth atau minHeight.

  • maxResizeWidth dan maxResizeHeight: menentukan ukuran maksimum peluncur yang dapat digunakan pengguna untuk mengubah ukuran widget.

XML berikut menunjukkan cara menggunakan atribut ukuran.

<appwidget-provider
  ...
  android:targetCellWidth="3"
  android:targetCellHeight="2"
  android:maxResizeWidth="250dp"
  android:maxResizeHeight="110dp">
</appwidget-provider>

Menyediakan tata letak yang responsif

Jika tata letak perlu diubah sesuai ukuran widget, sebaiknya buat set tata letak kecil yang masing-masing valid untuk berbagai ukuran. Jika ini tidak memungkinkan, opsi lainnya adalah menyediakan tata letak berdasarkan widget yang tepat ukuran saat runtime, seperti yang dijelaskan di halaman ini.

Fitur ini memungkinkan penskalaan yang lebih lancar dan sistem yang lebih baik secara keseluruhan kesehatan, karena sistem tidak perlu mengaktifkan aplikasi setiap kali widget akan ditampilkan dalam ukuran yang berbeda.

Contoh kode berikut menunjukkan cara memberikan daftar tata letak.

Kotlin

override fun onUpdate(...) {
    val smallView = ...
    val tallView = ...
    val wideView = ...

    val viewMapping: Map<SizeF, RemoteViews> = mapOf(
            SizeF(150f, 100f) to smallView,
            SizeF(150f, 200f) to tallView,
            SizeF(215f, 100f) to wideView
    )
    val remoteViews = RemoteViews(viewMapping)

    appWidgetManager.updateAppWidget(id, remoteViews)
}

Java

@Override
public void onUpdate(...) {
    RemoteViews smallView = ...;
    RemoteViews tallView = ...;
    RemoteViews wideView = ...;

    Map<SizeF, RemoteViews> viewMapping = new ArrayMap<>();
    viewMapping.put(new SizeF(150f, 100f), smallView);
    viewMapping.put(new SizeF(150f, 200f), tallView);
    viewMapping.put(new SizeF(215f, 100f), wideView);
    RemoteViews remoteViews = new RemoteViews(viewMapping);

    appWidgetManager.updateAppWidget(id, remoteViews);
}

Asumsikan bahwa widget memiliki atribut berikut:

<appwidget-provider
    android:minResizeWidth="160dp"
    android:minResizeHeight="110dp"
    android:maxResizeWidth="250dp"
    android:maxResizeHeight="200dp">
</appwidget-provider>

Cuplikan kode sebelumnya berarti sebagai berikut:

  • smallView mendukung dari 160 dp (minResizeWidth) × 110 dp (minResizeHeight) hingga 160dp × 199dp (titik potong berikutnya - 1dp).
  • tallView mendukung dari 160dp × 200dp hingga 214dp (titik potong berikutnya - 1) × 200dp.
  • wideView mendukung dari 215 dp × 110 dp (minResizeHeight) hingga 250 dp (maxResizeWidth) × 200dp (maxResizeHeight).

Widget Anda harus mendukung rentang ukuran mulai dari minResizeWidth × minResizeHeight hingga maxResizeWidth × maxResizeHeight. Dalam rentang itu, Anda dapat menentukan titik potong untuk mengganti tata letak.

Contoh tata letak responsif
Gambar 1. Contoh tata letak responsif.

Menyediakan tata letak yang tepat

Jika kumpulan kecil tata letak responsif tidak memungkinkan, Anda dapat memberikan tata letak yang berbeda dan telah disesuaikan dengan ukuran widget yang ditampilkan tersebut. Ini biasanya berupa dua ukuran untuk ponsel (mode potret dan lanskap) dan empat ukuran untuk perangkat foldable.

Untuk menerapkan solusi ini, aplikasi Anda harus melakukan langkah-langkah berikut:

  1. Kelebihan AppWidgetProvider.onAppWidgetOptionsChanged() yang dipanggil saat kumpulan ukuran berubah.

  2. Memanggil AppWidgetManager.getAppWidgetOptions() yang menampilkan Bundle yang berisi ukuran.

  3. Mengakses kunci AppWidgetManager.OPTION_APPWIDGET_SIZES dari Bundle.

Contoh kode berikut menunjukkan cara menyediakan tata letak yang persis.

Kotlin

override fun onAppWidgetOptionsChanged(
        context: Context,
        appWidgetManager: AppWidgetManager,
        id: Int,
        newOptions: Bundle?
) {
    super.onAppWidgetOptionsChanged(context, appWidgetManager, id, newOptions)
    // Get the new sizes.
    val sizes = newOptions?.getParcelableArrayList<SizeF>(
            AppWidgetManager.OPTION_APPWIDGET_SIZES
    )
    // Check that the list of sizes is provided by the launcher.
    if (sizes.isNullOrEmpty()) {
        return
    }
    // Map the sizes to the RemoteViews that you want.
    val remoteViews = RemoteViews(sizes.associateWith(::createRemoteViews))
    appWidgetManager.updateAppWidget(id, remoteViews)
}

// Create the RemoteViews for the given size.
private fun createRemoteViews(size: SizeF): RemoteViews { }

Java

@Override
public void onAppWidgetOptionsChanged(
    Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
    super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
    // Get the new sizes.
    ArrayList<SizeF> sizes =
        newOptions.getParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES);
    // Check that the list of sizes is provided by the launcher.
    if (sizes == null || sizes.isEmpty()) {
      return;
    }
    // Map the sizes to the RemoteViews that you want.
    Map<SizeF, RemoteViews> viewMapping = new ArrayMap<>();
    for (SizeF size : sizes) {
        viewMapping.put(size, createRemoteViews(size));
    }
    RemoteViews remoteViews = new RemoteViews(viewMapping);
    appWidgetManager.updateAppWidget(id, remoteViews);
}

// Create the RemoteViews for the given size.
private RemoteViews createRemoteViews(SizeF size) { }

Menentukan ukuran untuk widget Anda

Setiap widget harus menentukan targetCellWidth dan targetCellHeight untuk perangkat menjalankan Android 12 atau yang lebih tinggi—atau minWidth dan minHeight untuk semua yang menunjukkan jumlah minimum ruang yang digunakan secara {i>default<i}. Namun, saat pengguna menambahkan widget ke layar utama, biasanya menempati lebih dari lebar dan tinggi minimum yang Anda tentukan.

Layar utama Android menawarkan kepada pengguna petak ruang yang tersedia tempat mereka dapat widget dan ikon tempat. Kisi-kisi ini dapat bervariasi menurut perangkat; misalnya, banyak handset menawarkan kisi 5x4, dan tablet dapat menawarkan kisi yang lebih besar. Kapan widget Anda ditambahkan, akan direntangkan untuk menempati jumlah minimum sel, secara horizontal dan vertikal, diperlukan untuk memenuhi batasan targetCellWidth dan targetCellHeight di perangkat yang menjalankan Android 12 atau yang lebih tinggi, atau batasan minWidth dan minHeight di perangkat yang menjalankan Android 11 (level API 30) atau yang lebih lama.

Lebar dan tinggi sel serta ukuran margin otomatis yang diterapkan ke widget dapat bervariasi di berbagai perangkat. Gunakan tabel berikut untuk memperkirakan secara kasar dimensi minimum widget Anda dalam handset grid 5x4 biasa, dengan mempertimbangkan jumlah sel {i>grid<i} yang ditempati yang Anda inginkan:

Jumlah sel (lebar x tinggi) Ukuran yang tersedia dalam mode potret (dp) Ukuran yang tersedia dalam mode lanskap (dp)
1x1 57x102dp 127x51dp
2x1 130x102dp 269x51dp
3x1 203x102dp 412x51dp
4x1 276x102dp 554x51dp
5x1 349x102dp 697x51dp
5x2 349x220dp 697x117dp
5x3 349x337dp 697x184dp
5x4 349x455dp 697x250dp
... ... ...
n x m (73n - 16) x (118m - 16) (142n - 15) x (66m - 15)

Gunakan ukuran sel mode potret untuk menentukan nilai yang Anda berikan atribut minWidth, minResizeWidth, dan maxResizeWidth. Demikian pula, menggunakan ukuran sel mode lanskap untuk menginformasikan nilai yang Anda berikan untuk atribut minHeight, minResizeHeight, dan maxResizeHeight.

Alasannya adalah karena lebar sel biasanya lebih kecil dalam mode potret daripada dalam mode lanskap—dan, demikian pula, tinggi sel biasanya lebih kecil dalam mode lanskap daripada dalam mode potret.

Misalnya, jika Anda ingin lebar widget Anda dapat diubah ukurannya menjadi satu sel di Google Pixel 4, Anda harus menyetel minResizeWidth ke maksimal 56dp untuk memastikan nilai atribut minResizeWidth lebih kecil dari 57 dp—karena lebar sel minimal 57 dp dalam mode potret. Demikian pula, jika Anda ingin tinggi widget dapat diubah ukurannya dalam satu sel di perangkat yang sama, Anda perlu menyetel minResizeHeight ke maksimal 50dp untuk memastikan nilai untuk atribut minResizeHeight lebih kecil dari 51 dp—karena satu sel memiliki tinggi minimal 51 dp dalam mode lanskap.

Setiap widget dapat diubah ukurannya dalam rentang ukuran antara minResizeWidth/minResizeHeight dan maxResizeWidth/maxResizeHeight yang berarti harus beradaptasi dengan setiap rentang ukuran di antara mereka.

Misalnya, untuk mengatur ukuran default widget pada penempatan, Anda dapat tetapkan atribut berikut:

<appwidget-provider
    android:targetCellWidth="3"
    android:targetCellHeight="2"
    android:minWidth="180dp"
    android:minHeight="110dp">
</appwidget-provider>

Artinya, ukuran default widget adalah 3x2 sel, seperti yang ditentukan oleh Atribut targetCellWidth dan targetCellHeight—atau 180×110dp, sebagai ditentukan oleh minWidth dan minHeight untuk perangkat yang menjalankan Android 11 atau yang lebih lama. Untuk kasus yang terakhir, ukuran dalam sel bisa bervariasi tergantung pada perangkatnya.

Selain itu, untuk menetapkan rentang ukuran widget yang didukung, Anda dapat menetapkan atribut:

<appwidget-provider
    android:minResizeWidth="180dp"
    android:minResizeHeight="110dp"
    android:maxResizeWidth="530dp"
    android:maxResizeHeight="450dp">
</appwidget-provider>

Seperti yang ditetapkan oleh atribut sebelumnya, lebar widget dapat diubah ukurannya dari 180 dp menjadi 530 dp, dan tingginya dapat diubah ukurannya dari 110 dp menjadi 450 dp. Widget kemudian dapat diubah ukurannya dari sel 3x2 ke 5x2, selama hal berikut kondisi yang ada:

Kotlin

val smallView = RemoteViews(context.packageName, R.layout.widget_weather_forecast_small)
val mediumView = RemoteViews(context.packageName, R.layout.widget_weather_forecast_medium)
val largeView = RemoteViews(context.packageName, R.layout.widget_weather_forecast_large)

val viewMapping: Map<SizeF, RemoteViews> = mapOf(
        SizeF(180f, 110f) to smallView,
        SizeF(270f, 110f) to mediumView,
        SizeF(270f, 280f) to largeView
)

appWidgetManager.updateAppWidget(appWidgetId, RemoteViews(viewMapping))

Java

RemoteViews smallView = 
    new RemoteViews(context.getPackageName(), R.layout.widget_weather_forecast_small);
RemoteViews mediumView = 
    new RemoteViews(context.getPackageName(), R.layout.widget_weather_forecast_medium);
RemoteViews largeView = 
    new RemoteViews(context.getPackageName(), R.layout.widget_weather_forecast_large);

Map<SizeF, RemoteViews> viewMapping = new ArrayMap<>();
viewMapping.put(new SizeF(180f, 110f), smallView);
viewMapping.put(new SizeF(270f, 110f), mediumView);
viewMapping.put(new SizeF(270f, 280f), largeView);
RemoteViews remoteViews = new RemoteViews(viewMapping);

appWidgetManager.updateAppWidget(id, remoteViews);

Asumsikan bahwa widget menggunakan tata letak responsif yang ditentukan dalam cuplikan kode. Ini berarti tata letak yang ditentukan sebagai R.layout.widget_weather_forecast_small digunakan dari 180 dp (minResizeWidth) x 110 dp (minResizeHeight) hingga 269x279 dp (titik potong berikutnya - 1). Demikian pula, R.layout.widget_weather_forecast_medium digunakan dari 270x110 dp hingga 270x279 dp, dan R.layout.widget_weather_forecast_large digunakan dari 270x280 dp hingga 530dp (maxResizeWidth) x 450dp (maxResizeHeight).

Saat pengguna mengubah ukuran widget, tampilannya akan berubah untuk beradaptasi dengan setiap ukuran di sel, seperti yang ditunjukkan dalam contoh berikut.

Contoh widget cuaca dalam ukuran petak 3x2 terkecil. UI menunjukkan
            nama lokasi (Tokyo), suhu (14°), dan simbol yang menunjukkan
            cuaca sebagian berawan.
Gambar 2. 3x2 R.layout.widget_weather_forecast_small.

Contoh widget cuaca dalam &#39;sedang&#39; 4x2 ukuran. Mengubah ukuran widget
            dengan cara ini dibangun berdasarkan semua UI dari ukuran widget sebelumnya,
            dan menambahkan label &#39;Sebagian besar berawan&#39; dan perkiraan suhu dari
            16.00 hingga 19.00.
Gambar 3. 4x2 R.layout.widget_weather_forecast_medium.

Contoh widget cuaca dalam &#39;sedang&#39; 5x2 ukuran. Mengubah ukuran widget
            dengan cara ini menghasilkan UI yang sama dengan ukuran sebelumnya, hanya saja
            direntangkan sepanjang satu sel untuk 
mengambil lebih banyak ruang horizontal.
Gambar 4. 5x2 R.layout.widget_weather_forecast_medium.

Contoh widget cuaca dalam &#39;large&#39; (5x3) ukuran. Mengubah ukuran widget
            dengan cara ini dibangun berdasarkan semua UI dari ukuran widget sebelumnya,
            dan menambahkan tampilan di dalam widget yang berisi prakiraan cuaca
            pada hari Selasa dan Rabu. Simbol yang menunjukkan cuaca cerah atau hujan
            serta suhu tinggi dan rendah setiap hari.
Gambar 5. 5x3 R.layout.widget_weather_forecast_large.

Contoh widget cuaca dalam &#39;large&#39; (5x4) ukuran. Mengubah ukuran widget
            dengan cara ini dibangun berdasarkan semua UI dari ukuran widget sebelumnya,
            dan menambahkan hari Kamis dan Jumat (dan simbolnya yang sesuai
            menunjukkan jenis cuaca serta suhu tinggi dan rendah
            untuk setiap hari).
Gambar 6. 5x4 R.layout.widget_weather_forecast_large.