创建桌面小组件
- 创建AppWidgetProvider类
创建一个AppWidgetProvider类,桌面小组件在更新、启用、停用和删除应用微件时收到广播。而AppWidgetProvider继承BroadcastReceiver,并且专门对小组件进行了一定的广播过滤,因此我们需要创建一个自定义的AppWidgetProvider类用来处理小组件相关的操作。
package com.example.widgetdemo
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
const val TAG = "MyWidgetProvider"
class MyWidgetProvider : AppWidgetProvider() {
/**
* 每次收到广播之后就会调用该函数,会在其他方法之前进行回调。一般可以不实现
* 默认的 AppWidgetProvider 实现会过滤所有应用微件广播并视情况调用上述方法
*/
override fun onReceive(context: Context?, intent: Intent?) {
super.onReceive(context, intent)
Log.d(TAG, "invoke onReceive......")
}
/**
* 调用此方法可以按 AppWidgetProviderInfo 中的 updatePeriodMillis 属性定义的时间间隔来更新应用微件
* 当用户添加应用微件时也会调用此方法,所以它应执行基本设置,如定义视图的事件处理脚本以及根据需要启动临时的 Service。
* 不过,如果您已声明配置 Activity,则当用户添加应用微件时不会调用此方法,但会调用它来执行后续更新。
* 由配置 Activity 负责在配置完成后执行首次更新。
*/
override fun onUpdate(context: Context?, appWidgetManager: AppWidgetManager?, appWidgetIds: IntArray?) {
super.onUpdate(context, appWidgetManager, appWidgetIds)
Log.d(TAG, "invoke onUpdate......")
}
/**
* 当首次放置微件时以及每当调整微件的大小时,会调用此方法。您可以使用此回调来根据微件的大小范围显示或隐藏内容
*/
override fun onAppWidgetOptionsChanged(context: Context?, appWidgetManager: AppWidgetManager?, appWidgetId: Int, newOptions: Bundle?) {
super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions)
Log.d(TAG, "invoke onAppWidgetOptionsChanged......")
}
/**
* 每次从应用微件托管应用中删除应用微件时,都会调用此方法。
*/
override fun onDeleted(context: Context?, appWidgetIds: IntArray?) {
super.onDeleted(context, appWidgetIds)
Log.d(TAG, "invoke onDeleted......")
}
/**
* 首次创建应用微件的实例时,会调用此方法。例如,如果用户添加应用微件的两个实例,只有首次添加时会调用此方法。
* 如果您需要打开一个新的数据库或执行只需要对所有应用微件实例执行一次的其他设置,则此方法非常合适。
*/
override fun onEnabled(context: Context?) {
super.onEnabled(context)
Log.d(TAG, "invoke onEnabled......")
}
/**
* 从应用微件托管应用中删除了应用微件的最后一个实例时,会调用此方法。
* 您应使用此方法来清理在 onEnabled(Context) 中完成的所有工作,如删除临时数据库。
*/
override fun onDisabled(context: Context?) {
super.onDisabled(context)
Log.d(TAG, "invoke onDisabled......")
}
override fun onRestored(context: Context?, oldWidgetIds: IntArray?, newWidgetIds: IntArray?) {
super.onRestored(context, oldWidgetIds, newWidgetIds)
Log.d(TAG, "invoke onRestored......")
}
}
- 注册广播
在AndroidManifest.xml文件中注册receiver,并且指定广播接收的类为我们上面定义的类,其中label字段的值表示在小组件列表中可以展示出的小组件的名称,同时指定action为"android.appwidget.action.APPWIDGET_UPDATE",并且添加一个与小组件相关配置信息的文件my_appwidget_info
<receiver
android:name=".MyWidgetProvider"
android:exported="false"
android:label="我的卡片">
<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>
- my_appwidget_info.xml文件
initialLayout:小组件的初始化布局
minWidth:小组件最小宽度
minHeight:小组件最小高度
previewImage:小组件在应用组件列表中的预览图
resizeMode:是否可以调整大小,none|horizontal|vertical;固定大小|水平方向|垂直方向
updatePeriodMillis:更新间隔,多久更新一次,单位MS,官方限制30分钟,以减少耗电
widgetCategory:小组件可以展示在哪里,一般为主界面
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/layout_my_card_widget"
android:minWidth="80dp"
android:minHeight="40dp"
android:previewImage="@drawable/widget_icon"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="86400000"
android:widgetCategory="home_screen">
</appwidget-provider>
第一个桌面小组件就创建好了,效果如下:
事件交互
- 更新小组件
onUpdate方法会在用户首次往桌面添加小组件的时候调用,并且appWidgetIds属性标识了当前AppWidgetProvider管理几个小组件,因此在该方法中对小组件进行首次更新,我们可以在此处获取数据并更新小组件相关信息。
布局预览:
我们模拟一个场景,当用户首次想桌面添加小组件时,需要从网络拉取数据并更新到小组件当中,并且设置两个Button的点击事件。由于是演示,因此简单启动一个定时任务,来模拟网络耗时操作,实际情况比较复杂,需考虑各种case。
override fun onUpdate(context: Context?, appWidgetManager: AppWidgetManager?, appWidgetIds: IntArray?) {
super.onUpdate(context, appWidgetManager, appWidgetIds)
Log.d(TAG, "invoke onUpdate......")
val timer = Timer()
val timerTask: TimerTask = object : TimerTask() {
override fun run() {
//定时任务
appWidgetIds?.forEach {
val pendingIntent: PendingIntent =
Intent(context, SecondActivity::class.java).let {
intent ->
PendingIntent.getActivity(context, 0, intent, 0)
}
val views: RemoteViews =
RemoteViews(context?.packageName, R.layout.layout_my_card_widget).apply {
setTextViewText(R.id.name, "来自Code")//设置小组件TextView的值
//添加LeftButton的点击事件
setOnClickPendingIntent(R.id.btnLeft, getSelfPendingIntent(context, LEFT_BUTTON_CLICK))
//添加RightButton的点击事件
setOnClickPendingIntent(R.id.btnRight, getSelfPendingIntent(context, RIGHT_BUTTON_CLICK))
}
//执行更新
appWidgetManager?.updateAppWidget(appWidgetIds, views)
}
}
}
timer.schedule(timerTask, 1000)
}
因为小组件与app之间的交互是基于广播的,因此我们定义点击事件时需要自定义intent并且通过action进行点击事件的区分
private const val LEFT_BUTTON_CLICK = "widget_left_button"
private const val RIGHT_BUTTON_CLICK = "widget_right_button"
private fun getSelfPendingIntent(context: Context?, action: String): PendingIntent {
val intent = Intent(context, javaClass)//javaClass = this.class
intent.action = action
return PendingIntent.getBroadcast(context, 0, intent, 0)
}
接下来就是当我们点击小组件,然后回调到onReceive方法中对事件进行处理了
override fun onReceive(context: Context?, intent: Intent?) {
super.onReceive(context, intent)
Log.d(TAG, "invoke onReceive......")
intent?.action?.let {
action ->
context?.let {
val manager = AppWidgetManager.getInstance(context)
val remoteViews = RemoteViews(context.packageName, R.layout.layout_my_card_widget)
if (action == LEFT_BUTTON_CLICK) {
//设置一个颜色
remoteViews.setTextColor(R.id.name, Color.parseColor("#FF03DAC5"))
manager.updateAppWidget(ComponentName(context, MyWidgetProvider::class.java), remoteViews)
} else if (action == RIGHT_BUTTON_CLICK) {
//将颜色改回来
remoteViews.setTextColor(R.id.name, Color.parseColor("#FF000000"))
manager.updateAppWidget(ComponentName(context, MyWidgetProvider::class.java), remoteViews)
Toast.makeText(context, "点击右边按钮", Toast.LENGTH_SHORT).show()
}
}
}
}
布局文件比较单,到此关于小组件的简单使用就已经完成了。
- 注意事项:小组件的布局只支持RemoteViews,具体如下:
参考官方文档:Google开发文档