【Jetpack Compose】小组件Glance正式版发布啦

Glance是专门用于Compose的小组件框架,Glance提供了一套单独的Compose可组合项,可以帮助开发者使用更少的代码构建主屏幕的响应式小组件。

Glance是在2021年12月15日发布的第一个预览版,经过将近两年的时间终于在2023年9月6日发布了第一个正式版本,目前开发者可以在生产环境下正式使用Glance来构建应用的小组件了,此正式版本需要compileSdk为34的条件下使用。

接下来本文将详细介绍Glance是如何在Compose之上构建小组件的知识。

添加Glance依赖

Glance需要在Compose的项目中应用,所以我们需要先在app/build.gradle文件中添加Compose的配置

ini 复制代码
android {
    namespace = "com.t.composeglance"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.t.composeglance"
        minSdk = 24
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables {
            useSupportLibrary = true
        }
    }
  	...
    buildFeatures {
        compose = true
    }
    composeOptions {
        kotlinCompilerExtensionVersion = "1.4.3"
    }
}

然后在dependencies模块中添加Glance的依赖

scss 复制代码
implementation("androidx.glance:glance-appwidget:1.0.0")

创建简单的小组件

我们先通过GlanceAppWidget创建处要展示的组件布局

kotlin 复制代码
class GlanceWidgetDemo : GlanceAppWidget() {
    override suspend fun provideGlance(context: Context, id: GlanceId) {
        provideContent {
            Column(
                modifier = GlanceModifier.fillMaxSize()
                    .background(Color.White),
                horizontalAlignment = Alignment.Horizontal.CenterHorizontally,
                verticalAlignment = Alignment.Vertical.CenterVertically
            ) {
                Text(text = "First Glance")
                Image(
                    provider = ImageProvider(resId = R.drawable.ic_launcher_foreground),
                    contentDescription = ""
                )
            }
        }
    }
}

GlanceAppWidget是一个抽象类,需要继承它并且复写provideGlance()方法,通过方法名可以得知它就是提供小组件的地方,然后在方法内部调用provideContent()方法,provideContent()是一个GlanceAppWidget的扩展方法,唯一的参数是一个content,此参数使用@Composable和@GlanceComposable注解修饰,表示它是一个可组合项,GlanceComposable是小组件特殊的注解,内部的可组合项都是需要使用Glance提供的特殊可组合项。

我们在provideContent()方法内部创建了一个Column行,内部定义了一个Text和Image,注意这里的Column、Text和Image都是Glance包下的,不是我们之前compose包中的组件,他们的包位于androidx.glance之下:

arduino 复制代码
import androidx.glance.layout.Column
import androidx.glance.text.Text
import androidx.glance.Image

大家在引入包的时候要注意千万别引入错了。

定义好GlanceAppWidget之后,我们还需要定义GlanceAppWidgetReceiver,它是一个广播对象,用于将定义好的GlanceAppWidget提供给系统,这样系统就会知道该展示哪个组件。

kotlin 复制代码
class GlanceDemoReceiver : GlanceAppWidgetReceiver() {

    override val glanceAppWidget: GlanceAppWidget
        get() = GlanceWidgetDemo()
}

此类还是比较简单,主要就是复写glanceAppWidget对象,并将上一步定义好的GlanceWidgetDemo传给它即可。

然后还需要将此小组件广播在Manifest文件中注册一下

ini 复制代码
<receiver
    android:name=".glance.GlanceDemoReceiver"
    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/glance_config" />
</receiver>

APPWIDGET_UPDATE是小组件更新时发送的action,meta-data中name直接填写android.appwidget.provider即可,name是固定的,下面的resouce为小组件的配置文件,可在此文件中配置小组件的宽度、高度、更新频率(更依赖于系统)和拉伸方向等信息。

ini 复制代码
# glance_config

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/glance_default_loading_layout"
    android:minWidth="200dp"
    android:minHeight="200dp" />

我们在配置文件中定义了初始化的布局,这里采用的是默认的loading,然后定义最小宽度和高度为200dp,在API 31的条件下,可以使用targetCellHeight和targetCellWidth直接设置宽高所占格子数,能更好的根据组件内容定义小出显示的大小。

下面我们看看具体的小组件效果

这里的测试机为红米手机,小组件的展示需要长按屏幕添加小组件,然后在小组件中选择我们自定义的返回到主屏幕就可以看到效果了。

使用Glance处理交互

Glance提供了一套Action来简化小组件的交互流程,Action可以自定义用户执行的操作,并且可以响应该Action从而执行对应的逻辑,Glance预定义了一些特殊的操作比如启动某个Activity、启动某个Service和发送某个Broadcast,除了这些操作外还可以根据ActionCallback处理一些特殊的操作。

启动某个Activity
ini 复制代码
provideContent {
    Column(
        modifier = GlanceModifier.fillMaxSize()
            .background(Color.White),
        horizontalAlignment = Alignment.Horizontal.CenterHorizontally,
        verticalAlignment = Alignment.Vertical.CenterVertically
    ) {
        Text(text = "First Glance")
        Image(
            provider = ImageProvider(resId = R.drawable.ic_launcher_foreground),
            contentDescription = ""
        )
        Button(text = "启动Activity", onClick = actionStartActivity<MainActivity>())
        Spacer(modifier = GlanceModifier.height(10.dp))
    }
}

我们在之前的组件布局中添加一个Button,然后在onClick方法中执行actionStartActivity(),以此来通过组件中按钮启动MainActivity,下面来看看实际效果是否能达到

启动某个Service

再了解了启动Activity之后,通过Glance启动Service就比较简单了,只需要将actionStartActivity()换成actionStartService()即可,也可通过下方Intent、ComponentName等方法启动,所有的方法都有一个isForegroundService参数,表示是否是前台服务,默认为false,按需赋值。

ini 复制代码
actionStartService(intent: Intent, isForegroundService: Boolean = false)

actionStartService(
    componentName: ComponentName,
    isForegroundService: Boolean = false
)

<reified T : Service> actionStartService(
    isForegroundService: Boolean = false
)
发送某个Broadcast

发送指定的广播可通过actionSendBroadcast()方法,也可以传入具体的Intent或者ComponentName,这个和Service基本一致

css 复制代码
<reified T : BroadcastReceiver> actionSendBroadcast()

actionSendBroadcast(componentName: ComponentName)

actionSendBroadcast(intent: Intent)

大家可以选择合适的方式来发送Broadcast。

运行动作回调ActionCallback

除了上述所说的启动具体动作外,还可以运行Glance的ActionCallback来传入一些自定义Action,这样就可以根据不同的Action执行特殊的逻辑了。

kotlin 复制代码
Button(
    text = "发送自定义Action", onClick = actionRunCallback<GlanceActionCallback>()
)

class GlanceActionCallback : ActionCallback {
    override suspend fun onAction(
        context: Context, glanceId: GlanceId, parameters: ActionParameters
    ) {
        Log.d("taonce", "onAction")
    }
}

我们在Button的onClick事件中调用了actionRunCallback()方法,此方法用于发送一个GlanceActionCallback事件,GlanceActionCallback实现了ActionCallback接口,我们可以在此事件的onAction()方法中单独处理对应的操作。

除了actionRunCallback()这种方法发送Action以外,还可以Callback的参数中添加自定义的ActionParameters,然后在onAction()内部解析parameters。

kotlin 复制代码
Button(
    text = "发送自定义Action", onClick = actionRunCallback<GlanceActionCallback>(
        actionParametersOf(ActionParameters.Key<String>("custom") to "ActionCallback")
    )
)

class GlanceActionCallback : ActionCallback {
    override suspend fun onAction(
        context: Context, glanceId: GlanceId, parameters: ActionParameters
    ) {
        val action = parameters[ActionParameters.Key<String>("custom")]
				Log.d("taonce", "onAction paramters action: $action")
    }
}

ActionParameters可以采用actionParametersOf方法定义,内部需要传入一个键值对,然后在解析的时候直接通过ActionParameters[Key]获取键值对对应的值即可。

通过State更新小组件界面

在创建小组件章节我们提到了Glance中引用的可组合项都是Glance包下的,并没有采用compose原有的一些可组合项,此时如果我们想更新小组件界面上的值是否还是可以使用State来更新呢?下面我们通过一个计数加减的例子来看看State更新小组件的可组合项。

ini 复制代码
override suspend fun provideGlance(context: Context, id: GlanceId) {
    provideContent {
        val count = remember {
            mutableStateOf(1)
        }
        Column(
            modifier = GlanceModifier.fillMaxSize()
                .background(Color.White),
            horizontalAlignment = Alignment.Horizontal.CenterHorizontally,
            verticalAlignment = Alignment.Vertical.CenterVertically
        ) {
            Text(text = "First Glance Count: ${count.value}")
            Image(
                provider = ImageProvider(resId = R.drawable.ic_launcher_foreground),
                contentDescription = ""
            )
            Spacer(modifier = GlanceModifier.height(10.dp))
            Button(
                text = "Count+1", onClick = {
                    count.value++
                }
            )
            Spacer(modifier = GlanceModifier.height(10.dp))
            Button(
                text = "Count-1", onClick = {
                    count.value--
                }
            )
        }
    }
}

我们在provideContent{}中创建了一个count对象,然后小组件的底部两个按钮分别对count做加和减运算,顶部的Text显示的值正是count.value,预期结果应该是Text随着按钮的点击分别在加减变化,下面我们运行代码看看具体效果:

通过录屏可以看出,即使Glance的可组合项是单独的一套,但是我们在Compose中使用的State依旧适用,这样我们就可以使用State来更新界面。

到这为止Glance的基本内容就介绍完了,当然还有很多其它特性没有全部介绍完,感兴趣的可以在 官方文档中了解更多~

关于我

我是Taonce,如果觉得本文对你有所帮助,帮忙关注、赞或者收藏三连一下,谢谢~

相关推荐
Estar.Lee1 小时前
查手机号归属地免费API接口教程
android·网络·后端·网络协议·tcp/ip·oneapi
温辉_xh2 小时前
uiautomator案例
android
工业甲酰苯胺3 小时前
MySQL 主从复制之多线程复制
android·mysql·adb
少说多做3433 小时前
Android 不同情况下使用 runOnUiThread
android·java
Estar.Lee4 小时前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
找藉口是失败者的习惯5 小时前
从传统到未来:Android XML布局 与 Jetpack Compose的全面对比
android·xml
Jinkey6 小时前
FlutterBasic - GetBuilder、Obx、GetX<Controller>、GetxController 有啥区别
android·flutter·ios
大白要努力!8 小时前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
天空中的野鸟9 小时前
Android音频采集
android·音视频
小白也想学C10 小时前
Android 功耗分析(底层篇)
android·功耗