1. 简介
借助 Compose for Wear OS,您可以将您学到的有关使用 Jetpack Compose 构建应用的知识应用于穿戴式设备。
由于内置了对 Material Design 的支持,Compose for Wear OS 可以简化并加快界面开发流程,并可帮助您使用更少的代码创建精美的应用。
对于本 Codelab,我们希望您具备一些 Compose 方面的知识,但当然您不必是这方面的专家。
您将创建一些专用于 Wear 的可组合项(简单的和复杂的都有),最后,您可以开始编写适用于 Wear OS 的应用。立即开始吧!
学习内容
- 与您之前的 Compose 使用体验有何相似/不同之处
- 简单的可组合项及其在 Wear OS 上的运作方式
- 专用于 Wear OS 的可组合项
- Wear OS 的
LazyColumn
(TransformingLazyColumn
) - Wear OS 的
Scaffold
版本
构建内容
您将构建一个简单的应用,该应用会显示一个可滚动列表,在其中列出已针对 Wear OS 优化的可组合项。
由于您将使用 AppScaffold 和 ScreenScaffold,因此您还会在顶部以弧形文字样式显示时间,并沿着设备侧边显示滚动指示器。
以下是完成此 Codelab 后,您构建的应用的样子。
前提条件
- 对 Android 开发有基本的了解
- 对 Kotlin 有基本的了解
- 具备 Compose 方面的基础知识
2. 准备设置
在此步骤中,您将设置环境并下载起始项目。
所需条件
- 最新的稳定版 Android Studio
- Wear OS 设备或模拟器(第一次使用?这里介绍了设置方法。)
下载代码
如果您已安装 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 中运行其中任一模块。
在 Android Studio 中打开项目
- 在“Welcome to Android Studio”窗口中,选择
Open...
- 选择文件夹
[Download Location]
。 - Android Studio 导入项目后,请测试您能否在 Wear OS 模拟器或实体设备上运行
start
和finished
模块。 start
模块应如以下屏幕截图所示。您将在其中完成所有工作。
探索起始代码
build.gradle
中包含基本的应用配置。它包含创建 Composable Wear OS 应用所需的依赖项。我们将讨论 Jetpack Compose 版本与 Wear OS 版本之间的相似之处和不同之处。main > AndroidManifest.xml
包含创建 Wear OS 应用所需的元素。这与非 Compose 应用相同,并且与移动应用类似,因此我们不讨论这一点。main > theme/
文件夹包含 Compose 针对主题使用的Color
、Type
和Theme
文件。main > MainActivity.kt
包含使用 Compose 创建应用的样板文件。它还包含应用的顶级可组合项(如Scaffold
和TransformingLazyColumn
)。main > ReusableComponents.kt
包含适用于我们将创建的大多数 Wear 专用可组合项的函数。我们将在此文件中完成许多任务。
3. 查看依赖项
您所做的大多数与 Wear 相关的依赖项更改都将位于顶级架构层(下面红色突出显示的部分)。
这意味着,当您以 Wear OS 为目标平台时,您已经结合 Jetpack Compose 使用过的许多依赖项不会发生变化。例如,界面、运行时、编译器和动画依赖项将保持不变。
然而,您需要使用正确的 Wear OS Material、Foundation 和 Navigation 库,这些库不同于您之前使用过的库。
我们在下方进行了对比,进一步阐明相关差异:
Wear OS 依赖项 (androidx.wear.*) | 比较 | 标准依赖项 (androidx.*) |
代替 | androidx.compose.material:material ₁ | |
代替 | androidx.navigation:navigation-compose | |
加上 | androidx.compose.foundation:foundation | |
加上 | androidx.compose.ui:ui-tooling-preview |
1. 开发者可以继续使用其他与 Material 相关的库,例如借助 Compose Material 库扩展的 Material 涟漪和 Material 图标。除了使用 compose-material3 版本之外,您还可以使用 androidx.wear.compose:compose-material,但不应在同一应用中混用 Material 3 和 Material 2.5。我们建议您使用 material3,因为它支持 Material 3 Expressive 设计。
打开 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 结合使用。在本 Codelab 中,我们实际上将通过添加 androidx.compose.material:material-icons-extended
来实现这一点。
最后,我们添加了适用于 Compose 的 Wear foundation
库 (androidx.wear.compose:compose-foundation
)。这是附加的,因此您可以将该库与您之前用过的标准 foundation
搭配使用。实际上,您可能已经意识到,我们已将它添加到常规 Compose 依赖项中!
现在,我们已经了解了依赖项,接下来,我们来看一下主应用。
4. 查看 MainActivity
我们要在
start
模块完成所有任务,因此请确保打开的每个文件都位于这个模块中。
首先,打开 start
模块中的 MainActivity
。
这是一个非常简单的类,用于扩展 ComponentActivity
并使用 setContent { WearApp() }
来创建界面。
根据您之前所掌握的 Compose 方面的知识,您应该已对此很熟悉。我们只需设置界面即可。
向下滚动到 WearApp()
可组合函数。在我们谈论代码本身之前,您应该会看到很多 TODO 分散在整个代码中。这些 TODO 分别表示此 Codelab 中的步骤。您可以暂时忽略它们。
代码应如下所示:
函数 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()
会生成界面。(我们将随着您在 Codelab 中的进展来填充其余内容。)
这些函数实际上位于 ReusableComponents.kt
文件中,我们将在下一部分中进行介绍。
让我们先来了解一下 Compose for Wear OS 吧!
5. 添加简单的可组合项
我们将从您可能早已熟悉的三个可组合项(Button
、Text
和 Card
)开始。
首先,我们将移除 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
,这是一种仅包含图标的圆形按钮,具有彩色背景和对比鲜明的内容颜色。该组件提供一个用于接收图标或图片的槽位。
然后,我们将点击事件设为空白 lamba。在本例中,这些可组合项仅用于演示,所以我们不需要它们。然而,在实际应用中,我们将通过与 ViewModel 等进行通信以执行业务逻辑。
然后,我们在按钮内设置一个图标。此代码与您之前看到过的 Icon
代码相同。此外,我们还要从 androidx.compose.material:material-icons-extended
库中获取图标。
最后,您无需担心在此阶段将按钮居中显示,因为在从 LazyColumn
迁移到 TransformingLazyColumn.
时,系统会自动修正按钮位置
运行应用后,您应该会看到如下内容,如果按钮暂时未居中,请勿担心:
这些代码您之前可能已经编写过(太棒了)。不同之处在于,您现在可以获取针对 Wear OS 优化的按钮。
很简单,我们再来看看其他代码。
创建 Text 可组合项
在 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
中,以便内容在首尾两端添加内边距。
Compose 开发者应该会非常熟悉 Text 可组合项的外观,其代码实际上与您之前使用的代码完全相同。
我们来看一下它是什么样的:
TextExample()
可组合函数(我们放置代码的位置)现在会以主要 Material 颜色生成 Text 可组合函数。该字符串是从我们的 res/values/strings.xml
文件中拉取的。
目前的过程一切顺利。我们再来看看最后一个相似的可组合项 Card
。
创建 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 略有不同,因为有两张主卡片:AppCard
和 TitleCard
。
在本例中,我们想在卡片中添加 Icon
,因此我们将使用 AppCard
。(TitleCard
包含的槽较少,请参阅卡片指南以了解有关详情)。
我们创建 AppCard
可组合项,设置其修饰符,添加 Icon
,添加多个 Text
可组合项参数(分别用于配置卡片上的不同空间),最后将主要内容文本设置在最后。
我们来看一看效果如何,请不要担心添加额外的底部内边距,因为在从 LazyColumn
迁移到 TransformingLazyColumn
时,系统会修正此问题:
这时,您可能意识到,这些可组合项的 Compose 代码与您之前用过的代码几乎相同,这很不错吧?您可以重复利用您已获得的所有知识!
好的,我们来看一些新的可组合项。
6. 添加 Wear 专用可组合项
在这一部分中,我们将探索 Chip
和 ToggleChip
可组合项。
创建 Chip 可组合项
Chip 是一种一键式快捷操作,对屏幕空间有限的 Wear 设备尤为有用。
您可以使用 Button
实现 Chip。下面是 Button
可组合函数的几种变体,您可通过它们了解您能创建什么:
让我们开始编写代码吧。
在 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
可组合项所用的许多参数都与其他可组合项(修饰符和 onClick
)所用的参数相同,因此我们无需查看这些参数。
此外,它还接受一个图标和一个内容槽位,用于呈现按钮上显示的可组合正文内容(我们为其创建一个 Text
可组合项)。
Icon
代码应该与您在其他可组合项中看到的代码完全相同,但对于此代码,我们将从 androidx.compose.material:material-icons-extended
库中提取 Self Improvement
图标。
我们来看看效果如何(记得要向下滚动),不用担心添加额外的底部内边距,因为从 LazyColumn
迁移到 TransformingLazyColumn
时系统会自动修正此问题:
我们来看一下 Button
的一个变体,即 SwitchButton
可组合项。
创建 SwitchChip 可组合项
SwitchButton
与 Button
类似,但支持用户与开关进行交互。
在 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
。由于其他功能主要用于提供界面演示,方便您了解 Wear 提供哪些功能,因此我们尚未在其他函数中执行此操作。
在普通应用中,您可能希望传递选中状态以及处理点按的 lambda,因此可组合项可以是无状态的(点击此处了解详情)。
在本例中,我们只是使用操作切换开关来简单展示 SwitchChip
的实际效果(即使我们不对状态执行任何操作)。
接下来,我们设置选中状态和切换开关控件,为我们提供所需的开关。
然后,我们创建一个 lambda 来改变状态,最后使用 Text
可组合项(以及一些基本参数)设置标签。
我们来看一下它是什么样的:
现在,您已经看到了许多特定于 Wear OS 的可组合项,并且如前所述,大多数代码与您之前编写的代码几乎相同。
我们再来看一些更高级的代码。
7. 迁移到 TransformingScalingLazyColumn
您可能在移动应用中使用了 LazyColumn
来生成垂直滚动列表。
由于圆形设备的顶部和底部较小,因此显示内容的空间较小。因此,Wear OS 有自己的 LazyColumn
版本,可以更好地支持这些圆形设备。
转换 LazyColumn
会扩展 LazyColumn
,以支持屏幕顶部和底部的缩放和透明度,使内容更易于用户阅读。
下面是一个演示:
注意,当红色对象靠近中心位置时,会放大到完整尺寸,然后随着移开而缩小(并且变得更透明)。
以下是一个来自应用的具体示例:
我们发现这确实有助于提高可读性。
现在,您已了解了 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, verticalArrangement
和 modifier
一起移除。TransformingLazyColumn
已提供默认设置,可保证实现更好的默认视觉效果,因为大多数视口会填充列表项。在大多数情况下,使用默认形参就足够了,如果您在顶部有标题,我们建议您将其作为第一项放入 ListHeader
中。
第 8 步
// TODO: Swap a TransformingLazyColumn (Wear's version of LazyColumn)
TransformingLazyColumn(
state = listState,
contentPadding = contentPadding)
为项添加滚动转换效果
现在,我们使用以下 Modifier
和 SurfaceTransformation
为 TransformingLazyColumn
元素添加 ShapeMorphing 效果。将同一代码应用于所有组件(
除尚不支持此功能的 IconButtonExample
之外).
第 9 步
TextExample(
modifier = Modifier.fillMaxWidth().transformedHeight(this, transformationSpec),
transformation = SurfaceTransformation(transformationSpec),
)
大功告成!我们来看一下它是什么样的:
滚动屏幕时您可以看到,内容会缩放且屏幕顶部和底部的透明度会调整,只需执行极少工作即可完成迁移!
在上下移动冥想可组合项时,您可以真正观察它。
现在谈到最后一个主题,Wear OS 的 Scaffold
。
8. 添加 Scaffold
AppScaffold 和 ScreenScaffold 提供了一种布局结构来帮助您按照常见模式排列界面,就像在移动设备上一样,但与应用栏、FAB、抽屉式导航栏或其他移动设备专用元素不同的是,它支持包含顶层组件的三种 Wear 专用布局:时间、滚动/位置指示器和页面指示器。
这些信息大致是这样显示的:
我们将详细介绍前三个组件,不过,我们首先搭建 Scaffold。
基架组件 AppScaffold
和 ScreenScaffold
会布局屏幕的结构,并协调 ScrollIndicator
和 TimeText
组件的转换。
AppScaffold
可让静态屏幕元素(例如 TimeText
)在应用内过渡(例如滑动关闭)期间保持可见。ScreenScaffold
默认会在屏幕中心位置显示 ScrollIndicator
,并协调显示/隐藏 TimeText
和 ScrollIndicator
添加 Scaffold
现在,我们为 AppScaffold
和 ScreenScaffold
添加样板代码。
找到“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)
}
我们先运行该代码。您应该会看到与以下类似的内容:
请注意,代码中添加了以下内容:
TimeText
:此项在后台使用曲线文本样式,让开发者可以轻松显示时间,而无需放置可组合函数或处理与时间相关的类。此外,Material 准则建议您在应用内任何界面的顶部显示时间,并在滚动时让时间淡出。ScrollIndicator
:此项是界面右侧的指示器,用于根据传入的状态对象的类型显示当前指示器的位置。在本例中,状态对象是TransformingLazyColumnState
。
现在,我们来预览一下效果如何:
请尝试上下滚动。您应该只会在滚动时看到滚动指示器显示。
添加边缘填充按钮
EdgeButton
是适用于 Wear OS 版本的 M3 Compose 素材中的一种新的表达式按钮。边缘填充容器是一种新形状,它采用圆形设计,并最大限度地利用圆形设备规格中的空间。
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 种不同的尺寸:ExtraSmall
、Small
、Medium
和 Large
。
现在,我们来预览一下效果如何:
太棒了,您已完成了大多数 Wear OS 可组合项的界面演示!
9. 恭喜
恭喜!您已学习了在 Wear OS 上使用 Compose 的基础知识!
现在,您可以重新应用您掌握的所有 Compose 知识,打造精美的 Wear OS 应用!
后续操作
查看其他 Wear OS Codelab:
深入阅读
- GitHub 上的 Compose Starter 示例
- 如需更多指南,请参阅借助 Wear OS 开发适用于手表的应用
- 在 Wear OS 上使用 Jetpack Compose
反馈
我们希望倾听有关您使用 Compose for Wear OS 的体验以及您可以构建的内容方面的反馈!欢迎加入 Kotlin Slack #compose-wear 渠道的讨论,并在问题跟踪器中持续提供反馈。
乐享编码!