Android 12桌面小组件

7,810 阅读3分钟

前言

Anroid上的桌面小组件很早以前就有了,相比iOS的小组件一出来就大火不同,Android桌面小组件一直很难用,最近看到了Google微信号推送的文章:

我以为Android小组件间要崛起了!!开心的去看了[官网demo](user-interface-samples/AppWidget at main · android/user-interface-samples (github.com))

在Android12上主要更新了可调整窗口大小,不同主题下的效果,和一些圆角边距等细节设置,看起来还是不太聪明的样子

快速上手Widget

桌面小组件被Google成为微件:AppWidget,查看官网文档

右键菜单栏中创建Widget组件,其会自动创建相关类: image.png

我们以创建一个TODO组件为例

相关类

  • AppWidgetProvider

组件类,通过广播与其连接,可以通过onUpdate,onEnableonDelete等方法与组件进行通信。 这里我们新建TodoWidget:

class TodoWidget : AppWidgetProvider() {
    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {

    }

    override fun onEnabled(context: Context) {
    
    }

    override fun onDisabled(context: Context) {
    
    }
}

同时我们需要在Mainfest.xml中注册:

<receiver
    android:name=".TodoWidget"
    android:exported="true">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>

    <meta-data
        android:name="android.appwidget.provider"
        android:resource="@xml/todo_widget_info" />
</receiver>

widgetProvider继承自广播,本身可以通过onReceive()来接受消息

  • AppWidgetProviderInfo

提供了组件的各种信息,尺寸、布局、预览等信息,以xml文件的形式创建

在资源文件下新建xml目录:

image.png

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/app_widget_description"
    android:initialKeyguardLayout="@layout/todo_widget_min"
    android:initialLayout="@layout/todo_widget_min"
    android:minWidth="110dp"
    android:minHeight="110dp"
    android:previewImage="@drawable/example_appwidget_preview"
    android:previewLayout="@layout/todo_widget_min"
    android:resizeMode="horizontal|vertical"
    android:targetCellWidth="1"
    android:targetCellHeight="1"
    android:updatePeriodMillis="86400000"
    android:widgetCategory="home_screen" />

创建布局

Widget组件布局基于:RemoteViews有很多特殊性,这也是目前Android小窗口的局限,很多组件都无法使用,交互也表困难。

RemoteViews 可以支持以下布局类:

  • FrameLayout
  • LinearLayout
  • RelativeLayout
  • GridLayout

以及以下微件类:

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

对于我们常见的RecyclerView、约束布局都不支持!

布局代码可以看Demo,这里说下狂口调整时候的布局变化,调整窗口的大小在Android12上才可用

我们可以在WidgetProvider类中的onUpdate中更新组件:

override fun onUpdate(
    context: Context,
    appWidgetManager: AppWidgetManager,
    appWidgetIds: IntArray
) {
    // There may be multiple widgets active, so update all of them
    for (appWidgetId in appWidgetIds) {
        updateAppWidget(context, appWidgetManager, appWidgetId)
    }
}

创建多个RemoteView,我这里创建了三个不同布局对应不同尺寸:

@SuppressLint("RemoteViewLayout")
internal fun updateAppWidget(
    context: Context,
    appWidgetManager: AppWidgetManager,
    appWidgetId: Int
) {
    val remoteViewsMin = RemoteViews(
        context.packageName,
        R.layout.todo_widget_min
    )

    val remoteViewsNormal = RemoteViews(
        context.packageName,
        R.layout.todo_widget_normal
    )

    val remoteViewsMax = RemoteViews(
        context.packageName,
        R.layout.todo_widget_max
    )

    /*不同大小控件对应不同布局*/
    val viewMapping: MutableMap<SizeF, RemoteViews> = mutableMapOf()
    viewMapping[SizeF(180.0f, 110.0f)] = remoteViewsMin
    viewMapping[SizeF(230.0f, 180.0f)] = remoteViewsNormal
    viewMapping[SizeF(270.0f, 300.0f)] = remoteViewsMax

    /*只有在Android12以上版本才支持*/
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        appWidgetManager.updateAppWidget(appWidgetId, RemoteViews(viewMapping))
    }else{
        appWidgetManager.updateAppWidget(appWidgetId, remoteViewsNormal)
    }

}

查看效果

长按app图标就可在桌面添加组件,不同系统操作方式不一样。

Screenshot_20210928-172300.png

Screenshot_20210928-172311.png

通信交互

打开App,在Activity做出响应

widget和宿主app不属于同一进程,可以通过PendingIntent通信,和传统的Intent有点不一样,简言之:PendingIntent含有app的context,可以在外部进程打开app进程,PendingIntent不是立即执行的。

RemoteView设置点击事件

setOnClickPendingIntent(R.id.add, createPendingIntent(context,R.id.add))

createPendingIntent()是一个将id传给activity的方法,你也可以设置其他信息,在activity做出响应

private fun createPendingIntent(context: Context,@IdRes id:Int):PendingIntent{
    /*打开APP intent*/
    val activityIntent = Intent(context, MainActivity::class.java).apply {
        setData(Uri.parse("harvic:$id"))
        flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
    }
    val appOpenIntent = PendingIntent.getActivity(
        context,
        1,
        activityIntent,
        PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
    )
    return appOpenIntent
}

在onReceive中处理:

和上述的基本一样,根据id来处理不同的点击事件

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    val clickIntent = PendingIntent.getBroadcast(
        context,
        2,
        Intent(context, TodoWidget::class.java).apply {
            putExtra(EXTRA_VIEW_ID, R.id.title)
        },
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
    )
    setOnClickPendingIntent(R.id.title,clickIntent)
}

最后

点击查看:项目代码,欢迎大佬们的Star

感觉Andorid桌面小组件依旧不够完善,和以前相比也没啥大大的改变。=.=

图片名称

之前的文章介绍了网络封装、组件化、基础工具等,有兴趣的可以查看:

还有一些关于UI的基础工具: