Compose for Wear OS Codelab

1. 简介

2c9dd335c9d65f10.png

Compose for Wear OS 让您能够将构建 Jetpack Compose 应用程序所学到的知识应用到可穿戴设备上。

凭借对 Material Design 的内置支持,Compose for Wear OS 简化并加速了 UI 开发,帮助您用更少的代码创建精美的应用程序。

对于本 Codelab,我们希望您对 Compose 有一定的了解,但您无需成为专家。

您将创建多个 Wear OS 特有的可组合项(包括简单和复杂的),学完后,您就可以开始为 Wear OS 编写自己的应用程序了。我们开始吧!

您将学到什么

  • 您之前使用 Compose 的经验与本次体验的相似点/不同点
  • 简单的可组合项以及它们在 Wear OS 上的工作方式
  • Wear OS 特有的可组合项
  • Wear OS 的 LazyColumn (TransformingLazyColumn)
  • Wear OS 版的 Scaffold

您将构建什么

您将构建一个简单的应用程序,显示一个针对 Wear OS 优化的可滚动可组合项列表。

由于您将使用 AppScaffoldScreenScaffold,您还将获得一个顶部弯曲的文本时间和一个绑定到设备侧面的滚动指示器。

以下是您完成本 Codelab 后的样子

3162140d003d8e31.gif

先决条件

2. 设置环境

在此步骤中,您将设置环境并下载启动项目。

您将需要什么

下载代码

如果您已安装 git,您只需运行以下命令即可从此仓库克隆代码。要检查是否安装了 git,请在终端或命令行中键入 git --version 并验证其是否正确执行。

git clone https://github.com/android/codelab-compose-for-wear-os.git
cd codelab-compose-for-wear-os

如果您没有 git,您可以点击以下按钮下载本 Codelab 的所有代码

您可以随时通过更改工具栏中的运行配置来在 Android Studio 中运行任何一个模块。

51cb2645eece1f20.png

在 Android Studio 中打开项目

  1. 在 Android Studio 欢迎窗口中选择 c01826594f360d94.png 打开...
  2. 选择文件夹 [下载位置]
  3. Android Studio 导入项目后,测试您是否可以在 Wear OS 模拟器或实体设备上运行 startfinished 模块。
  4. start 模块应如下图所示。您将在此处完成所有工作。

271451f93a57db41.png

探索起始代码

  • build.gradle 包含基本的应用程序配置。它包括创建可组合 Wear OS 应用程序所需的依赖项。我们将讨论 Jetpack Compose 和 Wear OS 版本之间的异同。
  • main > AndroidManifest.xml 包含创建 Wear OS 应用程序所需的元素。这与非 Compose 应用程序相同,也与移动应用程序类似,因此我们不会对此进行回顾。
  • main > theme/ 文件夹包含 Compose 用于主题的 ColorTypeTheme 文件。
  • main > MainActivity.kt 包含使用 Compose 创建应用程序的样板代码。它还包含我们应用程序的顶级可组合项(如 ScaffoldTransformingLazyColumn)。
  • main > ReusableComponents.kt 包含我们创建的大多数 Wear OS 特有可组合项的函数。我们将在该文件中完成大量工作。

3. 查看依赖项

您所做的与 Wear OS 相关的大多数依赖项更改都将在顶部架构层(下图中红色突出显示的部分)进行。

d92519e0b932f964.png

这意味着,当您针对 Wear OS 时,您已经与 Jetpack Compose 一起使用的许多依赖项不会改变。例如,UI、运行时、编译器和动画依赖项将保持不变。

但是,您将需要使用正确的 Wear OS MaterialFoundationNavigation 库,它们与您以前使用过的库不同。

下面是帮助澄清差异的比较

Wear OS 依赖项(androidx.wear.*)

比较

标准依赖项(androidx.*)

androidx.wear.compose:compose-material3

而不是

androidx.compose.material:material

androidx.wear.compose:compose-navigation

而不是

androidx.navigation:navigation-compose

androidx.wear.compose:compose-foundation

此外还有

androidx.compose.foundation:foundation

androidx.wear.compose:compose-ui-tooling

此外还有

androidx.compose.ui:ui-tooling-preview

