Wear OS 代码实验室

1. 简介

2c9dd335c9d65f10.png

Wear OS Compose 允许您将使用 Jetpack Compose 构建应用程序的知识迁移到可穿戴设备。

Wear OS Compose 内置支持 Material Design,简化和加速 UI 开发,帮助您使用更少的代码创建漂亮的应用程序。

在本代码实验室中,我们希望您具备一些 Compose 的知识,但您肯定不需要成为专家。

我们将使用 Horologist,这是一个基于 Jetpack Compose 构建的开源项目,可帮助开发人员加速应用程序开发。

您将创建多个针对 Wear 的可组合项(简单和复杂的),最后,您可以开始编写自己的 Wear OS 应用程序。让我们开始吧!

您将学到什么

  • Compose 的先前经验的相似之处/不同之处
  • 简单的可组合项及其在 Wear OS 上的工作方式
  • 针对 Wear OS 的可组合项
  • Wear OS 的 LazyColumn (ScalingLazyColumn)
  • Wear OS 版本的 Scaffold

您将构建什么

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

由于您将使用 Scaffold,您还将在顶部获得弯曲的文本时间,一个 晕影,最后是一个与设备侧面绑定的滚动指示器。

以下是完成代码实验室后的样子

31cb08c0fa035400.gif

先决条件

2. 设置

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

您需要什么

  • 最新稳定版本的 Android Studio
  • Wear OS 设备或模拟器(不熟悉?此处 说明如何设置。)

下载代码

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

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

如果您没有 git,可以单击以下按钮下载本代码实验室的所有代码

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

b059413b0cf9113a.png

在 Android Studio 中打开项目

  1. 在“欢迎使用 Android Studio”窗口中,选择 c01826594f360d94.png 打开现有项目
  2. 选择文件夹 [下载位置]
  3. 当 Android Studio 导入项目后,请测试您是否可以在 Wear OS 模拟器或物理设备上运行 startfinished 模块。
  4. start 模块应该类似于下面的屏幕截图。您将在那里进行所有工作。

d6d4b92ac53d9b3e.png

探索开始代码

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

3. 查看依赖项

您进行的与 Wear 相关的依赖项更改大多将在顶层 体系结构层(如下面的红色突出显示部分)中完成。

d92519e0b932f964.png

这意味着您之前在 Jetpack Compose 中使用的许多依赖项在以 Wear OS 为目标时不会更改。例如,UI、运行时、编译器和动画依赖项将保持不变。

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

以下是一个比较,以帮助阐明差异

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

比较

标准依赖项(androidx.*)

androidx.wear.compose:compose-material

而不是

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. 开发人员可以使用 Wear Compose Material 库继续使用其他与 Material 相关的库,如 Material Ripple 和 Material Icons Extended。

打开 build.gradle,在您的 start 模块中搜索“TODO: Review Dependencies”。(此步骤只是为了查看依赖项,您无需添加任何代码。)

start/build.gradle

   def 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)

    debugImplementation libs.compose.ui.tooling
    debugImplementation libs.androidx.ui.test.manifest
    debugImplementation composeBom

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

让我们转到 Wear OS 依赖项。

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

重要的是要注意,您可以将其他 Material 库与 Wear Material 结合使用。实际上,我们在此代码实验室中通过包含 androidx.compose.material:material-icons-extended 来做到这一点。

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

好的,现在我们已经了解了依赖项,让我们看看主应用程序。

4. 查看 MainActivity

我们将在

start

模块中完成所有工作,因此请确保您打开的每个文件都在那里。

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

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

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

向下滚动到 WearApp() 可组合函数。在我们讨论代码本身之前,您应该看到代码中散布着许多 TODO。这些都代表了本代码实验室中的步骤。您现在可以忽略它们。

它应该类似于这样

