Glance创建widget

要在Android开发中创建一个widget,你可以使用Glance,这是由Google推出的一个全新的Jetpack库,专门用于构建简洁、美观且响应式的Android Widget。Glance提供了一个声明式的API来创建小部件,使得开发更加简单和直观。

添加依赖

Groovy 复制代码
glance-appwidget = { group = "androidx.glance", name = "glance-appwidget", version.ref = "glance" }

清单说明

XML 复制代码
<receiver
            android:name=".receiver.ProgressWidgetReceiver"
            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/progress_widget_info" />
        </receiver>

其中progress_widget_info.xml

XML 复制代码
?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="110dp"
    android:minHeight="110dp"
    android:updatePeriodMillis="0"
    android:initialLayout="@layout/widget_placeholder"
    android:resizeMode="horizontal|vertical"
    android:widgetCategory="home_screen"/>

widget_placeholder.xml 初始布局

XML 复制代码
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="8dp">
    <!-- Glance 会显示图片,不需要复杂内容 -->
    <ImageView
        android:id="@+id/widget_image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop" />
</FrameLayout>

定义receiver

复制代码
class ProgressWidgetReceiver : GlanceAppWidgetReceiver(){
    override val glanceAppWidget = WorkoutPlanProgress2X2()
}

WorkoutPlanProgress2X2

Kotlin 复制代码
class WorkoutPlanProgress2X2() : GlanceAppWidget() {

   //通过prefs获取plan具体信息然后更新界面

    override suspend fun provideGlance(
        context: Context,
        id: GlanceId
    ) {
        provideContent {
            val prefs = currentState<Preferences>()
            val planJson = prefs[WorkoutPlanPrefs.PLAN_JSON] ?: ""

            val plan = if (planJson.isNotEmpty()) {
                Gson().fromJson(planJson, Plan::class.java)
            } else null
            WorkoutPlanProgressCard2X2(plan)
        }
    }
}

以compose方式些widget,与普通的Android view少许不同

Kotlin 复制代码
import android.graphics.BitmapFactory
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.glance.GlanceModifier
import androidx.glance.Image
import androidx.glance.ImageProvider
import androidx.glance.LocalContext
import androidx.glance.layout.Alignment
import androidx.glance.layout.Box
import androidx.glance.layout.Column
import androidx.glance.layout.Row
import androidx.glance.layout.Spacer
import androidx.glance.layout.fillMaxWidth
import androidx.glance.layout.height
import androidx.glance.layout.size
import androidx.glance.layout.width
import androidx.glance.text.FontWeight
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import androidx.glance.unit.ColorProvider
import com.demo.common.network.bean.Plan
import java.io.File


@Composable 
fun WorkoutPlanProgressCard2X2(plan: Plan?) {
    val context = LocalContext.current
    if (plan != null) {
        // create your AppWidget here
        val cacheFile = File(context.cacheDir, "progress_widget.png")
        val imageProvider = if (cacheFile.exists()) {
            // 读取 Bitmap,并传给 ImageProvider
            val bitmap = BitmapFactory.decodeFile(cacheFile.absolutePath)
            ImageProvider(bitmap)
        } else {
            // 如果没有 bitmap,显示一个占位(Text)
            null
        }
        WorkoutPlanProgressDetailCard2X2(plan, imageProvider)
    } else {
        WorkoutPlanProgressEmptyCard2X2()
    }
}

@Composable
fun WorkoutPlanProgressDetailCard2X2(plan: Plan, imageProvider: ImageProvider?) {
    GradientCard {
        // 顶部图标 + 标题
        Row(verticalAlignment = Alignment.CenterVertically) {
            Image(
                provider = ImageProvider(com.healthfitness.widget.R.drawable.icon),
                contentDescription = null,
                modifier = GlanceModifier.size(16.dp)
            )
            Spacer(GlanceModifier.width(6.dp))
            Text(
                text = "Workout Plan"
            )
        }

        Spacer(GlanceModifier.height(12.dp))

        Row(
            verticalAlignment = Alignment.CenterVertically,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {

            Box(contentAlignment = Alignment.Center) {
                if (imageProvider != null) {
                    Image(
                        provider = imageProvider,
                        contentDescription = "progress",
                        modifier = GlanceModifier.size(80.dp)
                    )
                }
                Column(horizontalAlignment = Alignment.CenterHorizontally) {
                    Text(
                        text = "${(plan.progress ?: 0).toInt()}%",
                        style = TextStyle(fontSize = 20.sp, fontWeight = FontWeight.Bold)
                    )
                    Text(
                        text = "Progress",
                        style = TextStyle(
                            fontSize = 12.sp,
                            color = ColorProvider(Color(0x4D000000))
                        )
                    )

                }
            }
        }

        Spacer(GlanceModifier.height(12.dp))

        // 下方 remain + missed
        Row(
            modifier = GlanceModifier.fillMaxWidth(),
        ) {
            Text(
                text = "Remain: ${(plan.total - plan.finished).toInt()}",
                style = TextStyle(fontSize = 12.sp),
            )
            Spacer(modifier = GlanceModifier.defaultWeight())
            Text(
                text = "total: ${plan.total.toInt()}",
                style = TextStyle(fontSize = 12.sp)
            )
        }
    }
}

@Composable
fun WorkoutPlanProgressEmptyCard2X2() {
    Image(
        provider = ImageProvider(com.healthfitness.widget.R.drawable.icon_add),
        contentDescription = null,
        modifier = GlanceModifier.size(70.dp)
    )
}

如何更新widget信息呢

Kotlin 复制代码
 suspend fun updateAllWidgets(context: Context, plan: Plan?) {
            val manager = GlanceAppWidgetManager(context)
            val glanceIds = manager.getGlanceIds(WorkoutPlanProgress2X2::class.java)
            val widget = WorkoutPlanProgress2X2()
            val json = plan?.let { Gson().toJson(it) } ?: ""

            glanceIds.forEach { id ->
                updateAppWidgetState(context, PreferencesGlanceStateDefinition, id) { prefs ->
                    prefs.toMutablePreferences().apply {
                        this[WorkoutPlanPrefs.PLAN_JSON] = json
                    }
                }
                widget.update(context, id)
            }
        }

通过updateAppWidgetState将bean通过JSON string传递

相关推荐
墨天梦4 小时前
D19_系统分享与文件URI
gitee·kotlin
Cc_Debugger2 天前
【从别人gitee仓库复制到自己仓库下(所有分支)】
gitee
7177773 天前
支持私有部署的 DevOps 平台推荐:Gitee 专业版与主流开源方案对比
gitee·开源·devops
7177773 天前
中小团队 CI/CD 工具选哪个:2026 年主流平台对比与 Gitee 方案解析
ci/cd·gitee
7177775 天前
国产研发管理平台推荐:技术决策者选型指南(2026)
gitee
墨天梦6 天前
D03_Compose列表与稳定身份
android·gitee·kotlin
Sun子矜6 天前
Git+Gitee 新手教程精简归纳版
git·gitee
创世虚拟世界6 天前
我做了一个 3D 虚拟世界基底,基于这个再做 3D 虚拟世界或者元宇宙,会事半功倍
javascript·人工智能·3d·gitee·虚拟现实
7177776 天前
有没有国产 Jira?2026 年主流国产替代方案对比与 Gitee 定位解析
gitee·jira
7177777 天前
软件成分分析工具哪个好:2026年主流SCA工具选型与Gitee生态实践指南
gitee