使用Compose开发Android桌面小组件Widget

278 阅读2分钟

​前言

Jetpack Glance 是基于 Jetpack Compose 运行时构建的框架,可以使用Compose API 开发和设计桌面小组件 widget。

正文

1.添加依赖
dependencies {
   ...
   //添加依赖
   implementation "androidx.glance:glance-appwidget:1.1.0"
   implementation "androidx.glance:glance-material3:1.1.0"
}

2.清单文件中注册
<receiver android:name=".MyAppWidgetReceiver"
    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/my_app_widget_info" />
</receiver>

3.添加Receiver文件
class MyAppWidgetReceiver : GlanceAppWidgetReceiver() {
    override val glanceAppWidget: GlanceAppWidget = MyAppWidget()
}

4.添加my_app_widget_info文件

在res/xml文件夹中创建my_app_widget_info文件,并配置

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
                    android:minWidth="100dp"//最小宽度
                    android:minHeight="50dp"//最大宽度
                    android:updatePeriodMillis="1800000"//刷新间隔,最低半个小时
                    android:resizeMode="horizontal|vertical"
                    android:initialLayout="@layout/glance_default_loading_layout">//初始布局
</appwidget-provider>

5.创建widget,并用Compose写界面

可以看到,导的包和Jetpack Compose的包是不同的,这个包内的api非常少

据官方文档上所写,是因为要将其映射为原生的RemoteViews

import androidx.glance.Button
import androidx.glance.layout.Column
import androidx.glance.layout.Row
import androidx.glance.text.Text

class MyAppWidget : GlanceAppWidget() {

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        provideContent {
            GlanceTheme {
                MyContent()
            }
        }
    }

    @Composable
    private fun MyContent() {
        Column(
            modifier = GlanceModifier.fillMaxSize()
                .background(GlanceTheme.colors.background),
            verticalAlignment = Alignment.Top,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text(text = "Where to?", modifier = GlanceModifier.padding(12.dp))
            Row(horizontalAlignment = Alignment.CenterHorizontally) {
                Button(
                    text = "Home",
                    onClick = actionStartActivity<MyActivity>()
                )
                Button(
                    text = "Work",
                    onClick = actionStartActivity<MyActivity>()
                )
            }
        }
    }
}

6.ui中的一些问题

由于不能写阴影,因此使用了shape叠加的方式来实现类似的阴影效果(阴影层是半透明的,并且前景层加了一点偏移):

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 阴影层 -->
    <item>
        <shape android:shape="rectangle">
            <solid android:color="#1B707070"/> <!-- 阴影颜色和透明度 -->
            <corners android:radius="@dimen/dp4"/> <!-- 圆角半径 -->
        </shape>
    </item>
    <!-- 前景层 -->
    <item android:top="@dimen/dp0_5" android:left="@dimen/dp0_5" android:bottom="@dimen/dp1" android:right="@dimen/dp1">
        <shape>
            <solid android:color="@color/ps_color_white"/>
            <corners android:radius="@dimen/dp4"/>
        </shape>
    </item>
</layer-list>

使用Modifier或给Image设置图片:

Image(ImageProvider(R.drawable.widget_no_data), null)

给控件设置点击跳转到Activity:

        Button(
            text = "Home",
            onClick = actionStartActivity<MyActivity>()
        )
或
GlanceModifier.clickable(actionStartActivity<MyActivity>())//注意这里用的是小括号,因为方法返回的是一个Action对象

7.刷新widget的ui
MyAppWidget().updateAll(context)//子线程中

ps:由于widget至少半小时才能刷新第二次,所以建议放在app回到桌面时在进行刷新,比如使用如下方式判断离开了app:

//Application中
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
            override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}

            override fun onActivityStarted(activity: Activity) {}

            override fun onActivityResumed(activity: Activity) {
                activeCount++
            }

            override fun onActivityPaused(activity: Activity) {
                activeCount--
                if (activeCount <= 0) {
                    activeCount = 0
                    //app切换到了后台
                }
            }

            override fun onActivityStopped(activity: Activity) {}
            override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
            override fun onActivityDestroyed(activity: Activity) {}
        })

总结

方便,但只方便了一点

基本只支持几个compose的api,像阴影,圆角什么的都不支持(因此使用shape来实现)

参考: Jetpack Glance | Jetpack Compose | Android Developers

对Kotlin或KMP感兴趣的同学可以进Q群 101786950

如果这篇文章对您有帮助的话

可以扫码请我喝瓶饮料或咖啡(如果对什么比较感兴趣可以在备注里写出来)