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,如果觉得本文对你有所帮助,帮忙关注、赞或者收藏三连一下,谢谢~