好记性不如烂笔头之 App widgets(一)

好记性不如烂笔头之 App widgets(一),第1张

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

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

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

一、定义一个小组件

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

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

定义 DemoWidget 继承自 AppWidgetProvider:
package com.qinshou.appwidgetsdemo

import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.content.Intent
import android.os.Bundle

class DemoWidget : AppWidgetProvider() {
}

定义 DemoWidget 的布局 widget_demo:




在 res/xml 下定义小组件的配置文件 widget_config_demo:




AndroidManifest.xml 的 application 标签中声明小组件,这是最关键的一步,将刚才创建的文件和配置都关联起来,其实只要记住这一步,前三步就不会忘,看声明的时候缺少什么就补什么就行了:


    
        
    

    

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

二、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 修饰的布局和控件。

支持的布局有:

FrameLayoutLinearLayoutRelativeLayoutGridLayout

支持的控件有:

AnalogClockButtonChronometerImageButtonImageViewProgressBarTextViewViewFlipperListViewGridViewStackViewAdapterViewFlipper

但是不支持这些控件的子类,比如用 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 用于接收小组件更新的广播。


    

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

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

六、小组件简单示例

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

widget_demo.xml:




    

    

    

DemoWidget.kt:

package com.qinshou.appwidgetsdemo

import 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.RemoteViews

class 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

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/990668.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存