在 iOS 14 更新的时候,新增了一个功能——桌面小组件。当时我一看,妈呀,都说 Android 一直在模仿 iOS,但是这个小组件不一直都是 Android 有的功能吗,终于轮到被 iOS 模仿一回了。不过 App widgets 在 Android 上一直都不温不火,相信都会有部分的 Android Developer 不知道还有这个功能。不过不知道是不是由于 iOS 竞争的原因,在 Android 12 发布的新特性上,官方宣布对小组件进行一次升级,这也算是重视了一下这块功能。

个人之前一直很喜欢小组件的,像查看天气、查看备忘录、倒计时这些很简单很小但是却很常用的功能,相比于打开应用进入某个界面,小组件要方便多了。

既然官方都重视了一下,那我也来复习一下 App Widgets,这里也是相当于做个笔记,因为小组件的开发与日常的控件开发还是有很多不同的,做个笔记避免每次到处找。

一、定义一个小组件

以我的 Redmi Note 9 Pro 为例,要添加一个小组件的步骤依次为长按桌面空白处-->点击添加工具-->选择目标小组件拖动到合适位置:

那么,如何让我们的小组件出现在在这个列表里面呢?总共分四步:

  1. 定义 DemoWidget 继承自 AppWidgetProvider:

    package com.qinshou.appwidgetsdemoimport android.appwidget.AppWidgetManager
    import android.appwidget.AppWidgetProvider
    import android.content.Context
    import android.content.Intent
    import android.os.Bundleclass DemoWidget : AppWidgetProvider() {
    }
  2. 定义 DemoWidget 的布局 widget_demo:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".DemoWidget"></LinearLayout>
  3. 在 res/xml 下定义小组件的配置文件 widget_config_demo:

    <?xml version="1.0" encoding="utf-8"?>
    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"android:initialKeyguardLayout="@layout/widget_demo"android:initialLayout="@layout/widget_demo"android:minWidth="110dp"android:minHeight="110dp"android:previewImage="@mipmap/ic_launcher"android:resizeMode="horizontal|vertical"android:updatePeriodMillis="86400000"android:widgetCategory="home_screen"></appwidget-provider>
  4. AndroidManifest.xml 的 application 标签中声明小组件,这是最关键的一步,将刚才创建的文件和配置都关联起来,其实只要记住这一步,前三步就不会忘,看声明的时候缺少什么就补什么就行了:

    <receiverandroid:name=".DemoWidget"android:exported="true"android:label="Demo小组件"><intent-filter><action android:name="android.appwidget.action.APPWIDGET_UPDATE" /></intent-filter><meta-dataandroid:name="android.appwidget.provider"android:resource="@xml/widget_config_demo" />
    </receiver>

至此,我们就可以在小组件列表中找到我们自定义的小组件了,下面再详细解释这四步中需要注意的地方。

二、AppWidgetProvider

我们的 DemoWidget 是继承自 AppWidgetProvider,系统会在小组件被添加、更新、删除、属性改变时回调 AppWidgetProvider 对应的方法,所以我们可以覆写这些方法来实现我们的业务逻辑。

  • override fun onAppWidgetOptionsChanged(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int, newOptions: Bundle):

    小组件配置改变时回调,比如宽高被修改时。

  • override fun onDeleted(context: Context, appWidgetIds: IntArray):

    小组件被移除时回调。

  • override fun onDisabled(context: Context):

    最后一个小组件被移除时回调。

  • override fun onEnabled(context: Context):

    第一个小组件被添加时回调。

  • override fun onReceive(context: Context, intent: Intent):

    收到广播时回调,重要,基本上都是依靠该回调来处理逻辑。

  • override fun onRestored(context: Context, oldWidgetIds: IntArray, newWidgetIds: IntArray):

    这个回调我没有主动触发出来,根据 api 描述应该是在小组件被异常销毁后,重新恢复时回调,在该回调中恢复状态,类似于 Activity 的 onRestoreInstanceState(savedInstanceState: Bundle)。

  • override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray):

    小组件被更新时回调,重要,小组件会有固定刷新时间,可在配置文件中配置,但这个时间不完全准确,系统会在空闲的时候才发出更新小组件的广播,该回调中也可以处理我们的逻辑。

