应用基础知识

使用 Jetpack Glance 和 Remote Compose 将卡片演变为 Wear 微件

扬声器

  • François Deschênes

    产品经理

  • Yuri Schimke

    软件工程师

关于

演讲简介

在本场讲座中,我们将从技术角度深入探究从全屏功能块到动态、部分高度 Wear 微件的演变。

我们将探讨统一的 Jetpack Glance API 和全新 Remote Compose 引擎的技术优势,并详细介绍它们如何与 Modern Android Development 无缝衔接。

最后,我们将分享最新的用户体验指南,并介绍实用的迁移策略,帮助您打造新一代一目了然的界面。

摘要

  • Wear OS 正在将全屏卡片演变为使用 Jetpack Glance 构建并由 Remote Compose 提供支持的 Wear 微件。
  • Remote Compose 与 Jetpack Compose 界面共享相同的编译器和运行时,同时安全地将界面与应用进程分离开来。
  • 小型和大型容器尺寸与移动网格格式保持一致,而 Jetpack Glance Wear API 可向后兼容至 Wear 4。
  • 将现有卡片迁移到微件时,可以使用推荐的双服务策略或单一兼容性服务。

技能

资源

Gemini Notebook

主题

  • wear-os
  • wear-widgets
  • jetpack-glance
  • remote-compose
  • tiles-migration
  • jetpack-compose

转写文稿

简介

  • 大家好!

  • 我叫 Frank,负责 Wear OS 的一目了然体验。

  • 手表是用户全天候的重要伙伴。今天,我们将带您了解如何利用全新的 Wear 微件体验。

  • 我们将探讨如何将您最重要的功能直接提供给用户,并使用最新的 API 来实现这一目标。这些 API 旨在让像您这样的 Android 开发者能够以熟悉且直观的方式为 Wear OS 构建应用。

  • 今天,我邀请了我的优秀同事兼邻桌好友 Yuri 与我一同登台,深入探讨我们是如何做到这一点的。

  • 大家好,我是 Yuri Schimke。

  • 我是 Wear OS 团队的一名工程师,主要负责 Remote Compose API。

  • 今天,我将简要介绍 Remote Compose,并讨论从卡片迁移到 Wear 微件的过程。

  • 接下来,我将把时间交还给 Frank。

将功能块演变为 Wear 微件

  • 太棒了!

  • 谢谢,Yuri。

  • 我们将全屏卡片升级为 Wear 小组件,正式将腕间设备纳入统一的 Android 小组件生态系统。

  • 由于这些体验现在是使用 Jetpack Glance 构建的,因此您可以看到,它们与移动 widget 使用的是相同的基础架构。

  • 您会发现,未来为 Wear 构建应用会变得越来越熟悉。

Remote Compose 概览

  • 为了支持这些体验,我们正在改进底层的界面堆栈。

  • Wear 微件由 Remote Compose 提供支持,Remote Compose 是一个用于远程界面的全新 Android 框架。

  • 这意味着,您终于可以使用与 Jetpack Compose 紧密契合的声明式 API 来构建应用外体验(例如微件)。

  • Remote Compose 为微件带来了现代图形和自适应布局,同时确保了性能和电池效率。

  • 通过将界面与应用进程解耦,您的微件可以安全地序列化,并在各种 Android 设备上轻松呈现。

  • 这意味着,您只需付出极少的努力,就能将这种丰富的体验带到几乎所有受支持的远程界面。

布局大小和向后兼容性

  • 为了让从卡片到微件的过渡尽可能顺畅,我们推出了两种标准容器尺寸:小号和大号。

  • 这些格式旨在与您在移动设备上构建微件时已经习惯的 2x1 和 2x2 网格微件格式保持一致。

  • 从 Wear 7 开始,在某些硬件制造商的设备上,您的 Wear 微件现在将与其他应用的微件一起显示在可垂直滚动的托盘中。

  • 例如,以下是 Wear 微件在 Samsung Galaxy Watch 上的显示效果。

  • 该平台架构旨在支持垂直滚动托盘,以便硬件制造商在未来采用这种模式。

  • 您也无需担心会遗漏任何用户或现有用户。

  • Jetpack Glance Wear API 旨在支持向后兼容至 Wear 4。

  • 在支持水平托盘(用于放置微件或卡片)的旧款设备上,系统会为您处理。

  • 您只需在代码中声明要支持全屏模式,系统就会在这些设备上将大型微件显示为全屏传统卡片。

  • 现在,Yuri 将带我们浏览代码,向大家展示如何将现有卡片迁移到 Wear 微件。

