創造無限可能 | 在 Android 12 中使用 widget

Android開發者發表於2021-10-01

本文是 "更新 Android 12 中 widget" 系列的第二篇文章。在上一部分 我們探討了通過一些簡單的方法,為 APP 使用者呈現出非常顯性的視覺更新。在這篇文章中,我們將一起了解一些更高階別的特性,這些功能會讓您的 widget 更具互動性,更容易配置,並能在 Android 12 上呈現更好的 UI 體驗。

更簡單的配置

在 Android 12 之前,重新設定 widget 意味著使用者必須刪除現有 widget,然後使用新配置重新新增。Android 12 在多個方面改進了 widget 的配置方式,從而幫助使用者採用更簡單的方式對 widget 進行個性化配置。

使用者可重新設定原有 widget

可重組的 widget 允許使用者對 widget 進行自定義設定。在 Android 12 中,使用者將無需通過刪除和重新新增 widget 來調整這些原有設定。

要使用這一功能,您需在 appwidget-provider 中把 widgetFeatures 屬性設定為 reconfigurable。

xml/app_widget_info_checkbox_list.xml
<appwidget-provider
   android:configure="com.example.android.appwidget.ListWidgetConfigureActivity"
   android:widgetFeatures="reconfigurable"
   ... />

預設配置

如果您的 widget 依賴預設設定,在 Android 12 中您可跳過初始化操作,通過預設配置來設定 widget。

讓我們一起看下示例 widget 如何工作吧。在這個用例中,我們希望使用者能夠在兩種不同的 widget 佈局之間進行選擇,即 Grocery List 和 To-Do List。我們會設定 Grocery List 為預設設定,這樣使用者就不需要執行配置步驟,除非他們想切換至 To-Do List。

要實現此用例,您可以儲存使用者選項,並在沒有做出選擇操作的前提下,將 Grocery List 作為返回預設值。

ListAppWidget.kt
val layoutId = ListSharedPrefsUtil.loadWidgetLayoutIdPref(
   context, appWidgetId
)
val remoteViews = if (layoutId == R.layout.widget_grocery_list) {
   // 以 dp 為單位,指定最大寬度和高度,
 // 並指定一個用於已指定尺寸的佈局
   val viewMapping = mapOf(
       SizeF(150f, 150f) to constructRemoteViews(
           R.layout.widget_grocery_list
       ), SizeF(250f, 150f) to constructRemoteViews(
           R.layout.widget_grocery_grid
       )
   )
       RemoteViews(viewMapping)
   } else {
       constructRemoteViews(
           layoutId
       )
   }
appWidgetManager.updateAppWidget(appWidgetId, remoteViews)

此時,該 widget 已被設定為 "提供預設配置",您需要將 configuration_optional flag 設定為 widgetFeatures 屬性。這個操作會跳過額外的配置步驟,您可以直接在使用者的主螢幕上呈現 widget。與此同時,請您確保新增 reconfigurable flag,以便使用者後續可以更改生效的預設配置。

xml/app_widget_info_checkbox_list.xml
<appwidget-provider
   android:configure="com.example.android.appwidget.ListWidgetConfigureActivity"
   android:widgetFeatures="reconfigurable|configuration_optional"
   ... />

基於此更改,當使用者將 widget 新增至主螢幕時,該 widget 會自動啟用 Grocery List 佈局。由於我們把配置活動新增至 appwidget-providerconfigure 屬性中,使用者長按 widget 並點選編輯/重新設定按鈕時,配置就會生效。

當使用者配置該 widget 時,新的配置會被記錄在 ListWidgetConfigureActivity 中。

ListWidgetConfigureActivity.kt
private fun onWidgetContainerClicked(@LayoutRes widgetLayoutResId: Int) {
   ListSharedPrefsUtil.saveWidgetLayoutIdPref(this, appWidgetId, widgetLayoutResId)
   // 配置活動有責任更新 app widget
   val appWidgetManager = AppWidgetManager.getInstance(this)
   ListAppWidget.updateAppWidget(this, appWidgetManager, appWidgetId)
   // 請您確保回傳原始的 appWidgetId
   val resultValue = Intent()
   resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
   setResult(RESULT_OK, resultValue)
   finish()
}

全新&改良版 API

談及 Android 系統的裝置,使用者的選擇頗多,無論是手機、平板電腦、可摺疊裝置,還是其他型別的產品。Android 12 引入了完善的尺寸屬性和更靈活的佈局,這使得 widget 更易於定製,且在不同裝置和螢幕尺寸上均有穩定可靠的表現。

Widget 的尺寸限制

除了現有的 minWidthminHeighminResizeWidth 以及 minResizeHeight 以外,Android 12 還新增了新的 appwidget-provider 屬性。

您可以使用新的 maxResizeWidthmaxResizeHeight 屬性,來定義使用者所能夠調整的 widget 尺寸的最大高度和寬度。新的 targetCellWidthtargetCellHeight 屬效能夠定義裝置主螢幕上的 widget 預設尺寸。