1. 开发者可以继续将其他 Material 相关库(如 Material Ripple 和 Material Icons Extended)与 Wear Compose Material 库一起使用。除了 compose-material3 版本,您还可以使用 androidx.wear.compose:compose-material,但您不应在同一应用程序中混合使用 Material 3 和 Material 2.5。我们建议您使用 Material 3,因为它支持 Material 3 表现力设计。

打开 build.gradle,在您的 start 模块中搜索 "TODO: Review Dependencies"。(此步骤仅用于查看依赖项,您不会添加任何代码。)

start/build.gradle

dependencies {
    val composeBom = platform(libs.androidx.compose.bom)

    // General compose dependencies
    implementation(composeBom)
    implementation(libs.androidx.activity.compose)
    implementation(libs.compose.ui.tooling.preview)

    implementation(libs.androidx.material.icons.extended)

    // Compose for Wear OS Dependencies
    implementation(libs.wear.compose.material)

    // Foundation is additive, so you can use the mobile version in your Wear OS app.
    implementation(libs.wear.compose.foundation)

    // Compose preview annotations for Wear OS.
    implementation(libs.androidx.compose.ui.tooling)

    implementation(libs.horologist.compose.layout)

    coreLibraryDesugaring(libs.desugar.jdk.libs)

    debugImplementation(libs.compose.ui.tooling)
    debugImplementation(libs.androidx.ui.test.manifest)
    debugImplementation(composeBom)
}

您应该能识别出许多通用 Compose 依赖项,因此我们不会介绍这些。

让我们转到 Wear OS 依赖项。

正如之前所概述的,只包含了 Wear OS 专用版本的 material (androidx.wear.compose:compose-material3)。也就是说,您不会在项目中看到或包含 androidx.compose.material:material3

需要指出的是,您可以将其他 Material 库与 Wear Material 一起使用。实际上,我们通过包含 androidx.compose.material:material-icons-extended 在本 Codelab 中做到了这一点。

最后,我们包含了 Compose 的 Wear foundation 库 (androidx.wear.compose:compose-foundation)。这是附加的,因此您可以将其与您之前使用的标准 foundation 一起使用。事实上,您可能已经意识到我们将其包含在通用 Compose 依赖项中!

好的,既然我们了解了依赖项,现在来看看主应用程序。

4. 查看 MainActivity

我们所有的工作都将在

start

模块中完成,因此请确保您打开的每个文件都在其中。

让我们首先在 start 模块中打开 MainActivity

这是一个相当简单的类,它扩展了 ComponentActivity 并使用 setContent { WearApp() } 来创建 UI。

根据您之前对 Compose 的了解,这应该看起来很熟悉。我们只是在设置 UI。

向下滚动到 WearApp() 可组合函数。在我们讨论代码本身之前,您应该会看到代码中散布着大量的 TODO。它们每个都代表本 Codelab 中的步骤。您现在可以忽略它们。

它应该看起来像这样

fun WearApp() 中的代码

WearAppTheme {
     /* *************************** Part 4: Wear OS Scaffold *************************** */
    // TODO (Start): Create a AppScaffold (Wear Version)

    // TODO: Swap to TransformingLazyColumnState
    val listState = rememberLazyListState()

    /* *************************** Part 4: Wear OS Scaffold *************************** */
    // TODO (Start): Create a ScreenScaffold (Wear Version)

    /* *************************** Part 3: ScalingLazyColumn *************************** */
    // TODO: Swap a TransformingLazyColumn (Wear's version of LazyColumn)
    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        state = listState,
    ) {
        // TODO: Remove item; for beginning only.
        item { StartOnlyTextComposables() }

        /* ******************* Part 1: Simple composables ******************* */
        item { ButtonExample() }
        item { TextExample() }
        item { CardExample() }

        /* ********************* Part 2: Wear unique composables ********************* */
        item { ChipExample() }
        item { ToggleChipExample() }
        }

    // TODO (End): Create a ScreenScaffold (Wear Version)
    // TODO (End): Create a AppScaffold (Wear Version)
}

我们首先设置主题,WearAppTheme { }。这与您之前编写的方式完全相同,也就是说,您使用颜色、排版和形状设置 MaterialTheme

