Android很早就支持了小部件(APP Widget),但貌似关注的人比较少。
开发桌面小部件和把大象放进冰箱一样,都是需要分三步。
- 第一步,创建声明小部件信息的XML,告诉系统这个小部件长怎么样和一些配置信息。
- 第二步,创建BroadcastReceiver,接收小部件的各种事件(添加,删除,更新等)。
- 第三步,创建View,将创建的View更新在桌面上。
声明小部件信息的XML
在项目的res/xml/
目录下面创建一个app_widget_info.xml
,使用 <appwidget-provider>
标签,类似写布局一样,将小部件的信息声明一下,告诉系统这个小部件长怎么样和一些配置信息。。
例:
xml
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="110dp"
android:minHeight="110dp"
android:targetCellWidth="2"
android:targetCellHeight="2"
android:description="@string/hello_widget_description"
android:previewImage="@drawable/hello_widget_preview_img"
android:initialLayout="@layout/hello_widget_initial_layout"
android:previewLayout="@layout/hello_widget_preview_layout"
android:updatePeriodMillis="86400000" />
minWidth和minHeight
设定小部件的宽高,系统会根据最接近的网格尺寸调整宽高。
例如:桌面的网格大小为50x50,设置的宽高为110x110,最终展示的宽高为100x100,对应桌面的2x2。
targetCellWidth和targetCellHeight
代表这个小部件宽度占多少个格子,高度占多少个格子。
Android12及以上支持,如果同时设置了minWidth和minHeight,会优先使用这个。
description
在添加小部件的界面,可以看到这个描述文字,12及以上才支持。
initialLayout
初始化的布局,系统通知需要更新小部件的时候,会将这个xml
中的view
作为初始化布局展示。
注意:只能支持RemoteViews
所支持的View
。
previewImage和previewLayout
previewImage
,使用图片作为预览,在添加界面展示。
previewLayout
,使用xml布局作为预览,在添加界面展示。仅Android12及以上支持。只能支持RemoteViews
所支持的View
。
如果同时设置了这两个属性,在Android12及以上会使用previewLayout
。
updatePeriodMillis
定时更新间隔,单位毫秒,最低为30分钟。
注意:因为各个厂商的电池优化策略,可能设置的间隔并不是很管用。除了关闭APP的电池优化策略,暂时没有很好的办法。
这第一步只是创建了信息的声明文件,还没有地方用到,下一步的最后将会用到这个文件。
创建BroadcastReceiver
系统会将小部件的更新和生命周期以发送广播的方式通知到我们,所以我们需要创建一个BroadcastReceiver
来接收这些事件。
Android已经有现成的BroadcastReceiver
了,叫AppWidgetProvider
,里面封装了一些通用的逻辑,方便我们使用,我们直接继承AppWidgetProvider
做一些我们关注的事情就行。
继承AppWidgetProvider
创建类 HelloWidgetProvider
继承 AppWidgetProvider
kotlin
class HelloWidgetProvider : AppWidgetProvider() {
/**
* 小部件创建、定时更新触发等时机会调用。
*/
override fun onUpdate(context: Context?, appWidgetManager: AppWidgetManager?, appWidgetIds: IntArray?) {}
/**
* 小部件被第一次创建的时候会调用,可能会创建多个,只有第一个的时候会调用。
*/
override fun onEnabled(context: Context?) {}
/**
* 小部件被删除的时候会被调用。
*/
override fun onDeleted(context: Context?, appWidgetIds: IntArray?) {}
/**
* 最后一个小部件被删除的时候调用,与onDeleted的区别是,onDeleted每次删除都会调用。
*/
override fun onDisabled(context: Context?) {}
}
创建了BroadcastReceiver
当然是需要在manifest中声明的。
将HelloWidgetProvider声明在manifest中
在AndroidManifest.xml
中声明我们刚才创建的HelloWidgetProvider
。
然后在Provider中添加刚才创建的app_widget_info.xml
。
HelloWidgetProvider
:用来接收系统事件的,例如小部件的生命周期等等。
app_widget_info.xml
:用来声明小部件的一些信息,用户在小组件添加页面可以预览。
xml
<receiver
android:name=".HelloWidgetProvider"
android:exported="false"
android:label="小部件的名字,添加界面可见">
<intent-filter>
<!-- 接收系统事件,例如生命周期等-->
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<!--声明appwidget的信息-->
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/appwidget_info" />
</receiver>
好了,我们已经可以成功的声明小部件的信息了,这个时候可以添加小部件了,不过只能展示初始化界面,也就是initialLayout
的内容。
最后一步,就是更新桌面小部件,将我们想要的UI展示在桌面上。
实际场景中,我们可能会调用接口获取数据,然后更新小部件的UI。
创建View并展示
这里介绍的是使用XML
的方式创建并展示小部件,如果要使用compose
可以参考Jetpack Glance,前段时间刚发布的稳定版本。
更新小部件可以使用AppWidgetManager#updateAppWidget(ComponentName,RemoteViews)
第一个参数ComponentName
为小部件的BroadcastReceiver,也就是刚才创建的HelloWidgetProvider
。
第二个参数RemoteViews
为展示的View
,具体使用可以参考文档。
RemoteViews
支持的布局:
AdapterViewFlipper
FrameLayout
GridLayout
GridView
LinearLayout
ListView
RelativeLayout
StackView
ViewFlipper
支持的控件:
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextClock
TextView
Android 12及以上支持的控件:
CheckBox
RadioButton
RadioGroup
Switch
接下来我们在刚才写的HelloWidgetProvider
中的onUpdate
加入更新小部件UI的逻辑,代码如下。
kotlin
/**
* 小部件创建、定时更新触发等时机会调用。
*/
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
GlobalScope.launch {
val provider = ComponentName(context, HelloWidgetProvider::class.java)
val remoteViews = RemoteViews(context.packageName, R.layout.hello_widget_layout)
val textFromRemote = getTextFromRemote()
remoteViews.setTextViewText(R.id.text, textFromRemote)
//更新小部件
appWidgetManager.updateAppWidget(provider, remoteViews)
}
}
private suspend fun getTextFromRemote(): String {
//假装是网络延迟
delay(5000)
return "Hello APP Widget"
}
hello_widget_layout.xml
如下
xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="@color/white">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="这个是标题"
android:textColor="@color/black" />
</FrameLayout>
OK,运行程序查看效果。
到这里已经实现了一个简单的小部件了,接下来可能需要根据具体业务场景实现比较复杂的Widget。
更多
- 实现正方形的小部件。
- 使用WorkManager定时更新小部件。
- 在应用内展示添加小部件弹窗。