1.前言
本文介绍兼容Android小组件(AppWidget)的使用。
2.使用
2.1 AndroidManifest声明
参数设置:
- label:小组件名称
- resource:小组件配置文件(用于配置初始化图片/布局/系统定时时间间隔)
- intent-filter:广播接收过滤器(勇于接受广播)
2.2 demo_widget_info
用途:小组件(AppWidget)配置文件
参数设置:
initialLayout:
属性指向用于定义应用微件布局的布局资源。
configure:
属性定义要在用户添加应用微件时启动以便用户配置应用微件属性的 Activity。
这是可选的(请阅读下文的创建应用微件配置 Activity)。
previewImage:
属性指定预览来描绘应用微件经过配置后是什么样子的,用户在选择应用微件时会看到该预览。如果未提供,则用户会看到应用的启动器图标。
此字段对应于 AndroidManifest.xml 文件的
autoAdvanceViewId
属性指定应由应用微件的托管应用自动跳转的应用微件子视图的视图 ID。此属性是在 Android 3.0 中引入的。
resizeMode:
属性指定可以按什么规则来调整微件的大小。
您可以使用此属性来让主屏幕微件在横轴上可调整大小、在纵轴上可调整大小,或者在这两个轴上均可调整大小。 用户可轻触并按住微件以显示其大小调整手柄,然后拖动水平和/或垂直手柄以更改布局网格上的大小。
resizeMode 属性的值包括“horizontal”、“vertical”和“none”。要将微件声明为在水平和垂直方向上均可调整大小,请提供值“horizontal|vertical”。此属性是在 Android 3.1 中引入的。
minResizeWidth
属性指定可将微件大小调整到的最小高度(以 dp 为单位)。
如果此字段的值大于 minHeight 或未启用垂直大小调整(请参阅 resizeMode),则此字段不起作用。此属性是在 Android 4.0 中引入的。
minResizeHeight:
属性指定可将微件大小调整到的最小高度(以 dp 为单位)。
如果此字段的值大于 minHeight 或未启用垂直大小调整(请参阅 resizeMode),则此字段不起作用。此属性是在 Android 4.0 中引入的。
widgetCategory:
属性声明应用微件是否可以显示在主屏幕 (home_screen) 和/或锁定屏幕 (keyguard) 上。只有低于 5.0 的 Android 版本才支持锁定屏幕微件。对于 Android 5.0 及更高版本,只有 home_screen 有效。
minWidth/minHeight:
它们指定了App Widget布局需要的最小区域。
缺省的App Widgets所在窗口的桌面位置基于有确定高度和宽度的单元网格中。
如果App Widget的最小长度或宽度和这些网格单元的尺寸不匹配,那么这个App Widget将上舍入(上舍入即取比该值大的最接近的整数——译者注)到最接近的单元尺寸。
注意:app widget的最小尺寸,不建议比 “4x4” 个单元格要大。关于app widget的尺寸,后面在详细说明。
updatePeriodMillis:
它定义了 widget 的更新频率。
实际的更新时机不一定是精确的按照这个时间发生的。
建议更新尽量不要太频繁,最好是低于1小时一次。 或者可以在配置 Activity 里面供用户对更新频率进行配置。
实际上,当updatePeriodMillis的值小于30分钟时,系统会自动将更新频率设为30分钟!
关于这部分,后面会详细介绍。
注意: 当更新时机到达时,如果设备正在休眠,那么设备将会被唤醒以执行更新。如果更新频率不超过1小时一次,那么对电池寿命应该不会造成多大的影响。 如果你需要比较频繁的更新,或者你不希望在设备休眠的时候执行更新,那么可以使用基于 alarm 的更新来替代 widget 自身的刷新机制。
将 alarm 类型设置为 ELAPSED_REALTIME 或 RTC,将不会唤醒休眠的设备,同时请将 updatePeriodMillis 设为 0
。
initialLayout:
指向 widget 的布局资源文件
initialKeyguardLayout
指向 widget 位于 lockscreen 中的布局资源文件。Android 4.2 引入。
2.3 创建应用微件布局
您必须在 XML 中定义应用微件的初始布局,并将其保存在项目的 res/layout/ 目录中。您可以使用下面列出的视图对象来设计应用微件,但在开始设计应用微件之前,请先阅读并了解应用微件设计准则。
如果您熟悉布局,那么创建应用微件布局非常简单。不过,您必须知道,应用微件布局基于 RemoteViews,并不是每种布局或视图微件都受其支持。
RemoteViews 对象(因而应用微件)可以支持以下布局类:
- FrameLayout
- LinearLayout
- RelativeLayout
- GridLayout
以及以下微件类:
- AnalogClock
- Button
- Chronometer
- ImageButton
- ImageView
- ProgressBar
- TextView
- ViewFlipper
- ListView
- GridView
- StackView
- AdapterViewFlipper
备注:
不支持这些类的后代。
RemoteViews 还支持ViewStub,它是一个大小为零的不可见视图,您可以使用它在运行时以懒散的方式扩充布局资源。
2.3 AppWidgetProvider
2.3.1 介绍:
AppWidgetProvider为BroadcastReceiver的子类。用来处理应用微件广播。
AppWidgetProvider仅接收与应用微件有关的事件广播,例如当更新、删除、启用和停用应用微件时发出的广播。当发生这些广播事件时,AppWidgetProvider 会接收以下方法调用:
2.3.2 实现:
class ExampleAppWidgetProvider : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
// Perform this loop procedure for each App Widget that belongs to this provider
appWidgetIds.forEach { appWidgetId ->
// Create an Intent to launch ExampleActivity
val pendingIntent: PendingIntent = Intent(context, ExampleActivity::class.java)
.let { intent ->
PendingIntent.getActivity(context, 0, intent, 0)
}
// Get the layout for the App Widget and attach an on-click listener
// to the button
val views: RemoteViews = RemoteViews(
context.packageName,
R.layout.appwidget_provider_layout
).apply {
setOnClickPendingIntent(R.id.button, pendingIntent)
}
// Tell the AppWidgetManager to perform an update on the current app widget
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
}
2.3.3 回调方法:
onUpdate()
调用此方法可以按 AppWidgetProviderInfo 中的 updatePeriodMillis 属性定义的时间间隔来更新应用微件(请参阅上文的添加 AppWidgetProviderInfo 元数据)。
当用户添加应用微件时也会调用此方法,所以它应执行基本设置,如定义视图的事件处理脚本以及根据需要启动临时的 Service。
不过,如果您已声明配置 Activity,则当用户添加应用微件时不会调用此方法,但会调用它来执行后续更新。
由配置 Activity 负责在配置完成后执行首次更新。(请参阅下文的创建应用微件配置 Activity。)
onAppWidgetOptionsChanged()
当首次放置微件时以及每当调整微件的大小时,会调用此方法。您可以使用此回调来根据微件的大小范围显示或隐藏内容。
您可以通过调用 getAppWidgetOptions() 来获取大小范围,该方法会返回包含以下各项的 Bundle:
OPTION_APPWIDGET_MIN_WIDTH - 包含微件实例的当前宽度的下限(以 dp 为单位)。
OPTION_APPWIDGET_MIN_HEIGHT - 包含微件实例的当前高度的下限(以 dp 为单位)。
OPTION_APPWIDGET_MAX_WIDTH - 包含微件实例的当前宽度的上限(以 dp 为单位)。
OPTION_APPWIDGET_MAX_HEIGHT - 包含微件实例的当前高度的上限(以 dp 为单位)。
此回调是在 API 级别 16 (Android 4.1) 中引入的。如果您实现此回调,请确保您的应用不依赖于它,因为在旧款设备上不会调用它。
onDeleted(Context, int[])
每次从应用微件托管应用中删除应用微件时,都会调用此方法。
onEnabled(Context)
首次创建应用微件的实例时,会调用此方法。
例如,如果用户添加应用微件的两个实例,只有首次添加时会调用此方法。如果您需要打开一个新的数据库或执行只需要对所有应用微件实例执行一次的其他设置,则此方法非常合适。
onDisabled(Context)
从应用微件托管应用中删除了应用微件的最后一个实例时,会调用此方法。
您应使用此方法来清理在 onEnabled(Context) 中完成的所有工作,如删除临时数据库。
onReceive(Context, Intent)
针对每个广播调用此方法,并且是在上述各个回调方法之前调用。 您通常不需要实现此方法,因为默认的 AppWidgetProvider 实现会过滤所有应用微件广播并视情况调用上述方法。
2.4 更新
2.4.1 完全更新
完整更新:调用AppWidgetManager。updateAppWidget(int, android.widget.RemoteViews)来完全更新小部件。这将用一个新的RemoteViews替换之前提供的RemoteViews。这是计算开销最大的更新。
val appWidgetManager = AppWidgetManager.getInstance(context)
val remoteViews = RemoteViews(context.getPackageName(), R.layout.widgetlayout).also {
setTextViewText(R.id.textview_widget_layout1, "Updated text1")
setTextViewText(R.id.textview_widget_layout2, "Updated text2")
}
appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
2.4.2 局部更新
部分更新:调用AppWidgetManager。partiallyUpdateAppWidget更新小部件的部分。这将合并新的remoteview与之前提供的remoteview。如果小部件没有通过updateAppWidget(int[], RemoteViews)接收到至少一次完整的更新,则忽略此方法。
val appWidgetManager = AppWidgetManager.getInstance(context)
val remoteViews = RemoteViews(context.getPackageName(), R.layout.widgetlayout).also {
setTextViewText(R.id.textview_widget_layout, "Updated text")
}
appWidgetManager.partiallyUpdateAppWidget(appWidgetId, remoteViews)
2.4.3 集合数据更新
集合数据刷新:调用AppWidgetManager。notifyappwidgetviewdatachhanged来使小部件中集合视图的数据失效。这会触发RemoteViewsFactory.onDataSetChanged。在此期间,旧数据显示在小部件中。昂贵的任务可以用这种方法安全地同步执行。
val appWidgetManager = AppWidgetManager.getInstance(context)
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_listview)
2.5 动态添加小部件
val appWidgetManager: AppWidgetManager = context.getSystemService(AppWidgetManager::class.java)
val myProvider = ComponentName(context, MyAppWidgetProvider::class.java)
val successCallback: PendingIntent? = if (appWidgetManager.isRequestPinAppWidgetSupported) {
// Create the PendingIntent object only if your app needs to be notified
// that the user allowed the widget to be pinned. Note that, if the pinning
// operation fails, your app isn't notified.
Intent(...).let { intent ->
// Configure the intent so that your app's broadcast receiver gets
// the callback successfully. This callback receives the ID of the
// newly-pinned widget (EXTRA_APPWIDGET_ID).
PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
} else {
null
}
successCallback?.also { pendingIntent ->
appWidgetManager.requestPinAppWidget(myProvider, null, pendingIntent)
}
参考文档:
- https://developer.android.google.cn/guide/topics/appwidgets?hl=zh-cn
- https://blog.csdn.net/sd19871122/article/details/123030955