Google官方文档:Build an App Widget
创建一个App Widget,需要以下基础内容:
AppWidgetProviderInfo
对象
描述App Widget的元数据,例如App Widget的布局,大小、更新频率等等,这个对象必须在res/xml
目录下定义XML文件。
AppWidgetProvider
类继承实现
AppWidgetProvider
继承自BroadcastReceiver
,内部定义了基于广播事件来实现App Widget的接口方法,通过这个类,在App Widget updated
、enabled
、disabled
和deleted
的时候都会接收到相应的广播。
App Widget视图布局
App Widget的视图布局文件,在res/layout
下面定义的XML布局文件。
在项目中添加窗口小部件的布局文件,布局文件没什么可讲的,跟普通的布局文件一样,但是要注意,App Widget对布局和控件的支持是有限的。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextClock
android:id="@+id/timeText"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="3"
android:format12Hour="hh:mm"
android:format24Hour="HH:mm"
android:gravity="center"
android:textColor="@android:color/black"
android:textSize="40sp"
android:textStyle="bold"/>
<TextClock
android:id="@+id/dateText"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:format12Hour="yyyy/MM/dd E"
android:format24Hour="yyyy/MM/dd E"
android:gravity="center"
android:textColor="@android:color/black"
android:textSize="16sp"/>
LinearLayout>
TextClock
是TextView
的子类,所以也是被AppWidget所支持的,更多关于TextClock
的使用,请参考:Android数字时钟神一般的实现——TextClock
App Widget布局文件支持的layout
类
App Widget布局文件支持的widget
类
为App Widget添加边距
从Android 4.0(Api level 14)开始,会自动为App Widget添加边距,如果你的targetSdkVersion
设置成14及以上,可无需配置此项。为App Widget添加边距,只需要在布局的根layout
中添加android:padding
属性只可。
AppWidgetProviderInfo
元数据配置 AppWidgetProviderInfo
元数据配置是放在res/xml
中的一个XML文件,他描述一个App Widget的基本性质,比如最小布局尺寸、初始布局尺寸、更新时间间隔以及配置Activity
(可选)等等。配置文件的根节点是
的单个元素。
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="180dp"
android:minHeight="110dp"
android:updatePeriodMillis="0"
android:previewImage="@mipmap/ic_launcher"
android:initialLayout="@layout/clock_widget"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen">
appwidget-provider>
属性简介
minWidth
和minHeight
属性值指定默认情况下 App Widget占用的最小空间量 。在默认主屏幕中,定义了具有特定高度和宽度的单元格网格,用来放置App Widget。如果定义t的最小宽度或高度的值不匹配的单元格网格的尺寸,则该App Widget尺寸会向上取最接近的大小。尺寸计算规则如下:
单元网格数量 (行/列) |
可用尺寸(dp) (minWidth/minHeight) |
---|---|
1 | 40dp |
2 | 110dp |
3 | 180dp |
4 | 250dp |
… | … |
n | 70 × n − 30 |
有关调整应用程序小部件大小的更多信息,请参阅应用程序小部件设计指南。
注意:为使您的应用程序小部件可跨设备移植,应用程序小部件的最小尺寸不得大于4 x 4单元。
minResizeWidth
和minResizeHeight
属性值指定应用Widget的绝对最小尺寸。这些值指定App Widget尺寸小于该值将难以辨认或无法使用。使用这些属性可以使用户将App Widget的大小调整为小于minWidth
和minHeigh
t属性定义的默认大小 。这些属性在Android 3.1中引入,计算规则如上所述。
有关调整应用程序小部件大小的更多信息,请参阅应用程序小部件设计指南。
updatePeriodMillis
属性定义App Widget更新频率,这个时间设定了AppWidgetProvider
调用onUpdate()
回调方法请求更新的频率。该值不能保证实际更新操作会准时发生,建议在设定是不要太频繁地进行更新(为了节省电池,每小时不要超过一次),配置中的更新频率可以允许用户自行调整。
注意:如果App Widget在更新(如定义
updatePeriodMillis
)时设备处于睡眠状态,则设备将唤醒以执行更新。如果更新频率非常低,则不会对电池寿命造成重大问题。但是,如果您需要频繁地更新,可以配置在设备处于睡眠状态时不需要更新,这个可以根据唤醒设备的警报来执行更新,为此,在AppWidgetProvider
收到AlarmManager
的Intent警报 ,将警报类型设置为ELAPSED_REALTIME
或RTC
,这样在设备醒着时才会发出警报。然后设置 updatePeriodMillis为零(“0”)。
initialLayout
属性定义App Widget布局的布局资源
configure
属性定义了当用户添加App Widget时启动的Activity
,这个Activity
是用来配置App Widget属性的一个页面(可选,详情参阅创建应用程序小部件配置活动)。
previewImage
属性指定App Widget的预览图,在小部件选择列表中展示。如果未提供,则用应用程序的启动器图标。该字段对应 于文件中元素的 android:previewImage属性。有关使用的更多讨论,请参见设置预览图像。(在Android 3.1中引入)
resizeMode
属性指定App Widget调整大小的规则。该resizeMode
属性的值 包括“水平(horizontal
)”,“垂直(vertical
)”和“无(none
)”,支持多种模式填写多个值,中间用|
隔开。(在Android 3.1中引入)
minResizeHeight
属性指定App Widget可以调整到的最小高度(以dps为单位)。如果该字段值大于minHeight
或未启用垂直调整大小,则此字段无效。(在Android 4.0中引入)
minResizeWidth
属性指定App Widget可以调整到的最小宽度(以dps为单位)。如果该字段大于minWidth
或未启用水平调整大小,则此字段无效。(在Android 4.0中引入)
widgetCategory
属性声明您的App Widget支持显示的位置,可以是主屏幕(home_screen
),锁定屏幕(keyguard
),或者同时显示在两者上。在低于Android 5.0的版本支持锁定屏幕小部件,对于Android 5.0及更高版本,仅home_screen
有效。
有关
元素接受的属性的更多信息,请参考:AppWidgetProviderInfo类介绍
AppWidgetProvider
类继承自 BroadcastReceiver
类,内部实现了相关逻辑,为处理App Widget的广播提供便利。AppWidgetProvider
只会接收与App Widget相关的广播,例如:App Widget的更新(updated
)、删除(deleted
)、启用(enabled
)、禁用(disabled
) ,当接收到这些广播AppWidgetProvider
将会调用对应的方法。
AppWidgetProvider
,并实现相应的方法package com.owen.clockwidget
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.content.Intent
import android.widget.RemoteViews
import android.app.PendingIntent
import android.app.Service
import android.content.ComponentName
import android.content.IntentFilter
import android.os.Bundle
import android.os.Handler
import android.os.SystemClock
import android.util.Log
import java.util.*
/**
*
*
Author:yunying.zhang
*
Email: [email protected]
*
Date: 2019/12/5
*/
class ClockWidgetProvider : AppWidgetProvider() {
val TAG = "ClockWidgetProvider"
private val mHandler = Handler()
override fun onReceive(context: Context?, intent: Intent?) {
Log.i(TAG, "小部件提供程序接收到广播")
super.onReceive(context, intent)
if(Intent.ACTION_TIME_CHANGED == intent?.action) {
updateWidget(context)
}
}
override fun onUpdate(context: Context?, appWidgetManager: AppWidgetManager?, appWidgetIds: IntArray?) {
super.onUpdate(context, appWidgetManager, appWidgetIds)
Log.i(TAG, "小部件更新")
// 一个App Widget提供程序为一个App Widget提供支持,但是一个App Widget却可以在多个地方添加,
// 每一个显示的App Widget视图有一个id,如果多个地方添加将会有多个id,这里循环对每一个视图进行更新
appWidgetIds?.forEach { appWidgetId ->
// 创建App Widget点击事件意图(说明:如果不需要实现点击事件,可以不定义此项)
// 通知AppWidgetManager更新当前的App Widget
appWidgetManager?.updateAppWidget(appWidgetId, buildView(context))
}
}
override fun onAppWidgetOptionsChanged(context: Context, appWidgetManager: AppWidgetManager,
appWidgetId: Int, newOptions: Bundle) {
super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions)
Log.i(TAG, "$appWidgetId 小部件选项更新")
}
override fun onEnabled(context: Context) {
super.onEnabled(context)
Log.i(TAG, "小部件启用")
}
override fun onDisabled(context: Context) {
super.onDisabled(context)
Log.i(TAG, "小部件禁用")
}
override fun onDeleted(context: Context, appWidgetIds: IntArray) {
super.onDeleted(context, appWidgetIds)
appWidgetIds?.forEach {
Log.i(TAG, "$it 小部件被删除")
}
}
companion object {
fun buildView(context: Context?): RemoteViews {
val c = Calendar.getInstance()
val timePendingIntent: PendingIntent = Intent(context, MainActivity::class.java)
.let { intent ->
PendingIntent.getActivity(context, 0, intent, 0)
}
// 如果有多个控件可点击,可以创建多个点击事件意图
val datePendingIntent:PendingIntent = Intent(Intent.ACTION_QUICK_CLOCK).let { intent->
PendingIntent.getActivity(context, 1, intent, 0)
}
// 获取App Widget的视图布局
return RemoteViews(context?.packageName, R.layout.clock_widget).apply {
// 设置点击事件意图(说明:如果App Widget内部有多个控件点击事件,可以在此添加多个控件的点击事件)
setOnClickPendingIntent(R.id.timeText, timePendingIntent)
setOnClickPendingIntent(R.id.dateText, datePendingIntent)
}
}
fun updateWidget(context: Context?) {
val appWidgetManager = AppWidgetManager.getInstance(context)
val componentName = ComponentName(context!!, ClockWidgetProvider::class.java)
val appWidgetIds = appWidgetManager.getAppWidgetIds(componentName)
appWidgetIds?.forEach {
appWidgetManager.updateAppWidget(componentName, buildView(it, context))
}
}
}
}
AppWidgetProvider
回调方法简介
onUpdate()
这个方法在App Widget定期更新的时候调用(在AppWidgetProviderInfo
中由updatePeriodMillis
定义更新平路),另外,当App Widget第一次添加时也会调用该方法,因此该方法中应该包含必要的启动代码(例如:控件事件监听处理、启动临时服务等)。但是如果你定义了配置Activity
(通过AppWidgetProviderInfo
中的configure
节点配置),当用户添加App Widget时将不会调用改方法(但是在后续的更新调用该方法),此时,App Widget的第一次更新由配置Activity
在配置完成时实现。
onAppWidgetOptionsChanged()
这个方法在App Widget第一次防止或者在任何时候改变尺寸是调用。在这个回调方法中,可以根据App Widget的尺寸范围,显示或者隐藏一些内容。可以通过getAppWidgetOptions()
获取App Widget的尺寸范围信息,返回一个Bundle
类型对象。
AppWidgetOptions信息字段说明:
OPTION_APPWIDGET_MIN_WIDTH
(key值:appWidgetMinWidth
)包含当前App Widget宽度下限(单位:dp)OPTION_APPWIDGET_MIN_HEIGHT
(key值:appWidgetMinHeight
)包含当前App Widget高度下限(单位:dp)OPTION_APPWIDGET_MAX_WIDTH
(key值:appWidgetMaxWidth
)包含当前App Widget宽度上限(单位:dp)OPTION_APPWIDGET_MAX_HEIGHT
(key值:appWidgetMaxHeight
)包含当前App Widget高度上限(单位:dp)
onDeleted(Context, int[])
这个方法在App Widget从App Widget host中删除的时候调用。
onEnabled(Context)
这个方法在App Widget第一次创建对象的时候调用。如果同一个App Widget多次添加,这个方法也只会调用一次。
onDisabled(Context)
这个方法在App Widget的最后一个实例被删除的时候调用,如果同一个App Widget多次添加了,删除一个不会调用该方法,直到最后一个被删除才会调用。
onReceive(Context, Intent)
这个方法在接收到任何支持的广播的时候都会调用,AppWidgetProvider
实现了过滤接收所有以上所有App Widget的广播,通常不需要实现这个方法,但是如果需要接收其他广播,需要在AndroidManifest.xml
中定义提供器的时候添加
,并在该方法内实现相应的代码逻辑。
AppWidgetProvider
类最重要的回调方法就是onUpdate()
,因为他在每个App Widget添加到host的时候都会调用(除非你定义了配置Activity),如果你的App Widget接受任何的用户事件交互,那么需要在这个回调方法内实现点击事件的注册。
AndroidManifest.xml
中声明App Widget 在AndroidManifest.xml
中,声明App Widget,请注意以下示例代码中的android:name
属性。
<application>
.......
<receiver android:name=".ClockWidgetProvider">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/clock_widget_info" />
receiver>
application>
完成了以上步骤,接下来就可以编译安装应用,然后在设备的桌面就可以添加App Widget了。
demo项目源码可在Github上下载:ClockWidget
说明:因demo源码会不断更新,当前文章的代码可能会跟Github上的存在差异