fun WearApp() 中的代码

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

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

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

    // Modifiers used by our Wear composables.
    val contentModifier = Modifier.fillMaxWidth().padding(bottom = 8.dp)
    val iconModifier = Modifier.size(24.dp).wrapContentSize(align = Alignment.Center)

    /* *************************** Part 3: ScalingLazyColumn *************************** */
    // TODO: Swap a ScalingLazyColumn (Wear's version of LazyColumn)
    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(
            top = 32.dp,
            start = 8.dp,
            end = 8.dp,
            bottom = 32.dp,
        ),
        verticalArrangement = Arrangement.Center,
        state = listState,
    ) {
        // TODO: Remove item; for beginning only.
        item { StartOnlyTextComposables() }

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

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

    // 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 文件进一步探索,但是,同样,它与手机上的内容相同。

接下来,我们为即将构建的 Wear 可组合项创建一些修饰符,这样就不需要每次都指定它们。它主要是居中内容并添加一些填充。

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

代码

item { StartOnlyTextComposables() }

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

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

对于项目本身,只有 StartOnlyTextComposables() 会生成任何 UI。(我们将在整个代码实验室中填充其余部分。)

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

让我们开始使用 Compose 为 Wear OS 开发吧!

5. 添加简单可组合项

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

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

搜索“TODO: Remove item” 并删除注释和它下面的行

步骤 1

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

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

创建 Button 可组合项

start 模块中打开 ReusableComponents.kt,搜索“TODO: Create a Button Composable”,并将当前的可组合方法替换为以下代码。

步骤 2

// TODO: Create a Button Composable (with a Row to center)
@Composable
fun ButtonExample(
    modifier: Modifier = Modifier,
    iconModifier: Modifier = Modifier
) {
    Row(
        modifier = modifier,
        horizontalArrangement = Arrangement.Center
    ) {
        // Button
        Button(
            modifier = Modifier.size(ButtonDefaults.LargeButtonSize),
            onClick = { /* ... */ },
        ) {
            Icon(
                imageVector = Icons.Rounded.Phone,
                contentDescription = "triggers phone action",
                modifier = iconModifier
            )
        }
    }
}

ButtonExample() 可组合函数(代码存在的地方)现在将生成一个居中的按钮。

让我们逐步分析代码。

Row 仅用于此处,以在圆形屏幕上居中 Button 可组合项。您可以看到,我们通过应用在 MainActivity 中创建的修饰符并将它传递给此函数来实现。稍后,当我们在圆形屏幕上滚动时,我们希望确保我们的内容不会被截断(这就是它居中的原因)。

接下来,我们创建 Button 本身。代码与您之前使用 Button 的代码相同,但是,在我们的例子中,我们使用 ButtonDefault.LargeButtonSize。这些是针对 Wear OS 设备优化的预设大小,因此请确保使用它们!

之后,我们将点击事件设置为一个空 lambda。在我们的例子中,这些可组合项只是为了演示,因此我们不需要它。但是,在实际应用中,我们会与例如 ViewModel 通信来执行业务逻辑。

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

最后,我们设置之前为图标设置的修饰符。

如果您运行应用程序,您应该会得到类似这样的结果

881cfe2dcdef5687.png

这可能是您之前已经编写的代码(这很棒)。不同之处在于现在您得到一个针对 Wear OS 优化的按钮。

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

创建 Text 可组合项

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

步骤 3

// TODO: Create a Text Composable
@Composable
fun TextExample(modifier: Modifier = Modifier) {
    Text(
        modifier = modifier,
        textAlign = TextAlign.Center,
        color = MaterialTheme.colors.primary,
        text = stringResource(R.string.device_shape)
    )
}

我们创建 Text 可组合项,设置其修饰符,对齐文本,设置颜色,最后从字符串资源设置文本本身。

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

让我们看看它是什么样的

b4f0e65e666cf3eb.png

TextExample() 可组合函数(我们放置代码的地方)现在在我们的主要材质颜色中生成一个 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.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 可组合项参数(每个参数对应卡片上的一个不同空间),最后在末尾设置主要内容文本。

让我们看看它是什么样的

430eaf85d8ee5883.png

此时,您可能已经认识到,对于这些可组合项来说,Compose 代码与您之前使用的代码几乎相同,这很棒,对吧?您可以重复使用已经获得的所有知识!

好了,让我们看一下一些更高级的可组合项。

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

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

创建 Chip 可组合项

芯片实际上是在材质指南中指定的,但在标准材质库中没有实际的可组合函数。

它们旨在快速执行一次点击操作,这对于屏幕空间有限的 Wear 设备来说尤其有意义。

以下是一些 Chip 可组合函数的变体,让您了解可以创建的内容

让我们编写一些代码。

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

步骤 5

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

Chip 可组合项使用许多您熟悉的与其他可组合项相同的参数(修饰符和 onClick),因此我们不需要回顾这些参数。

它还接受一个标签(我们为它创建一个 Text 可组合项)和一个图标。

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

让我们看看它是什么样的(记得向下滚动)

bd178a52438cfcbc.png

好了,让我们看一下 Toggle 的变体,ToggleChip 可组合项。

创建 ToggleChip 可组合项

ToggleChip 就像一个 Chip,但允许用户与单选按钮、切换按钮或复选框进行交互。

ReusableComponents.kt 中,搜索“TODO: Create a ToggleChip”,并将当前的可组合方法替换为以下代码。

步骤 6

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

现在,ToggleChipExample() 可组合函数(代码存在的地方)将使用切换按钮(而不是复选框或单选按钮)生成一个 ToggleChip

首先,我们创建一个 MutableState。我们之前在其他函数中没有这样做,因为我们主要进行 UI 演示,这样您就可以看到 Wear 提供的功能。

在正常的应用程序中,您可能希望传入选中状态和处理点击的 lambda,以便可组合项可以是无状态的(此处了解更多信息)。

在我们的例子中,我们只是为了简单起见,展示了 ToggleChip 在具有工作切换按钮(即使我们没有对状态进行任何操作)的情况下是怎样的。

接下来,我们设置修饰符、选中状态和切换控件,以提供我们想要的开关。

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

让我们看看它是什么样的

76a0b8d96fd36438.png

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

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

7. 迁移到 ScalingLazyColumn

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

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

ScalingLazyColumn 扩展了 LazyColumn 以支持在屏幕顶部和底部进行缩放和透明度处理,以便用户可以更轻松地阅读内容。

这是一个演示

198ee8e8fa799f08.gif

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

以下是一个来自应用程序的更具体的示例

a5a83ab2e5d5230f.gif

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

既然您已经看到了ScalingLazyColumn 的实际应用,让我们开始将我们的 LazyColumn 转换。

我们将使用 Horologist ScalinglazyColumn 来确保列表中的项目具有正确的填充,并且不会在不同设备屏幕尺寸上被裁剪。

转换为 Horologist ScalingLazyColumnState

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

步骤 7

// TODO: Swap to ScalingLazyColumnState
val listState = rememberResponsiveColumnState(
    contentPadding = ScalingLazyColumnDefaults.padding(
        first = ItemType.SingleButton,
        last = ItemType.Chip,
    ),
)

名称几乎完全相同。就像 LazyListState 处理 LazyColumn 的状态一样,ScalingLazyColumnState 处理 ScalingLazyColumn 的状态。

转换为 Horologist ScalingLazyColumn

接下来,我们将替换为 ScalingLazyColumn

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

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

步骤 8

// TODO: Swap a ScalingLazyColumn (Wear's version of LazyColumn)
ScalingLazyColumn(
    columnState = listState

就是这样!让我们看看它的样子

442700f212089fd0.png

您可以看到内容已缩放,并且在您滚动时屏幕顶部和底部的透明度进行了调整,迁移工作量很小!

当您上下移动它时,您真的可以注意到冥想可组合物的效果。

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

8. 添加 Scaffold

Scaffold 提供了一个布局结构,可以帮助您按照通用模式排列屏幕,就像移动设备一样,但它不包含 应用栏、FAB、抽屉或其他特定于移动设备的元素,而是支持四个具有顶级组件的 Wear 特定布局:时间、滚动/位置指示器和页面指示器。

它们看起来像这样

TimeText

PositionIndicator

PageIndicator

我们将详细介绍前三个组件,但首先,让我们将脚手架放在适当的位置。

我们将使用 Horologist AppScaffoldScreenScaffold,它们默认情况下会在屏幕上添加一个 TimeText

并确保在屏幕之间导航时动画正确。

此外,ScreenScaffold 为可滚动内容添加了 PositionIndicator

添加 Scaffold

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

找到 "TODO (Start): Create a AppScaffold (Wear Version)" 并将其下面的代码添加进去。

步骤 9

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

找到 "TODO (Start): Create a ScreenScaffold (Wear Version)" 并将其下面的代码添加进去。

// TODO (Start): Create a Horologist ScreenScaffold (Wear Version)
ScreenScaffold( 
    scrollState = listState,
){

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

找到 "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)
}

让我们先运行它。您应该会看到类似于以下内容

c2cb637a495bc977.png

注意它添加了

好的,让我们看看现在的样子

1b6fa8091b920587.png

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

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

9. 祝贺

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

现在,您可以将您所有的 Compose 知识重新应用于创建漂亮的 Wear OS 应用!

接下来是什么?

查看其他 Wear OS 代码实验室

进一步阅读

反馈

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

祝您编码愉快!