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定时更新小部件。
  • 在应用内展示添加小部件弹窗。
相关推荐
恋猫de小郭13 小时前
Android Studio 正式版 10 周年回顾,承载 Androider 的峥嵘十年
android·ide·android studio
aaaweiaaaaaa16 小时前
php的使用及 phpstorm环境部署
android·web安全·网络安全·php·storm
工程师老罗18 小时前
Android记事本App设计开发项目实战教程2025最新版Android Studio
android
pengyu1 天前
系统化掌握 Dart 编程之异常处理(二):从防御到艺术的进阶之路
android·flutter·dart
消失的旧时光-19431 天前
android Camera 的进化
android
基哥的奋斗历程1 天前
Openfga 授权模型搭建
android·adb
Pakho love1 天前
Linux:文件与fd(被打开的文件)
android·linux·c语言·c++
勿忘初心912 天前
Android车机DIY开发之软件篇(九) NXP AutomotiveOS编译
android·arm开发·经验分享·嵌入式硬件·mcu
lingllllove2 天前
PHP中配置 variables_order详解
android·开发语言·php
消失的旧时光-19432 天前
Android-音频采集
android·音视频