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定时更新小部件。
  • 在应用内展示添加小部件弹窗。
相关推荐
C4rpeDime2 小时前
自建MD5解密平台-续
android
鲤籽鲲3 小时前
C# Random 随机数 全面解析
android·java·c#
m0_548514777 小时前
2024.12.10——攻防世界Web_php_include
android·前端·php
凤邪摩羯7 小时前
Android-性能优化-03-启动优化-启动耗时
android
凤邪摩羯7 小时前
Android-性能优化-02-内存优化-LeakCanary原理解析
android
喀什酱豆腐8 小时前
Handle
android
m0_748232929 小时前
Android Https和WebView
android·网络协议·https
m0_7482517210 小时前
Android webview 打开本地H5项目(Cocos游戏以及Unity游戏)
android·游戏·unity
m0_7482546612 小时前
go官方日志库带色彩格式化
android·开发语言·golang
zhangphil12 小时前
Android使用PorterDuffXfermode模式PorterDuff.Mode.SRC_OUT橡皮擦实现“刮刮乐”效果,Kotlin(2)
android·kotlin