现有图块的迁移策略

  • 谢谢,Frank。

  • 如果您要从头开始构建新的 Wear 微件,操作很简单。

  • 您只需定义一个 GlanceWearWidgetService,并使用 Remote Compose 构建界面。

  • 但如果您的应用已有正式版卡片,则需要迁移服务、界面和数据。

  • 为了让这一过渡尽可能顺畅,我们提供了多种途径,确保现有用户能够获得无缝体验。

  • 对于服务,您可以选择两种策略:双服务或单服务。

  • 我们建议您采用双重服务,即保留现有的 TileService 以支持旧版 Wear OS 设备,同时为 Wear OS 7 及更高版本添加新的 GlanceWearWidgetService。

  • 相比之下,如果使用单一服务,您只需创建一个新的单一 GlanceWearWidgetService,即可同时处理这两种协议,并在兼容模式下运行,以便在旧款设备上将微件转换为卡片。

  • 无论采用哪种方式,您都可以继续覆盖用户。

  • 它只会影响您今后维护的代码。

  • 我们刚刚在 Android 开发者网站上发布了全面的文档,详细介绍了这两种模式的使用方法和时机。

  • 欢迎查看,详细了解哪种策略最适合您的应用。

使用 Remote Compose 构建界面

  • 现在,我们来谈谈界面层。

  • 迁移时,您的思维模式会发生转变。

  • 以前,功能块使用 ProtoLayout,这是一个专为 Wear 功能块构建的库。

  • 而 Wear 微件则基于 Remote Compose 构建。

  • Remote Compose 与 Jetpack Compose 界面共享完全相同的基础,使用相同的核心编译器和运行时。

  • 其设计旨在让您感到熟悉,与 Compose 界面库(如 Foundation 和 Material 3)高度契合。

  • 但您只需将 Box、Column 或 Text 替换为 RemoteBox、RemoteColumn 或 RemoteText。

  • 在这种情况下,您会经常编写“remote”。

  • 此外,为了加快您的工作流程,我们还提供了一套专为 widget 设计的即用型 Remote Compose 组件。

  • 这意味着,您现有的 Wear Material 3 设计(包括您在 Figma 中的设计)可以轻松适配微件,让工程师和设计师都能轻松上手。

  • 为了了解这些框架的契合程度,我们来看一个具体示例。

  • 我们将在 Material 3 开关按钮的基础上构建自己的应用组件。

  • 左侧是您在应用中编写的标准 Wear Compose 组件,右侧是 Remote Compose 版本。

    // Wear Compose
    @Composable
    fun HistoryEraserButton(
      onClick: () -> Unit,
      modifier: Modifier = Modifier,
      checked: Boolean = true,
      onCheckedChange: (Boolean) -> Unit = {},
    )
    // Wear RemoteCompose
    @RemoteComposable
    @Composable
    fun RemoteHistoryEraserButton(
      onClick: Action,
      modifier: RemoteModifier = RemoteModifier,
      checked: RemoteBoolean = true.rb,
      onCheckedChange: Action = Action.Empty,
    )
  • 请注意这些模式有多么相似。

  • 不过,在编写 Remote Compose 代码时,您将使用 RemoteComposable 注解。

  • 如果您使用修饰符和布尔值等熟悉的形参,它们具有等效的 Remote Compose 类型,例如 RemoteModifier 和 RemoteBoolean。

  • 并且您使用的是操作,而不是 lambda。

  • 您无需定义要在 lambda 中执行的代码,而是需要声明一个操作,例如启动待处理 intent、调用服务或更新值。

  • 我们来看看组件的主体。

  • 您会发现,甚至连主题都是一致的。

  • 您可以像在标准 Compose 中一样,将 Material 3 配色方案应用于 Remote Compose 组件,从而使微件与应用的品牌风格完全一致。

    // Wear Compose
      val colors = MaterialTheme.colorScheme
      SplitSwitchButton(
        checked = checked,
        onCheckedChange = onCheckedChange,
        toggleContentDescription = "Arm Eraser",
        onContainerClick = onClick,
        modifier = modifier.fillMaxWidth(),
        colors =
          SwitchButtonDefaults.splitSwitchButtonColors(
            checkedContainerColor = colors.errorContainer, …
          ),
        label = { Text("History Eraser") },
        secondaryLabel = { Text("Don't touch it!") },
      )
    // Wear RemoteCompose
      val colors = RemoteMaterialTheme.colorScheme
      RemoteSplitSwitchButton(
        checked = checked,
        onCheckedChange = onCheckedChange,
        toggleContentDescription = "Arm Eraser".rs,
        onContainerClick = onClick,
        modifier = modifier.fillMaxWidth(),
        colors =
          RemoteSplitSwitchButtonDefaults.splitSwitchButtonColors(
            checkedContainerColor = colors.errorContainer, …
          ),
        label = { RemoteText("History Eraser".rs) },
        secondaryLabel = { RemoteText("Don't touch it!".rs) },
      )
  • 最后,我们来看看结果。最终的界面正是您所期望的:一个简洁、响应迅速、带有动画效果的切换按钮。

