mcpField
W
wear-widgets

wear-widgets

github.com/android开发10.0k208 次安装2 天前更新
代码仓库

关于 wear-widgets

面向 Android 开发者的技能,用于通过 Glance Wear 和 Remote Compose 创建或迁移 Wear OS 小组件,涵盖服务注册、自适应布局、远程状态与操作、预览及选择器资源。

主要功能

  1. 01配置 Glance Wear 小组件
  2. 02Remote Compose 自适应布局
  3. 03远程状态与点击操作
  4. 04小组件预览和选择器图片
  5. 05旧版 Tile 迁移指导

适用场景

  1. 01创建 Wear OS 小组件
  2. 02迁移旧版 Wear OS Tile
  3. 03添加小组件预览和选择器资源

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/android/skills --skill wear-widgets

安装后在 Claude Code 中输入/wear-widgets确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
previews-and-testing.md
remote-compose-layouts.md
state-and-actions.md
widget-service-and-manifest.md

详细介绍

Wear OS 微件使用 androidx.glance.wear(GlanceWearWidget 和 GlanceWearWidgetService)与 androidx.compose.remote(Remote Compose)。 它们会将声明式 UI 和状态文档序列化到手表宿主进程。

前提条件和设置

  1. 确定库版本:为 {GLANCE_WEAR_VERSION}(androidx.glance.wear)、{COMPOSE_REMOTE_VERSION}(androidx.compose.remote)和 {WEAR_COMPOSE_REMOTE_VERSION}(androidx.wear.compose.remote)确定最新版本。如果尚无稳定版本,请包含 -alpha、-beta 或 -rc 版本。不要运行 ./gradlew dependencies 来确定版本:

  2. SDK 和 Compose 编译器要求:除非用户要求创建微件、迁移 Tile 或设置依赖项,否则修改整个代码库的 Gradle 文件前必须先征得许可。设置 compileSdk = 37 或更高版本,并启用 buildFeatures { compose = true }。保留项目现有的 Android 和 Kotlin Gradle 插件声明。

    dependencies { implementation("androidx.glance.wear:wear:{GLANCE_WEAR_VERSION}") implementation("androidx.glance.wear:wear-core:{GLANCE_WEAR_VERSION}") implementation("androidx.glance.wear:wear-tooling-preview:{GLANCE_WEAR_VERSION}") implementation("androidx.compose.remote:remote-creation-compose:{COMPOSE_REMOTE_VERSION}") implementation("androidx.compose.remote:remote-creation:{COMPOSE_REMOTE_VERSION}") implementation("androidx.compose.remote:remote-core:{COMPOSE_REMOTE_VERSION}") implementation("androidx.compose.remote:remote-tooling-preview:{COMPOSE_REMOTE_VERSION}") implementation("androidx.wear.compose.remote:remote-material3:{WEAR_COMPOSE_REMOTE_VERSION}") }

探索代码库

  1. 在模块中搜索现有的 TileService、Material3TileService 或 GlanceWearWidget 实现、app/src/main/AndroidManifest.xml 中的 <service> 条目,以及 app/src/main/res/ 下的 @drawable/ 预览资源。
  2. 迁移现有 Tile 或修改不熟悉的多模块项目时,运行 ./gradlew :app:assembleDebug,以验证基线构建状态。

工作流

步骤 1:配置 AndroidManifest.xml 和 provider XML

在 app/src/main/AndroidManifest.xml 中注册 GlanceWearWidgetService 子类,并设置 BIND_TILE_PROVIDER 权限和 BIND_WIDGET_PROVIDER intent 操作。添加 <meta-data>,指向包含 SMALL 和 LARGE <container> 条目的 @xml/ <wearwidget-provider> 资源。配置服务、provider XML 或迁移旧版 TileService 时,必须遵循微件服务和清单参考。

<service
    android:name=".snippets.widget.skill.MyWidgetService"
    android:exported="true"
    android:permission="com.google.android.wearable.permission.BIND_TILE_PROVIDER">
    <intent-filter>
        <action android:name="androidx.glance.wear.action.BIND_WIDGET_PROVIDER" />
    </intent-filter>
    <meta-data
        android:name="androidx.glance.wear.widget.provider"
        android:resource="@xml/my_widget_info" />