然而,对于 Wear OS,我们通常建议使用默认的 Material Wear 形状,它们针对圆形设备进行了优化,所以如果您深入查看我们的 theme/Theme.kt,您会发现我们没有覆盖形状。

如果您愿意,可以打开 theme/Theme.kt 以进一步探索,但同样,它与手机上的相同。

然后我们创建一个 LazyColumn,它用于生成一个垂直滚动的项目列表(就像您以前做的那样)。

代码

item { StartOnlyTextComposables() }

/* ******************* Part 1: Simple composables ******************* */
item { ButtonExample() }
item { TextExample() }
item { CardExample() }

/* ********************* Part 2: Wear unique composables ********************* */
item { ChipExample() }
item { ToggleChipExample() }

对于项目本身,只有 StartOnlyTextComposables() 生成 UI。(我们将在整个 Codelab 中填充其余部分。)

这些函数实际上在 ReusableComponents.kt 文件中,我们将在下一节中访问该文件。

让我们开始使用 Compose for Wear OS!

5. 添加简单可组合项

我们将从您可能已经熟悉的三种可组合项 (ButtonTextCard) 开始。

首先,我们将删除 hello world 可组合项。

搜索 "TODO: Remove item" 并删除注释及其下面的行

步骤 1

// TODO: Remove item; for beginning only.
item { StartOnlyTextComposables() }

接下来,我们来添加第一个可组合项。

创建一个图标按钮可组合项

start 模块中打开 ReusableComponents.kt 并搜索 "TODO: Create a Icon Button Composable",然后用以下代码替换当前的可组合方法。

步骤 2

// TODO: Create a Icon Button Composable
@Composable
fun IconButtonExample(
    modifier: Modifier = Modifier,
) {
    FilledIconButton(
        onClick = { /* ... */ },
        modifier = modifier,
    ) {
        Icon(
            imageVector = Icons.Rounded.Phone,
            contentDescription = "triggers phone action",
        )
    }
}

IconButtonExample() 可组合函数(此处为代码所在位置)现在将生成一个居中按钮。

让我们来看看代码。

我们使用 FilledIconButton,它是一个带有彩色背景和对比内容颜色的圆形、仅图标的按钮。它提供了一个插槽来放置图标或图像。

之后,我们将点击事件设置为空的 lambda。在我们的例子中,这些可组合项只是为了演示,所以我们不需要它。然而,在实际应用程序中,我们会与 ViewModel 通信以执行业务逻辑。

然后我们在按钮内部设置一个图标。此代码与您之前见过的 Icon 代码相同。我们还从 androidx.compose.material:material-icons-extended 库中获取图标。

最后,您现在无需担心按钮的居中问题,因为在从 LazyColumn 迁移到 TransformingLazyColumn 时,它将自动修复。

如果您运行应用程序,您应该会得到类似这样的结果,暂时不用担心按钮未居中

9346dbd2b8bc6a56.png

这可能是您以前已经编写过的代码(这很棒)。不同之处在于,您现在获得了一个为 Wear OS 优化的按钮。

非常简单,我们来看另一个。

创建一个文本可组合项

ReusableComponents.kt 中,搜索 "TODO: Create a Text Composable" 并用以下代码替换当前的可组合方法。

步骤 3

// TODO: Create a Text Composable
@Composable
fun TextExample(modifier: Modifier = Modifier) {
   ListHeader{
      Text(
           modifier = modifier
               .fillMaxWidth(),
           textAlign = TextAlign.Center,
           text = stringResource(R.string.hello_compose_codelab),
       )
   }
}

我们创建 Text 可组合项,设置其修饰符,对齐文本,设置颜色,最后从字符串资源设置文本本身。由于我们稍后将添加一个列表,我们将此文本包装到 ListHeader 中,以便内容具有起始和结束内边距。

Text 可组合项对于 Compose 开发者来说应该非常熟悉,而且代码实际上与您之前使用的代码完全相同。

让我们看看它长什么样

dcad71112a91d706.png

TextExample() 可组合函数(我们放置代码的位置)现在生成了一个采用我们主材质颜色的 Text 可组合项。该字符串是从我们的 res/values/strings.xml 文件中提取的。

到目前为止,一切顺利。让我们看看我们最后一个相似的可组合项,Card