参考文档:

AppWidgetProvider  |  Android Developers

下面是将小组件添加到桌面,再移除,各回调的 log:

三、小组件的布局

小组件的布局需要注意的一点是布局中的控件只能使用 @RemoteView 修饰的布局和控件。

支持的布局有:

  • FrameLayout
  • LinearLayout
  • RelativeLayout
  • GridLayout

支持的控件有:

  • AnalogClock
  • Button
  • Chronometer
  • ImageButton
  • ImageView
  • ProgressBar
  • TextView
  • ViewFlipper
  • ListView
  • GridView
  • StackView
  • AdapterViewFlipper

但是不支持这些控件的子类,比如用 ImageView 是可以的,但是用 AppCompatImageView 就不可以。

参考文档:

构建应用微件  |  Android 开发者  |  Android Developers

四、小组件的配置

appwidget-provider 是小组件的配置,定义小组件的基本信息,如预览图、初始大小、更新时间等。

  • android:initialKeyguardLayout:被添加到锁屏界面时的初始化布局(Android 5.0 之后已经不允许添加小组件到锁屏界面,该属性可忽略)。
  • android:initialLayout:被添加到桌面时的初始化布局。
  • android:minWidth:小组件最小宽度,这个宽度不是精确宽度,因为系统桌面是相当于网格布局的,系统根据宽度来决定所占单元格多少。单元格数量与尺寸关系见下图。
  • android:minHeight:小组件最小高度,这个宽度不是精确高度,同最小宽度。
  • android:previewImage:小组件在组件列表中显示的预览图。
  • android:resizeMode:尺寸调整模式,为什么说宽高是最小宽高,因为小组件是可以让用户自行调整的,可选值有 horizontal(水平调整)、vertical(垂直调整),也可以两种模式都选。
  • android:updatePeriodMillis:小组件更新时间,单位毫秒,系统会根据这个间隔发出更新小组件的广播,AppWidgetProvider 的 onUpdate() 会回调。但系统并不能保证这个间隔时间的准确性,系统会在控件时更新。同时为了保证节省电量,系统建议更新间隔不应该太频繁,不应该小于 1 小时。
  • android:widgetCategory:小组件可以被添加的位置,可选值有 home_screen(桌面)、keyguard(锁屏,Android 5.0 之后已经不允许被添加到锁屏)、searchbox(不清楚)。

参考文档:

构建应用微件  |  Android 开发者  |  Android Developers

应用微件设计指南  |  Android 开发者  |  Android Developers

五、小组件的声明

上面三步是小组件的准备工作,最重要的一步是在清单文件中声明这个小组件,只有声明了该小组件,用户才可以在小组件列表中看到我们的组件,示例参考上面的代码。

  • name:指向小组件的逻辑实现类,即 AppWidgetProvider 的实现类。
  • label:小组件在组件列表中显示的名称。

声明小组件用的是 receiver 标签,说明它与广播有关系,在 receiver 标签中必须声明 intent-filter 用于接收小组件更新的广播。

<intent-filter><action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>

还必须声明 meta-data 元素用于关联小组件配置:

<meta-dataandroid:name="android.appwidget.provider"android:resource="@xml/widget_config_demo" />

其中 meta-data 的 name 属性为固定值 android.appwidget.provider,resource 表示配置文件所在位置。

六、小组件简单示例

知道如何显示一个小组件了,剩下的就是根据业务去处理逻辑了,只是修改小组件的控件,是没有在 Activity 或 Fragment 中修改 UI 方便的,由于小组件是 RemoteView,像点击事件这类简单的事情都只能通过广播间接完成,如果要加载网络图片,也没办法使用 Glide 这些我们常用的框架,下面就用一个简单示例来演示如何完成点击事件:

widget_demo.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#FFFFFFFF"android:gravity="center"android:orientation="vertical"tools:context=".DemoWidget"><TextViewandroid:id="@+id/text_view"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello World"android:textColor="#FF000000"android:textSize="18sp" /><ImageViewandroid:id="@+id/image_view"android:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@mipmap/ic_launcher" /><Buttonandroid:id="@+id/btn_update_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="修改文字" /><Buttonandroid:id="@+id/btn_hide_img"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="隐藏图片" />
</LinearLayout>

DemoWidget.kt:

package com.qinshou.appwidgetsdemoimport android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.util.Log
import android.view.View
import android.widget.RemoteViewsclass DemoWidget : AppWidgetProvider() {companion object {private const val TAG = "DemoWidget"private const val ACTION_UPDATE_TEXT = "updateText"private const val ACTION_HIDE_IMG = "hideImg"}override fun onReceive(context: Context, intent: Intent) {super.onReceive(context, intent)Log.i(TAG, "onReceive : " + "action--->" + intent.action)val appWidgetManager = AppWidgetManager.getInstance(context)val remoteViews = RemoteViews(context.packageName, R.layout.widget_demo)when (intent.action) {ACTION_UPDATE_TEXT -> {remoteViews.setTextViewText(R.id.text_view, "你好 世界")}ACTION_HIDE_IMG -> {remoteViews.setViewVisibility(R.id.image_view, View.INVISIBLE)}}// 更新小组件,重要,相当于把 RemoteView 同步一下appWidgetManager.updateAppWidget(ComponentName(context, DemoWidget::class.java), remoteViews)}override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {super.onUpdate(context, appWidgetManager, appWidgetIds)Log.i(TAG, "onUpdate")val remoteViews = RemoteViews(context.packageName, R.layout.widget_demo)remoteViews.setOnClickPendingIntent(R.id.btn_update_text, PendingIntent.getBroadcast(context, 0, Intent(context, DemoWidget::class.java).setAction(ACTION_UPDATE_TEXT), PendingIntent.FLAG_UPDATE_CURRENT))remoteViews.setOnClickPendingIntent(R.id.btn_hide_img, PendingIntent.getBroadcast(context, 0, Intent(context, DemoWidget::class.java).setAction(ACTION_HIDE_IMG), PendingIntent.FLAG_UPDATE_CURRENT))// 更新小组件,重要,相当于把 RemoteView 同步一下appWidgetManager.updateAppWidget(ComponentName(context, DemoWidget::class.java), remoteViews)}
}

效果如下:

七、总结

至此,我们可以简单的去使用 AppWidgets 去完成一些简单的事情了,像 iOS 系统那样的日历、天气那些只有展示没有交互的小组件,结合一些 api 可以轻松完成。但是如果要在 AppWidgets 中使用集合这样的功能,还需要了解更多的 api 和使用方法,限于篇幅会另外记录一篇博客。希望大家可以多利用小组件,方便自己的生活。

禽兽先生/AppWidgetsDemo

好记性不如烂笔头之 App widgets(一)相关推荐

  1. 好记性不如烂笔头之 App widgets(二)

    好记性不如烂笔头之 App widgets(一)_禽兽先生不禽兽的博客-CSDN博客 之前记录了 AppWidgets 的基本用法,当我的小组件中需要展示列表的时候,发现它的方式也跟普通的列表控件不一 ...

  2. 好记性不如烂笔头——Vite篇

    茶已备好,只待君来!感谢关注 前端点线面 (>‿<),本号定期推荐原创深度好文,帮助每一位在前端领域打拼的伙伴们走向前列,此外关注我获取最前沿知识点.海量学习资料.<前端百题斩> ...

  3. 进来在看《How Tomcat Works》这本书,将读书笔记贴在这里,好记性不如烂笔头。

    进来在看<How Tomcat Works>这本书,将读书笔记贴在这里,好记性不如烂笔头.  最简单的一个服务器,一个很简单的结果.  HttpServer构建ServerSocket,每 ...

  4. 好记性不如烂笔头:会议纪要本

    开发的大多数经历都用在思考代码如何写上,当你在进行某个项目时,突然开会又说要弄其他的项目,或者又是新的需求,需求错综复杂,多个项目交织在一起的时候容易乱. 开会时需要一个会议纪要本,会上讲了哪些关于你 ...

  5. 好记性不如烂笔头-笔记记录方法公开

    见字如面,我是东北码农. 俗话说,好记性不如烂笔头,在工作学习中记笔记是很重要的.今天介绍一下自己在工作中如何记录笔记,以及给大家安利一下win10自带的OneNote. 1.记录什么 1.1.流水账 ...

  6. 好记性不如烂笔头-linux学习笔记4apache相关知识

    好记性不如烂笔头-linux学习笔记4apache相关知识 apache 启动有2种模式 1是prefork模式,每个进程对应一个线程,如果是比较稳定的平台,那么prefork模式是worker模式 ...

  7. 好记性不如烂笔头——C++篇

    大家好,我是前端点线面,毕业于华中科技大学,非科班出身的一枚新时代农民工,现在是百度前端研发工程师,著有<前端百题斩>.数十篇学习思维导图(go.React.Redux.Vue.Vuex. ...

  8. 好记性不如烂笔头——Vuex篇

    俗话说的好"好记性不如烂笔头",今天秉承着后期复习方便的态度,整理了一份Vuex基础知识点,并以思维导图的方式呈现出来,方便跟老铁们一起查漏补缺. Vuex.png 一.基础 1. ...

  9. 涨知识!华为备忘录还能这样玩,难怪古人说好记性不如烂笔头

    涨知识!华为备忘录还能这样玩,难怪古人说"好记性不如烂笔头" 古人云:"好记性不如烂笔头!"这句话是我们从小一直听到大的,可是真的能够做到的,真的没有几位,毕竟 ...

最新文章

  1. 如何提取cocos iOS应用程序APP与游戏安装包里的资源与文件
  2. dot--向量或矩阵的点乘
  3. 求助啊,被STM32的CAN折磨的疯了
  4. 安装eclipse中html/jsp/xml editor插件以及改动html页面的字体
  5. 博士申请 | 美国杜克大学徐攀助理教授招收机器学习/人工智能全奖博士生
  6. 程序人生之我们的故事:十年如歌(9)
  7. 互斥锁属性PTHREAD_MUTEX_RECURSIVE
  8. 哈尔滨工业大学计算机培养计划,哈工大计算机科学与技术专业本科生培养方案.docx...
  9. vue实现首屏加载等待动画 避免首次加载白屏尴尬
  10. Codeforces Round #603 (Div. 2) E. Editor 线段树维护括号序列
  11. 学习 MySQL中导入 导出CSV
  12. 用WAP手机远程遥控电脑1
  13. Neo4j-import导入CSV的数据
  14. vue的第一份正式源码
  15. 分享5个苹果系统超实用的黑科技APP,个个都是精品
  16. 2021年11月软件设计师真题解析
  17. 大小写字母表(大小写字母表)
  18. 如何在 Adob​e Photoshop 中制作拉伸的风景?
  19. sklearn 中 pca.components_
  20. 中序和先序、中序和后序

热门文章

  1. 十万行代码——记我的ACM之路
  2. 【接口篇 / Wan】(7.0) ❀ 02. 配置路由器上网 ❀ FortiGate 防火墙
  3. 李一鹏:一份让我无法拒绝的offer | OneFlow U
  4. python分苹果问题_分苹果问题的C++和Python实现
  5. FactoryBean of Spring
  6. Qt下的国际化方法—翻译文件(.ts .qm文件)的使用
  7. 【Virtual Box】使用增强功能在Ubuntu系统实现共享文件夹、共享复制粘贴、主机间拖放文件
  8. 谷歌书签同步到gitee
  9. 最不该减负的,是孩子
  10. 8瓶水和3只小老鼠的故事(python解决)思路