针对微件的 Compose 预览支持

  • 很难分辨出区别。

  • 为了帮助您设计、迭代和验证微件布局,而无需始终在设备或模拟器上运行,我们为 Wear 微件添加了 Compose 预览支持。

  • 由于微件依赖于 OEM 特定的容器限制条件(例如大小、形状、内边距、圆角半径),因此我们基于标准预览注解进行构建,但会使用额外的参数提供程序来注入这些配置。

    @Preview(name = "Squircle Preview", device = "spec:width=1000dp,height=1000dp,dpi=320")
    @Composable
    fun WeatherWidgetSquirclePreview(
        @PreviewParameter(SquircleAllWidgetPreviewParams::class) params: WearWidgetParams
    ) = WearWidgetPreview(MockWeatherWidget(65, WeatherCondition.CLOUDY), params)
    
    @Preview(name = "Round Preview", device = "spec:width=1000dp,height=1000dp,dpi=320")
    @Composable
    fun WeatherWidgetRoundPreview(
        @PreviewParameter(RoundAllWidgetPreviewParams::class) params: WearWidgetParams
    ) = WearWidgetPreview(MockWeatherWidget(65, WeatherCondition.CLOUDY), params)
  • 该库提供了与实际 Wear OS 设备相匹配的预配置 PreviewParameter 提供程序实现。

  • 借助这些预览注解,您可以轻松验证布局在各种不同硬件形状中的效果。

  • 除了测试布局之外,您还可以使用这些预览生成设备端 widget 选择器所需的静态图片。

    @Preview(name = "Widget Picker Preview", device = "spec:width=1000dp,height=1000dp,dpi=320")
    @Composable
    fun WeatherWidgetRectangularPreview(
        @PreviewParameter(RectangularAllWidgetPreviewParams::class) params: WearWidgetParams
    ) = WearWidgetPreview(MockWeatherWidget(65, WeatherCondition.CLOUDY), params)
  • 该库提供了一个矩形预览形状参数,用于模拟完整的未剪裁矩形容器。

实现和迁移工具示例

  • 如果您想了解此 API 在实际应用中的具体运作方式,可以查看我们更新后的 Horologist 媒体示例应用,其中包含一个展示此 API 的新微件。

  • 我们将演示如何添加小型和大型媒体微件。

  • 您可以在 GitHub 上查看该示例,对比旧版 ProtoLayout 代码和新的 Remote Compose 实现。

  • 相信您一定会喜欢。

  • 最后,如果不提及 AI,这次演讲就不完整。

  • 为了加快迁移速度,我们在 GitHub 上发布了一项新的“卡片到微件迁移”技能。

  • 您可以将其与您选择的智能体搭配使用,也可以使用 Android CLI 将其直接添加到 Android Studio。

    android skills add wear-widgets
    android skills add --all
  • 现在,我将把时间交给 François,由他来做总结。