创建一个卡片可组合项

ReusableComponents.kt 中,搜索 "TODO: Create a Card" 并用以下代码替换当前的可组合方法。

步骤 4

// TODO: Create a Card (specifically, an AppCard) Composable
@Composable
fun CardExample(
    modifier: Modifier = Modifier,
    iconModifier: Modifier = Modifier
) {
    AppCard(
        modifier = modifier,
        appImage = {
            Icon(
                imageVector = Icons.AutoMirrored.Rounded.Message,
                contentDescription = "triggers open message action",
                modifier = iconModifier
            )
        },
        appName = { Text("Messages") },
        time = { Text("12m") },
        title = { Text("Kim Green") },
        onClick = { /* ... */ }
    ) {
        Text("On my way!")
    }
}

Wear 有点不同,我们有两种主要的卡片,AppCardTitleCard

在我们的例子中,我们希望在卡片中有一个 Icon,所以我们将使用 AppCard。(TitleCard 的插槽较少,请参阅 卡片指南以获取更多信息。)

我们创建 AppCard 可组合项,设置其修饰符,添加一个 Icon,添加几个 Text 可组合参数(每个参数对应卡片上的不同空间),最后设置主要内容文本。

让我们看看它长什么样,不用担心添加额外的底部填充,因为这将在从 LazyColumn 迁移到 TransformingLazyColumn 时修复。

faf2fe359baf0946.png

此时,您可能已经意识到,对于这些可组合项,Compose 代码实际上与您以前使用的代码相同,这很棒,对吗?您可以重用您已经获得的所有知识!

好了,让我们看看一些新的可组合项。

6. 添加 Wear OS 独有的可组合项

在本节中,我们将探讨 ChipToggleChip 可组合项。

创建一个 Chip 可组合项

Chips 旨在提供快速、一次点击的操作,这对于屏幕空间有限的 Wear 设备来说尤为重要。

您可以通过使用 Button 来实现 Chip。以下是 Button 可组合函数的几个变体,可让您了解可以创建什么

13c7dc5e08b5d2d4.png 9859b3f9880f75af.png

让我们来编写一些代码。

ReusableComponents.kt 中,搜索 "TODO: Create a Chip" 并用以下代码替换当前的可组合方法。

步骤 5

// TODO: Create a Chip Composable
@Composable
fun ChipExample(
    modifier: Modifier = Modifier,
) {
    Button(
        modifier = modifier,
        onClick = { /* ... */ },
        icon = {
            Icon(
                imageVector = Icons.Rounded.SelfImprovement,
                contentDescription = "triggers meditation action",
            )
        },
    ) {
        Text(
            text = "5 minute Meditation",
            maxLines = 1,
            overflow = TextOverflow.Ellipsis,
        )
    }
}

Button 可组合项使用许多与您熟悉的其它可组合项相同的参数(modifier 和 onClick),因此我们无需回顾这些。

它还接受一个图标和一个内容槽,用于显示在 Button 上的可组合主体内容(我们为此创建了一个 Text 可组合项)。

Icon 代码应该与您在其他可组合项中看到的完全相同,但对于这个可组合项,我们从 androidx.compose.material:material-icons-extended 库中获取 Self Improvement 图标。

让我们看看它长什么样(记得向下滚动),不用担心添加额外的底部填充,因为这将在从 LazyColumn 迁移到 TransformingLazyColumn 时修复。

22afab093efc7fc5.png

好了,我们来看一下 Button 的一个变体,即 SwitchButton 可组合项。

创建一个 SwitchChip 可组合项

SwitchButton 就像一个 Button,但允许用户与开关进行交互。

eb38c8acb3ac996.png

ReusableComponents.kt 中,搜索 "TODO: Create a SwitchChip" 并用以下代码替换当前的可组合方法。

步骤 6

// TODO: Create a Switch Chip Composable
@Composable
fun SwitchChipExample(modifier: Modifier = Modifier) {
    var checked by remember { mutableStateOf(true) }
    SwitchButton(
        modifier = modifier.fillMaxWidth(),
        label = {
            Text(
                "Sound",
                maxLines = 1,
                overflow = TextOverflow.Ellipsis,
                modifier = Modifier.semantics {
                    this.contentDescription = if (checked) "On" else "Off"
                },
            )
        },
        checked = checked,
        onCheckedChange = { checked = it },
        enabled = true,
    )
}

