Android小部件APP Widget开发

Android很早就支持了小部件(APP Widget),但貌似关注的人比较少。

开发桌面小部件和把大象放进冰箱一样,都是需要分三步

  • 第一步,创建声明小部件信息的XML,告诉系统这个小部件长怎么样和一些配置信息。
  • 第二步,创建BroadcastReceiver,接收小部件的各种事件(添加,删除,更新等)。
  • 第三步,创建View,将创建的View更新在桌面上。

声明小部件信息的XML

在项目的res/xml/ 目录下面创建一个app_widget_info.xml,使用 <appwidget-provider> 标签,类似写布局一样,将小部件的信息声明一下,告诉系统这个小部件长怎么样和一些配置信息。。

例:

xml 复制代码
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="110dp"
    android:minHeight="110dp"
    android:targetCellWidth="2"
    android:targetCellHeight="2" 
    android:description="@string/hello_widget_description"
    android:previewImage="@drawable/hello_widget_preview_img"
    android:initialLayout="@layout/hello_widget_initial_layout"
    android:previewLayout="@layout/hello_widget_preview_layout"
    android:updatePeriodMillis="86400000" />

minWidth和minHeight

设定小部件的宽高,系统会根据最接近的网格尺寸调整宽高。

例如:桌面的网格大小为50x50,设置的宽高为110x110,最终展示的宽高为100x100,对应桌面的2x2。

targetCellWidth和targetCellHeight

代表这个小部件宽度占多少个格子,高度占多少个格子。

Android12及以上支持,如果同时设置了minWidth和minHeight,会优先使用这个。

description

在添加小部件的界面,可以看到这个描述文字,12及以上才支持。

initialLayout

初始化的布局,系统通知需要更新小部件的时候,会将这个xml中的view作为初始化布局展示。

注意:只能支持RemoteViews所支持的View

previewImage和previewLayout

previewImage,使用图片作为预览,在添加界面展示。

previewLayout,使用xml布局作为预览,在添加界面展示。仅Android12及以上支持。只能支持RemoteViews所支持的View

如果同时设置了这两个属性,在Android12及以上会使用previewLayout

updatePeriodMillis

定时更新间隔,单位毫秒,最低为30分钟。

注意:因为各个厂商的电池优化策略,可能设置的间隔并不是很管用。除了关闭APP的电池优化策略,暂时没有很好的办法。

这第一步只是创建了信息的声明文件,还没有地方用到,下一步的最后将会用到这个文件。

创建BroadcastReceiver

系统会将小部件的更新和生命周期以发送广播的方式通知到我们,所以我们需要创建一个BroadcastReceiver来接收这些事件。

Android已经有现成的BroadcastReceiver了,叫AppWidgetProvider,里面封装了一些通用的逻辑,方便我们使用,我们直接继承AppWidgetProvider做一些我们关注的事情就行。

继承AppWidgetProvider

创建类 HelloWidgetProvider 继承 AppWidgetProvider

kotlin 复制代码
class HelloWidgetProvider : AppWidgetProvider() {
    /**
     * 小部件创建、定时更新触发等时机会调用。
     */
    override fun onUpdate(context: Context?, appWidgetManager: AppWidgetManager?, appWidgetIds: IntArray?) {}

    /**
     * 小部件被第一次创建的时候会调用,可能会创建多个,只有第一个的时候会调用。
     */
    override fun onEnabled(context: Context?) {}

    /**
     * 小部件被删除的时候会被调用。
     */
    override fun onDeleted(context: Context?, appWidgetIds: IntArray?) {}

    /**
     * 最后一个小部件被删除的时候调用,与onDeleted的区别是,onDeleted每次删除都会调用。
     */
    override fun onDisabled(context: Context?) {}

}

创建了BroadcastReceiver当然是需要在manifest中声明的。

将HelloWidgetProvider声明在manifest中

AndroidManifest.xml中声明我们刚才创建的HelloWidgetProvider

然后在Provider中添加刚才创建的app_widget_info.xml

HelloWidgetProvider:用来接收系统事件的,例如小部件的生命周期等等。

app_widget_info.xml:用来声明小部件的一些信息,用户在小组件添加页面可以预览。