总结和收尾

  • 谢谢。

  • 谢谢,Yuri。

  • 如您所见,将卡片演变为 Wear 微件的过程非常简单。

  • 简而言之,迁移到 Jetpack Glance Wear 和 Remote Compose 不仅能更新界面,还能让您加入更统一的 Android 微件生态系统。

  • 现在,您可以使用与 Jetpack Compose 完全一致的现代声明式框架,构建美观且向后兼容的小组件。

  • 您可以立即开始迁移功能块,只需前往 Android 开发者网站上的入门指南,即可了解所有详细信息。

  • 我们期待看到您为腕部设备打造的各种富有表现力、一目了然的体验。

  • 非常感谢大家今天上午的参与,希望大家享受大会的后续内容。

  • 谢谢。

代码

代码段

“使用 Remote Compose 构建界面”中 06:40 处的 Kotlin 代码段
// Wear Compose
@Composable
fun HistoryEraserButton(
  onClick: () -> Unit,
  modifier: Modifier = Modifier,
  checked: Boolean = true,
  onCheckedChange: (Boolean) -> Unit = {},
)
“使用 Remote Compose 构建界面”视频中 06:40 处的 Kotlin 代码段
// Wear RemoteCompose
@RemoteComposable
@Composable
fun RemoteHistoryEraserButton(
  onClick: Action,
  modifier: RemoteModifier = RemoteModifier,
  checked: RemoteBoolean = true.rb,
  onCheckedChange: Action = Action.Empty,
)
“使用 Remote Compose 构建界面”中 07:39 处的 Kotlin 代码段
// Wear Compose
  val colors = MaterialTheme.colorScheme
  SplitSwitchButton(
    checked = checked,
    onCheckedChange = onCheckedChange,
    toggleContentDescription = "Arm Eraser",
    onContainerClick = onClick,
    modifier = modifier.fillMaxWidth(),
    colors =
      SwitchButtonDefaults.splitSwitchButtonColors(
        checkedContainerColor = colors.errorContainer, …
      ),
    label = { Text("History Eraser") },
    secondaryLabel = { Text("Don't touch it!") },
  )
“使用 Remote Compose 构建界面”中 07:39 处的 Kotlin 代码段
// Wear RemoteCompose
  val colors = RemoteMaterialTheme.colorScheme
  RemoteSplitSwitchButton(
    checked = checked,
    onCheckedChange = onCheckedChange,
    toggleContentDescription = "Arm Eraser".rs,
    onContainerClick = onClick,
    modifier = modifier.fillMaxWidth(),
    colors =
      RemoteSplitSwitchButtonDefaults.splitSwitchButtonColors(
        checkedContainerColor = colors.errorContainer, …
      ),
    label = { RemoteText("History Eraser".rs) },
    secondaryLabel = { RemoteText("Don't touch it!".rs) },
  )
“Compose preview support for widgets”中 08:20 处的 Kotlin 代码段
@Preview(name = "Squircle Preview", device = "spec:width=1000dp,height=1000dp,dpi=320")
@Composable
fun WeatherWidgetSquirclePreview(
    @PreviewParameter(SquircleAllWidgetPreviewParams::class) params: WearWidgetParams
) = WearWidgetPreview(MockWeatherWidget(65, WeatherCondition.CLOUDY), params)

@Preview(name = "Round Preview", device = "spec:width=1000dp,height=1000dp,dpi=320")
@Composable
fun WeatherWidgetRoundPreview(
    @PreviewParameter(RoundAllWidgetPreviewParams::class) params: WearWidgetParams
) = WearWidgetPreview(MockWeatherWidget(65, WeatherCondition.CLOUDY), params)
“Compose preview support for widgets”中 08:58 处的 Kotlin 代码段
@Preview(name = "Widget Picker Preview", device = "spec:width=1000dp,height=1000dp,dpi=320")
@Composable
fun WeatherWidgetRectangularPreview(
    @PreviewParameter(RectangularAllWidgetPreviewParams::class) params: WearWidgetParams
) = WearWidgetPreview(MockWeatherWidget(65, WeatherCondition.CLOUDY), params)
“示例实现和迁移工具”中 09:58 处的 Shell 代码段
android skills add wear-widgets
android skills add --all