现在 SwitchChipExample() 可组合函数(此处为代码所在位置)生成了一个 SwitchChip,它使用了一个开关切换器(而不是复选框或单选按钮)。

首先,我们创建一个 MutableState。我们没有在其他函数中这样做,因为我们主要做 UI 演示,这样您就可以看到 Wear OS 提供了什么。

在普通应用程序中,您可能希望传入选中状态和处理点击的 lambda,这样可组合项就可以是无状态的(更多信息在此)。

在我们的例子中,我们只是为了简单起见,展示 SwitchChip 在一个工作切换开关下看起来如何(尽管我们没有对状态做任何事情)。

接下来,我们设置选中状态,以及用于提供所需开关的切换控件。

然后我们创建一个用于更改状态的 lambda,最后用一个 Text 可组合项(和一些基本参数)设置标签。

让我们看看它长什么样

8ef93a35bdb7302a.png

好的,现在您已经看到了很多 Wear OS 特定的可组合项,正如之前所说,大多数代码与您之前编写的代码几乎相同。

让我们来看一些更高级的东西。

7. 迁移到 TransformingScalingLazyColumn

您可能在移动应用程序中使用过 LazyColumn 来生成垂直滚动列表。

由于圆形设备顶部和底部较小,可显示项目空间较少。因此,Wear OS 有自己的 LazyColumn 版本,以更好地支持这些圆形设备。

TransformingLazyColumn 扩展了 LazyColumn,以支持屏幕顶部和底部的缩放和透明度,从而使内容对用户更具可读性。

以下是一个演示

c056381ba4a7475d.gif

请注意,当项目接近中心时,它会放大到全尺寸,然后当它移开时,它会缩小(同时变得更透明)。

以下是应用程序中更具体的示例

6dbb1e13f99e5e1f.gif

我们发现这确实有助于提高可读性。

现在您已经看到了 TransformingLazyColumn 的实际效果,让我们开始转换我们的 LazyColumn

转换为 TransformingLazyColumnState

MainActivity.kt 中,搜索 "TODO: Swap to TransformingLazyColumnState" 并用此代码替换该注释及其下面的行,请注意我们如何指定哪些是第一个和最后一个组件,以便使用最佳填充值来避免任何内容裁剪。

步骤 7

// TODO: Swap to TransformingLazyColumnState
val listState = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()

名称几乎相同。就像 LazyListState 处理 LazyColumn 的状态一样,TransformingLazyColumnState 处理 TransformingLazyColumn 的状态。

我们还在此处指定了 transformationSpec,以便我们可以为项目在屏幕上滚动时添加转换。

转换为 TransformingLazyColumn

接下来我们换成 TransformingLazyColumn

MainActivity.kt 中,搜索 "TODO: Swap a TransformingLazyColumn"。首先,将 LazyColumn 替换为 TransformingLazyColumn

然后完全删除 contentPadding, verticalArrangementmodifier - TransformingLazyColumn 已经提供了默认设置,保证了更好的默认视觉效果,因为大部分视口将被列表项填充。在大多数情况下,默认参数就足够了,如果您顶部有标题,我们建议将其作为第一个项目放入 ListHeader 中。

步骤 8

// TODO: Swap a TransformingLazyColumn (Wear's version of LazyColumn)
TransformingLazyColumn(
    state = listState,
    contentPadding = contentPadding)

为项目添加滚动转换效果

现在,我们通过使用以下 ModifierSurfaceTransformationTransformingLazyColumn 元素添加 ShapeMorphing 效果。将相同的代码应用于所有组件(

除了 IconButtonExample,它尚未支持此功能)

步骤 9

TextExample(
   modifier = Modifier.fillMaxWidth().transformedHeight(this, transformationSpec),
   transformation = SurfaceTransformation(transformationSpec),
)

就是这样!让我们看看它长什么样

4c97e27b6acb83ef.png

您可以看到内容在屏幕顶部和底部进行缩放,并且透明度也随之调整,而您只需很少的工作即可完成迁移!

在您上下移动冥想可组合项时,您会发现这一点非常明显。