xml 复制代码
<receiver
    android:name=".HelloWidgetProvider"
    android:exported="false"
    android:label="小部件的名字,添加界面可见">

    <intent-filter>
        <!-- 接收系统事件,例如生命周期等-->
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>

    <!--声明appwidget的信息-->
    <meta-data
        android:name="android.appwidget.provider"
        android:resource="@xml/appwidget_info" />
    
</receiver>

好了,我们已经可以成功的声明小部件的信息了,这个时候可以添加小部件了,不过只能展示初始化界面,也就是initialLayout的内容。

最后一步,就是更新桌面小部件,将我们想要的UI展示在桌面上。

实际场景中,我们可能会调用接口获取数据,然后更新小部件的UI。

创建View并展示

这里介绍的是使用XML的方式创建并展示小部件,如果要使用compose可以参考Jetpack Glance,前段时间刚发布的稳定版本。

更新小部件可以使用AppWidgetManager#updateAppWidget(ComponentName,RemoteViews)

第一个参数ComponentName为小部件的BroadcastReceiver,也就是刚才创建的HelloWidgetProvider

第二个参数RemoteViews为展示的View,具体使用可以参考文档

RemoteViews 支持的布局:

  • AdapterViewFlipper
  • FrameLayout
  • GridLayout
  • GridView
  • LinearLayout
  • ListView
  • RelativeLayout
  • StackView
  • ViewFlipper

支持的控件:

  • AnalogClock
  • Button
  • Chronometer
  • ImageButton
  • ImageView
  • ProgressBar
  • TextClock
  • TextView

Android 12及以上支持的控件:

  • CheckBox
  • RadioButton
  • RadioGroup
  • Switch

接下来我们在刚才写的HelloWidgetProvider中的onUpdate加入更新小部件UI的逻辑,代码如下。

kotlin 复制代码
    /**
     * 小部件创建、定时更新触发等时机会调用。
     */
    override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
        GlobalScope.launch {
            val provider = ComponentName(context, HelloWidgetProvider::class.java)
            val remoteViews = RemoteViews(context.packageName, R.layout.hello_widget_layout)
            val textFromRemote = getTextFromRemote()
            remoteViews.setTextViewText(R.id.text, textFromRemote)
            //更新小部件
            appWidgetManager.updateAppWidget(provider, remoteViews)
        }
    }

    private suspend fun getTextFromRemote(): String {
        //假装是网络延迟
        delay(5000)
        return "Hello APP Widget"
    }

hello_widget_layout.xml如下

xml 复制代码
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:background="@color/white">

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="这个是标题"
        android:textColor="@color/black" />
</FrameLayout>

OK,运行程序查看效果。

到这里已经实现了一个简单的小部件了,接下来可能需要根据具体业务场景实现比较复杂的Widget。

更多

  • 实现正方形的小部件。
  • 使用WorkManager定时更新小部件。
  • 在应用内展示添加小部件弹窗。
相关推荐
COSMOS_*5 小时前
2025最新版 Android Studio安装及组件配置(SDK、JDK、Gradle)
android·ide·jdk·gitee·android studio
jian110585 小时前
android studio Profiler性能优化,查看内存泄漏
android·性能优化·android studio
建群新人小猿7 小时前
陀螺匠企业助手——组织框架图
android·java·大数据·开发语言·容器
TheNextByte18 小时前
如何将文件从Android无线传输到 iPad
android·ios·ipad
赫萝的红苹果8 小时前
实验探究并验证MySQL innoDB中的各种锁机制及作用范围
android·数据库·mysql
叶落无痕529 小时前
Android Studio 2024.3.1 连接夜神模拟器
android·ide·android studio
玲子的猫9 小时前
安卓原生开发实现图片双指放大预览功能
android
2501_9151063210 小时前
如何在iPad上高效管理本地文件的完整指南
android·ios·小程序·uni-app·iphone·webview·ipad
似霰10 小时前
AIDL Hal 开发笔记5----实现AIDL HAL
android·framework·hal
2501_9151063211 小时前
iOS 成品包加固,在只有 IPA 的情况下,能做那些操作
android·ios·小程序·https·uni-app·iphone·webview