</service>
步骤 2:实现 GlanceWearWidget 和 GlanceWearWidgetService

扩展 GlanceWearWidget,并重写 provideWidgetData 以返回 WearWidgetDocument。为 GlanceWearWidgetService 子类添加 @AssociateWithGlanceWearWidget 注解,以便静态解析 provider:

// WRONG: Mobile GlanceAppWidget API (don't use on Wear OS)
class BrokenWidget : GlanceAppWidget() {
    override suspend fun provideGlance(context: Context, id: GlanceId) {}
}

// CORRECT: Wear OS GlanceWearWidget + GlanceWearWidgetService
@AssociateWithGlanceWearWidget(MyWidget::class)
class MyWidgetService : GlanceWearWidgetService() {
    override val widget: GlanceWearWidget = MyWidget()
}

class MyWidget : GlanceWearWidget() {
    override suspend fun provideWidgetData(
        context: Context,
        params: WearWidgetParams,
    ): WearWidgetData =
        WearWidgetDocument(
            background = WearWidgetBrush.color(Color(0xFF1C1B1F).rc)
        ) {
            MyWidgetContent(params = params)
        }
}
步骤 3:构建 Remote Compose 布局、状态和操作

Remote Compose 会在 Wear OS 宿主进程中评估 UI 和状态,而不是在应用进程中。构建自适应布局或全出血背景时,必须遵循 Remote Compose layouts reference。实现状态或点击操作时,必须遵循 State and actions reference。

  • Remote 值包装器:使用 .rs(RemoteString)、.rc(RemoteColor)、.rdp(RemoteDp)和 .ri(RemoteInt)转换基本类型。使用 RemoteInt.toRemoteString 成员方法格式化整数。
  • 自适应容器尺寸:根据 params.containerType(ContainerInfo.CONTAINER_TYPE_SMALL 与 ContainerInfo.CONTAINER_TYPE_LARGE)调整布局密度、排版和项目数量。
  • 未遮罩的全出血背景:在根 RemoteBox(RemoteModifier.fillMaxSize)中使用 RemoteImage 和 RemoteImageBitmap(bitmap.asImageBitmap)渲染背景位图。保持背景图稿未遮罩(cornerRadius = 0dp),不要使用 CircleShape 或 RoundedCornerShape 裁剪。宿主会将边缘裁剪为手表形状。
  • 声明式宿主状态和 PendingIntent 操作:使用 rememberMutableRemoteInt 和 valueChange(state, newValue) 修改本地微件状态,而不要使用内存中的 Compose 状态(mutableStateOf 或 mutableIntStateOf)。使用 pendingIntentAction { ctx -> ... } 触发 Activity 启动或后台广播(始终传入 (Context) -> PendingIntent lambda)。使用 widget.triggerUpdateAll(context) 刷新处于活动状态的微件。
步骤 4:添加 WearWidgetPreview 函数和选择器预览 PNG
  1. 可组合边框预览(androidx.glance.wear.tooling.preview):从 androidx.glance.wear.tooling.preview 导入 WearWidgetPreview、SquircleAllWidgetPreviewParams、RoundAllWidgetPreviewParams 和 RectangularAllWidgetPreviewParams(绝不要使用 androidx.glance.wear.testing)。定义调用 WearWidgetPreview(widget, params) 的 @Preview 函数。
  2. 矩形选择器 PNG(res/drawable-nodpi/):使用 320 dpi 下的 RectangularAllWidgetPreviewParams,或为提供程序 XML 中的每个 <container> 渲染非空白、未遮罩的矩形 PNG:
    • SMALL(CONTAINER_TYPE_SMALL):448x168 px(320 dpi 下为 224x84 dp,宽高比为 2.67)。
    • LARGE(CONTAINER_TYPE_LARGE):464x288 px(320 dpi 下为 232x144 dp,宽高比为 1.61)。
    • 每张预览 PNG 都必须呈现微件的实际 UI 元素,包括背景、文本标签、图标、头像首字母和按钮。绝不要使用空白占位图、没有标签的空形状或 1:1 平铺预览。添加可组合预览、选择器 PNG 或单元测试时,必须遵循 Previews and testing reference。

组件映射

使用此示例表加载完整的实现模式和导入项:

