Android Collection Widgets 是用來在 home screen 上顯示多筆相同型態的資料,例如圖片集、郵件列表等。所以,一般來說 collection widgets 可以上下滑動來顯示更多的資料。本文章將介紹如何建立一個 collection widgets。
Table of Contents
建立一個 Collection Widget
首先,我們要建立一個 app widget。如果還不熟悉怎麼建立一個 app widget 的話,請先參考以下文章。我們將延續以下文章中的 PersonInfoAppWidget 範例,將它修改成 collection widget。
修改 person_info_app_widget.xml 成如下。我們把它改成顯示一個 ListView。當 ListView 裡面沒有任何資料時,改為顯示 No Data 的 TextView。
新增 person_info_item.xml。它將用於顯示在 ListView 裡的每一筆資料。
RemoteViewsService 和 RemoteViewsService.RemoteViewsFactory
我們需要利用 RemoteViewsService 來提供要顯示的資料,以及每一筆資料的 view。在 RemoteViewsService 中,你可以從資料庫中取得資料,也可以 content provider 中取得資料。
就如同一般使用 ListView 時,我們需要一個 Adapter 來提供每筆資料的 view。我們要覆寫 RemoteViewsService.onGetViewFactory,並回傳 RemoteViewsService.RemoteViewsFactory。它是一個 Adapter 的 wrapper。因此,使用方式與 Adapter 幾乎相同。
class PersonInfoAppWidgetService : RemoteViewsService() {
override fun onGetViewFactory(intent: Intent?): RemoteViewsFactory {
return PersonInfoRemoteViewFactory(this)
}
}
class PersonInfoRemoteViewFactory(
private val context: Context,
) : RemoteViewsService.RemoteViewsFactory {
private lateinit var people: List
override fun onCreate() {
loadData()
}
override fun onDataSetChanged() {
loadData()
}
private fun loadData() {
people = listOf(
Person("Wayne", "Mobile Software Developer & Blogger"),
Person("David", "Android Developer"),
Person("Peter", "Embedded System Developer"),
)
}
override fun onDestroy() {
}
override fun getCount(): Int {
print("count=${people.size}")
return people.size
}
override fun getViewAt(position: Int): RemoteViews {
return RemoteViews(context.packageName, R.layout.person_info_item).apply {
val person = people[position]
setTextViewText(R.id.name_text_view, person.name)
setTextViewText(R.id.job_text_view, person.job)
}
}
override fun getLoadingView(): RemoteViews? = null
override fun getViewTypeCount(): Int = 1
override fun getItemId(position: Int): Long = people[position].name.hashCode().toLong()
override fun hasStableIds(): Boolean = true
} 在 AppWidgetProvider.onUpdate() 中,我們要呼叫 RemoteViews.setRemoteAdapter() 來設定 adapter。RemoteViews.setEmptyView() 設定當沒有資料時,要隱藏第一個參數的 view,並且顯示第二個參數的 view。
class PersonInfoAppWidget : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray,
) {
for (appWidgetId in appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId)
}
}
}
internal fun updateAppWidget(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int,
) {
val intent = Intent(context, PersonInfoAppWidgetService::class.java).apply {
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
}
val views = RemoteViews(context.packageName, R.layout.person_info_app_widget).apply {
setRemoteAdapter(R.id.person_info_list_view, intent)
setEmptyView(R.id.person_info_list_view, R.id.empty_view)
}
appWidgetManager.updateAppWidget(appWidgetId, views)
}最後,因為 PersonInfoAppWidgetService 是一個 Service。所以,我們必須在 AndroidManifest.xml 裡宣告它。
AppWidgetManager
至目前為止,PersonInfoAppWidget 大致完成了。系統會依據
更動資料後,app 可以呼叫 AppWidgetManager.notifyAppWidgetViewDataChanged() 來要求系統呼叫 PersonInfoAppWidget.onUpdate()。
如果 app 不知道要更新的 app widget 的 appWidgetId 的話,我們可以利用 AppWidgetManager.getappWidgetIds() 取得所有 PersonInfoAppWidget instance 的 appWidgetIds。
AppWidgetManager.getInstance(application).let { appWidgetManager ->
val appWidgetIds = appWidgetManager.getAppWidgetIds(
ComponentName(application, PersonInfoAppWidget::class.java)
)
appWidgetManager
.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.person_info_list_view)
}結語
Collection widget 看似有點複雜,但是實作起來其實就好像實作一個 Adapter。和實作 ListView 或 RecycleView 很相似。
參考
- Use widget collections, Google developers.