當定義了 targetCellWidthtargetCellHeight 屬性後,搭載 Android 12 的裝置將使用這些屬性,而非 minWidthminHeight。搭載 Android 11 及以下版本的裝置將繼續使用 minWidthminHeight 屬性。

提示: targetCellWidth targetCellHeight 屬性在 cells 中定義,而 maxResizeWidth maxResizeHeight 屬性是在 dps 中定義的。
xml/app_widget_info_checkbox_list.xml
<appwidget-provider
   android:maxResizeWidth="240dp"
   android:maxResizeHeight="180dp"
   android:minWidth="180dp"
   android:minHeight="110dp"
   android:minResizeWidth="180dp"
   android:minResizeHeight="110dp"
   android:targetCellWidth="3"
   android:targetCellHeight="2"
   ... />

響應式佈局

儘管通過尺寸限制可以幫助使用者根據自身需求調整 widget 大小,但您可能更想根據 widget 的大小,提供不同的佈局和內容型別。這也使系統能在不喚醒應用的情況下,顯示不同尺寸的 widget。

要做到這一點,首先您需要建立一組不同尺寸的佈局,然後呼叫 updateAppWidget() 函式,並傳入一組佈局 (如下圖所示)。當 widget 尺寸發生變化時,系統會自動更改佈局。

val viewMapping: MutableMap<SizeF, RemoteViews> = mutableMapOf()
// 以 dp 為單位,指定最大寬度和高度,
// 並指定一個用於已指定尺寸的佈局
val viewMapping = mapOf(
   SizeF(150f, 110f) to RemoteViews(
       context.packageName,
       R.layout.widget_grocery_list
   ),
   SizeF(250f, 110f) to RemoteViews(
       context.packageName,
       R.layout.widget_grocery_grid
   ),
)
appWidgetManager.updateAppWidget(appWidgetId, RemoteViews(viewMapping))
//...

複合式按鈕

在 Android 12 上,使用者無需啟動應用也能用 widget 做更多的事情啦!有了新的複合式按鈕,您可以將 widget 變得更具互動性。這並不會改變 widget 的無狀態特性,但您可以新增一個監聽器來觀察狀態的變化。您可以呼叫 RemoteResponse.fromPendingIntent(),並在狀態發生變化時向監聽器傳送一個 PendingIntent

ItemsCollectionAppWidget.kt
remoteViews.setOnCheckedChangeResponse(
   R.id.item_switch,
   RemoteViews.RemoteResponse.fromPendingIntent(
       onCheckedChangePendingIntent
   )
)

另一方面,如果 widget 有一個控制元件列表,不建議您在單專案 collection 上設定 PendingIntent,因為這會導致效能不佳。在這種情況下,您可以在該 collection 上設定一個 PendingIntent 模板,調起 RemoteResponse.fromFillInIntent(),並在狀態發生變化時向監聽器傳送一個 fillInIntent

ItemsCollectionAppWidget.kt
remoteViews.setOnCheckedChangeResponse(
   R.id.item_switch,
   RemoteViews.RemoteResponse.fromFillInIntent(
       onCheckedChangeFillInIntent
   )
)

Collection 中簡化版的 RemoteViews

Android 12 引入了一個新的 API,能夠簡化傳送單個 collection 去填充 widget 列表的過程。在此之前,如果您想通過專案的 collection 來填充 ListViewGridViewStackView 或其他檢視,則需要執行 RemoteViewsService 返回 RemoteViewsFactory 的動作。有了新的 setRemoteAdapter() API,您便可以輕鬆使用核心 RemoteView 來傳送 collection。我們也正在做 androidx 的回傳工作,以確保該 API 在舊 Android 版本上仍然生效。

如果該 collection 不採用常量設定佈局,您可以通過 setViewTypeCount() 函式的方式,來設定此 collection 中 RemoteView 將使用的佈局 ID 的最大值。

ItemsCollectionAppWidget.kt
remoteViews.setRemoteAdapter(
   R.id.items_list_view,
   RemoteViews.RemoteCollectionItems.Builder()
       .addItem(/* id= */ ID_1, RemoteViews(...))
       .addItem(/* id= */ ID_2, RemoteViews(...))
       //...
       .setViewTypeCount(MAX_NUM_DIFFERENT_REMOTE_VIEWS_LAYOUTS)
       .build()
)

結語

請將現有的 widget 更新到 Android 12 吧!您會體驗到一個有著全新外觀和更具互動性的 widget。

現在您已經在這篇文章中瞭解到可配置的、新的或是改進中的 API,請查閱我們之前的推文《更新您的 widget 以適配 Android 12》,瞭解更新 widget 設計以及在 widget picker 中提供更好的使用者體驗的方法。如需更進一步,請查閱文章中提及的 程式碼示例

如果您正在構造新的 widget,請您留意後續的釋出。為了使構造新的 widget 更加簡單,我們一直在努力!

歡迎您 點選這裡 向我們提交反饋,或分享您喜歡的內容、發現的問題。您的反饋對我們非常重要,感謝您的支援!

相關文章