功能或 API 范围参考指南
GlanceWearWidget、GlanceWearWidgetService、AndroidManifest.xml、<wearwidget-provider>、旧版 TileService 迁移Widget service and manifest reference
RemoteBox、RemoteColumn、RemoteRow、RemoteText、RemoteImage、ContainerInfo(SMALL 和 LARGE 自适应布局)Remote Compose layouts reference
RemoteButton、rememberMutableRemoteInt、valueChange、pendingIntentAction、BroadcastReceiver、triggerUpdateAllState and actions reference
WearWidgetPreview、SquircleAllWidgetPreviewParams、RoundAllWidgetPreviewParams、RectangularAllWidgetPreviewParams、选择器 PNG、captureRawContent 测试Previews and testing reference
旧版 ProtoLayout 和移动端 Glance 对应项
旧版或移动端 APIWear OS 小组件 API要采取的操作
androidx.wear.tiles.TileServiceGlanceWearWidgetService + GlanceWearWidget替换服务,并添加 @AssociateWithGlanceWearWidget 注解。
GlanceAppWidget / provideGlanceGlanceWearWidget / provideWidgetData从 provideWidgetData 返回 WearWidgetDocument。
LayoutElementBuilders / primaryLayoutRemoteColumn、RemoteRow、RemoteBox使用 @RemoteComposable 函数构建布局。
mutableStateOf / mutableIntStateOfrememberMutableRemoteInt + valueChange使用 valueChange 以声明式方式更改远程状态。
ModifiersBuilders.ClickableRemoteModifier.clickable(pendingIntentAction { ... })将 (Context) -> PendingIntent lambda 传给 pendingIntentAction。

故障排除

症状原因修复方法
无法解析引用 GlanceWearWidget缺少 androidx.glance.wear:wear:{GLANCE_WEAR_VERSION} 或 compileSdk 版本过低设置 compileSdk = 37,并添加 androidx.glance.wear:wear:{GLANCE_WEAR_VERSION}。
无法解析引用 toRemoteString将 toRemoteString 作为顶层扩展导入移除导入;将 remoteInt.toRemoteString 作为成员方法调用。
pendingIntentAction 类型不匹配传入了裸 PendingIntent,而不是 lambda将其包装在 lambda 中:pendingIntentAction { ctx -> pendingIntent }。
迁移后仍有无法解析的 wear.tiles 或 protolayout 引用模块中的其他类或源集仍在使用旧版磁贴 API从 build.gradle.kts 移除旧版 wear.tiles 和 protolayout 依赖之前,先迁移或更新剩余引用。

验证

  1. 运行 ./gradlew :app:assembleDebug,验证小组件、服务、AndroidManifest.xml 声明和 @xml/ <wearwidget-provider> 资源均可编译和链接,且没有错误。
  2. 如果项目包含单元测试或新增的小组件测试,运行 ./gradlew :app:testDebugUnitTest 并确认所有测试均通过。添加可组合项预览、选择器 PNG 或单元测试时,必须遵循预览和测试参考。

反模式

  • 绝不可扩展移动端 GlanceAppWidget 或 AppWidgetProvider,也不可在 Wear OS 小组件中使用已弃用的 GlanceTileService 或 TileService。
  • 绝不可在 Remote Compose UI 中使用内存中的客户端 Compose 状态(mutableStateOf、mutableIntStateOf)或进程内点击 lambda。
  • 绝不可将裸 PendingIntent 实例传给 pendingIntentAction;始终传入 (Context) -> PendingIntent lambda。
  • 绝不可对全出血小组件背景图应用 CircleShape 或 RoundedCornerShape 裁剪。绝不可将 1:1(400x400)磁贴预览或纯色空白占位图复用于小组件选择器 PNG。

最佳实践

  • 必须在 <service> 上声明 com.google.android.wearable.permission.BIND_TILE_PROVIDER 和 androidx.glance.wear.action.BIND_WIDGET_PROVIDER。
  • 必须在 @xml/ 中声明 SMALL 和 LARGE <container> 条目,并在 res/drawable-nodpi/ 中提供符合实际内容的 448x168 和 464x288 PNG。
  • 必须根据 params.containerType 调整布局内容,并为 SquircleAllWidgetPreviewParams 和 RoundAllWidgetPreviewParams 分别添加 @Preview 函数。

评论

支持 Markdown