现在,进入最后一个主题,Wear OS 的 Scaffold

8. 添加 Scaffold

AppScaffoldScreenScaffold 提供了一个布局结构,可帮助您以常见模式排列屏幕,就像移动设备一样,但它不是 App Bar、FAB、Drawer 或其他移动设备特定元素,而是支持三种具有顶级组件的 Wear OS 特定布局:时间、滚动/位置指示器和页面指示器。

它们长这样

TimeText

ScrollIndicator

HorizontalPageIndicator

我们将详细查看前三个组件,但首先,让我们将 Scaffold 放置到位。

Scaffold 组件 AppScaffoldScreenScaffold 布局屏幕结构,并协调 ScrollIndicatorTimeText 组件的过渡。

AppScaffold 允许静态屏幕元素(例如 TimeText)在应用内过渡(例如滑动关闭)期间保持可见。ScreenScaffold 默认在屏幕的中心-末端显示 ScrollIndicator,并协调 TimeTextScrollIndicator 的显示/隐藏。

添加 Scaffold

现在让我们为 AppScaffoldScreenScaffold 添加样板代码。

找到 "TODO (Start): Create a AppScaffold (Wear Version)" 并在其下方添加代码。

步骤 9

WearAppTheme {
// TODO (Start): Create a AppScaffold (Wear Version)
AppScaffold {

找到 "TODO (Start): Create a ScreenScaffold (Wear Version)" 并在其下方添加代码。

// TODO (Start): Create a ScreenScaffold (Wear Version)
ScreenScaffold( 
    scrollState = listState,
    contentPadding = rememberResponsiveColumnPadding(
       first = ColumnItemType.IconButton,
       last = ColumnItemType.Button,
    ),
){contentPadding ->

接下来,确保将右括号添加到正确的位置。

找到 "TODO (End): Create a ScreenScaffold (Wear Version)" 并在那里添加右括号

步骤 10

// TODO (End): Create a ScreenScaffold (Wear Version)
}

找到 "TODO (End): Create a AppScaffold (Wear Version)" 并在那里添加右括号

步骤 10

// TODO (End): Create a AppScaffold (Wear Version)
}

我们先运行一下。您应该会看到类似这样的内容

97e417901b8f8229.png

注意,它添加了

好了,现在来看看这个样子

fd00c8fc4f7283ef.png

尝试上下滚动。您应该只有在滚动时才会看到滚动指示器出现。

添加一个边缘贴合按钮

EdgeButton 是 M3 Compose Material for Wear OS 版本中新增的一种富有表现力的按钮。边缘贴合容器是一种新的形状,它拥抱圆形并最大限度地利用了圆形外形中的空间。

ScreenScaffold 为 EdgeButton 提供了一个插槽,该插槽占据滚动列表下方可用空间。当用户滚动到列表末尾时,它会放大并淡入,当用户向上滚动时,它会缩小并淡出。让我们将一个 EdgeButton 添加到我们的代码中

步骤 11

ScreenScaffold(
  scrollState = listState,
  contentPadding = rememberResponsiveColumnPadding(
    first = ColumnItemType.IconButton,
    last = ColumnItemType.Button,
  ),
/* *************************** Part 11: EdgeButton *************************** */
  // TODO: Add a EdgeButton
   edgeButton = {
     EdgeButton(
      onClick = { /* ... */ },
      buttonSize = EdgeButtonSize.Medium) {
        Text(stringResource(R.string.more))
      }
   }

您可以为 EdgeButton 指定 4 种不同的大小ExtraSmallSmallMediumLarge

现在让我们看看它长什么样

3a973bbfe4941e67.png

干得好,您已经完成了 Wear OS 大多数可组合项的 UI 演示!

9. 祝贺

恭喜!您已学习了在 Wear OS 上使用 Compose 的基础知识!

现在您可以将所有 Compose 知识重新应用于制作精美的 Wear OS 应用程序了!

下一步是什么?

查看其他 Wear OS Codelabs

延伸阅读

反馈

我们很乐意听取您使用 Compose for Wear OS 的体验以及您能够构建的内容!加入 Kotlin Slack#compose-wear 频道进行讨论,并继续在 问题跟踪器上提供反馈。

编码愉快!