Android小组件(AppWidget)

1.前言

本文介绍兼容Android小组件(AppWidget)的使用。

2.使用

2.1 AndroidManifest声明

    <receiver android:name=".appwidget.DemoWidgetProvider"
        android:label="@string/appwidget_title"
        android:exported="false">
        <meta-data
            android:name="android.appwidget.provider"
            android:resource="@xml/demo_widget_info" />
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        </intent-filter>
    </receiver>

参数设置:

  1. label:小组件名称
  2. resource:小组件配置文件(用于配置初始化图片/布局/系统定时时间间隔)
  3. intent-filter:广播接收过滤器(勇于接受广播)

2.2 demo_widget_info

用途:小组件(AppWidget)配置文件

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/appwidget_demo_layout"
    android:minWidth="@dimen/appwidget_demo_width"
    android:minHeight="@dimen/appwidget_demo_height"
    android:previewImage="@drawable/ic_appwidget_choiceness_4_preview"
    android:resizeMode="horizontal|vertical"
    android:updatePeriodMillis="1800000"
    android:widgetCategory="home_screen"/>

参数设置:

initialLayout:

属性指向用于定义应用微件布局的布局资源。

configure:

属性定义要在用户添加应用微件时启动以便用户配置应用微件属性的 Activity。
这是可选的(请阅读下文的创建应用微件配置 Activity)。

previewImage:

属性指定预览来描绘应用微件经过配置后是什么样子的,用户在选择应用微件时会看到该预览。如果未提供,则用户会看到应用的启动器图标。
此字段对应于 AndroidManifest.xml 文件的 <receiver> 元素中的 android:previewImage 属性。如需详细了解如何使用 previewImage,请参阅设置预览图片。此属性是在 Android 3.0 中引入的。

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)
}

参考文档:

  1. https://developer.android.google.cn/guide/topics/appwidgets?hl=zh-cn
  2. https://blog.csdn.net/sd19871122/article/details/123030955
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容