创建一个交互式骰子应用

1. 开始之前

在本 Codelab 中,您将创建一个交互式骰子应用,用户可以点按 Button 可组合项来掷骰子。掷骰的结果将通过屏幕上的 Image 可组合项显示。

您将使用 Jetpack Compose 和 Kotlin 构建应用布局,然后编写业务逻辑来处理点按 Button 可组合项时发生的情况。

前提条件

  • 能够在 Android Studio 中创建和运行基本的 Compose 应用。
  • 熟悉如何在应用中使用 Text 可组合项。
  • 了解如何将文本提取到字符串资源中,以便更轻松地翻译您的应用并重用字符串。
  • 了解 Kotlin 编程基础知识。

学习内容

  • 如何使用 Compose 将 Button 可组合项添加到 Android 应用。
  • 如何使用 Compose 为 Android 应用中的 Button 可组合项添加行为。
  • 如何打开和修改 Android 应用的 Activity 代码。

构建内容

  • 一个名为 Dice Roller 的交互式 Android 应用,允许用户掷骰子并向他们显示结果。

准备事项

  • 一台安装了 Android Studio 的计算机。

完成此 Codelab 后应用的外观如下

3e9a9f44c6c84634.png

2. 建立基线

创建项目

  1. 在 Android Studio 中,依次点击 File > New > New Project
  2. New Project 对话框中,选择 Empty Activity,然后点击 Next

39373040e14f9c59.png

  1. Name 字段中,输入 Dice Roller
  2. Minimum SDK 字段中,从菜单中选择最低 API 级别 24 (Nougat),然后点击 Finish

8fd6db761068ca04.png

3. 创建布局基础结构

预览项目

预览项目

  • SplitDesign 面板中,点击 Build & Refresh

9f1e18365da2f79c.png

现在您应该在 Design 面板中看到预览。如果看起来很小,请不用担心,因为它在您修改布局时会改变。

b5c9dece74200185.png

重构示例代码

您需要修改一些生成的代码,使其更符合骰子应用的主题。

如您在最终应用的截图中看到的,有一个骰子图片和一个用于掷骰子的按钮。您将构建可组合函数以反映此架构。

重构示例代码

  1. 删除 GreetingPreview() 函数。
  2. 创建一个带有 @Composable 注解的 DiceWithButtonAndImage() 函数。

此可组合函数表示布局的 UI 组件,并包含按钮点击和图片显示逻辑。

  1. 删除 Greeting(name: String, modifier: Modifier = Modifier) 函数。
  2. 创建一个带有 @Preview@Composable 注解的 DiceRollerApp() 函数。

由于此应用仅包含一个按钮和一张图片,您可以将此可组合函数视为应用本身。这就是它被称为 DiceRollerApp() 函数的原因。

MainActivity.kt

@Preview
@Composable
fun DiceRollerApp() {

}

@Composable
fun DiceWithButtonAndImage() {

}

由于您删除了 Greeting() 函数,DiceRollerTheme() lambda 主体中对 Greeting("Android") 的调用将显示为红色高亮。这是因为编译器找不到对该函数的引用了。

  1. 删除 onCreate() 方法中 setContent{} lambda 内的所有代码。
  2. setContent{} lambda 主体中,调用 DiceRollerTheme{} lambda,然后在 DiceRollerTheme{} lambda 内部,调用 DiceRollerApp() 函数。

MainActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        DiceRollerTheme {
            DiceRollerApp()
        }
    }
}
  1. DiceRollerApp() 函数中,调用 DiceWithButtonAndImage() 函数。

MainActivity.kt

@Preview
@Composable
fun DiceRollerApp() {
    DiceWithButtonAndImage()
}

添加修饰符

Compose 使用一个 Modifier 对象,它是一系列用于装饰或修改 Compose UI 元素行为的元素的集合。您可以使用它来设置骰子应用组件的 UI 组件样式。

添加修饰符

  1. 修改 DiceWithButtonAndImage() 函数以接受一个类型为 Modifiermodifier 参数,并为其分配默认值 Modifier

MainActivity.kt

@Composable 
fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
}

上面的代码段可能会让您感到困惑,我们来分解一下。该函数允许传入一个 modifier 参数。该 modifier 参数的默认值是一个 Modifier 对象,因此方法签名中有了 = Modifier 部分。参数的默认值让任何将来调用此方法的人可以决定是否为该参数传递一个值。如果他们传递自己的 Modifier 对象,他们可以自定义 UI 的行为和装饰。如果他们选择不传递 Modifier 对象,则它将采用默认值,即纯粹的 Modifier 对象。您可以将此做法应用于任何参数。有关默认参数的更多信息,请参阅 默认参数

  1. 现在 DiceWithButtonAndImage() 可组合项有一个修饰符参数,在调用该可组合项时传递一个修饰符。由于 DiceWithButtonAndImage() 函数的方法签名发生了变化,因此在调用它时应该传入一个带有所需装饰的 Modifier 对象。Modifier 类负责 DiceRollerApp() 函数中可组合项的装饰或行为添加。在这种情况下,需要向传递给 DiceWithButtonAndImage() 函数的 Modifier 对象添加一些重要的装饰。

您可能会奇怪,既然有默认值,为什么还要费心传递 Modifier 参数。原因是可组合项会 undergo recomposition(重组),这本质上意味着 @Composable 方法中的代码块会再次执行。如果在代码块中创建了一个 Modifier 对象,它可能会被重新创建,这会降低效率。重组将在本 Codelab 的后面部分介绍。

MainActivity.kt

DiceWithButtonAndImage(modifier = Modifier)
  1. fillMaxSize() 方法链式调用到 Modifier 对象上,以便布局填充整个屏幕。

此方法指定组件应填充可用空间。在本 Codelab 的前面部分,您看到了 Dice Roller 应用最终 UI 的截图。一个显著特点是骰子和按钮位于屏幕中央。wrapContentSize() 方法指定可用空间应至少与其中组件的大小相等。但是,由于使用了 fillMaxSize() 方法,如果布局中的组件小于可用空间,则可以将 Alignment 对象传递给 wrapContentSize() 方法,该方法指定组件应如何在可用空间内对齐。

MainActivity.kt

DiceWithButtonAndImage(modifier = Modifier
    .fillMaxSize()
)
  1. wrapContentSize() 方法链式调用到 Modifier 对象上,然后将 Alignment.Center 作为参数传递以使组件居中。Alignment.Center 指定组件在垂直和水平方向上都居中。

MainActivity.kt

DiceWithButtonAndImage(modifier = Modifier
    .fillMaxSize()
    .wrapContentSize(Alignment.Center)
)

4. 创建垂直布局

在 Compose 中,使用 Column() 函数创建垂直布局。

Column() 函数是一个可组合布局,它将其子项按垂直顺序放置。在预期的应用设计中,您可以看到骰子图片垂直显示在掷骰子按钮上方

7d70bb14948e3cc1.png

创建垂直布局

  1. DiceWithButtonAndImage() 函数中,添加一个 Column() 函数。
  1. DiceWithButtonAndImage() 方法签名中的 modifier 参数传递给 Column() 的修饰符参数。

modifier 参数确保 Column() 函数中的可组合项遵循在 modifier 实例上调用的约束。

  1. horizontalAlignment 参数传递给 Column() 函数,然后将其值设置为 Alignment.CenterHorizontally

这可确保列中的子项相对于宽度在设备屏幕上居中。

MainActivity.kt

fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
    Column (
        modifier = modifier,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {}
}

5. 添加按钮

  1. strings.xml 文件中,添加一个字符串并将其值设置为 Roll

res/values/strings.xml

<string name="roll">Roll</string>
  1. Column() 的 lambda 主体中,添加一个 Button() 函数。
  1. MainActivity.kt 文件中,在 Button() 的 lambda 主体中添加一个 Text() 函数。
  2. roll 字符串的字符串资源 ID 传递给 stringResource() 函数,并将结果传递给 Text 可组合项。

MainActivity.kt

Column(
    modifier = modifier,
    horizontalAlignment = Alignment.CenterHorizontally
) {
    Button(onClick = { /*TODO*/ }) {
        Text(stringResource(R.string.roll))
    }
}

6. 添加图片

应用的另一个重要组件是骰子图片,它会在用户点按掷骰按钮时显示结果。您可以使用 Image 可组合项添加图片,但这需要图片资源,因此您首先需要下载为此应用提供的一些图片。

下载骰子图片

  1. 打开此 URL,将包含骰子图片的 zip 文件下载到您的计算机,然后等待下载完成。

在您的计算机上找到该文件。它可能位于您的下载文件夹中。

  1. 解压 zip 文件以创建一个新的 dice_images 文件夹,其中包含六个骰子图片文件,骰子值从 1 到 6。

将骰子图片添加到您的应用

  1. 在 Android Studio 中,依次点击 View > Tool Windows > Resource Manager
  2. 点击 + > Import Drawables 打开文件浏览器。

12f17d0b37dd97d2.png

  1. 找到并选择包含六个骰子图片的文件夹,然后继续上传。

上传的图片将如下所示。

4f66c8187a2c58e2.png

  1. 点击 Next

688772df9c792264.png

出现 Import Drawables 对话框,显示资源文件在文件结构中的位置。

  1. 点击 Import 确认您要导入这六张图片。

图片应出现在 Resource Manager 面板中。

c2f08e5311f9a111.png

干得好!在下一个任务中,您将在应用中使用这些图片。

添加 Image 可组合项

骰子图片应显示在掷骰按钮上方。Compose 内在地按顺序放置 UI 组件。换句话说,先声明的可组合项先显示。这可能意味着第一个声明的显示在紧随其后声明的可组合项的上方或之前。Column 可组合项内部的可组合项将设备上彼此上方/下方出现。在此应用中,您使用 Column 垂直堆叠可组合项,因此,在同一个 Column() 函数内,在先声明的可组合项将显示在紧随其后声明的可组合项之前。

添加 Image 可组合项

  1. Column() 函数体中,在 Button() 函数之前创建一个 Image() 函数。

MainActivity.kt

Column(
    modifier = modifier,
    horizontalAlignment = Alignment.CenterHorizontally
) {
    Image()
    Button(onClick = { /*TODO*/ }) {
      Text(stringResource(R.string.roll))
    }
}
  1. Image() 函数传递一个 painter 参数,然后为其分配一个接受 drawable 资源 id 参数的 painterResource 值。现在,传递以下资源 id:R.drawable.dice_1 参数。

MainActivity.kt

Image(
    painter = painterResource(R.drawable.dice_1)
)
  1. 任何时候在应用中创建 Image,您都应该提供所谓的“内容说明”。内容说明是 Android 开发的重要组成部分。它们将其说明附加到各自的 UI 组件,以增强可访问性。有关内容说明的更多信息,请参阅描述每个 UI 元素。您可以将内容说明作为参数传递给图片。

MainActivity.kt

Image(
    painter = painterResource(R.drawable.dice_1),
    contentDescription = "1"
)

现在所有必要的 UI 组件都已到位。但是 ButtonImage 有点相互拥挤。

54b27140071ac2fa.png

  1. 为了解决这个问题,在 ImageButton 可组合项之间添加一个 Spacer 可组合项。Spacer 接受一个 Modifier 作为参数。在这种情况下,ImageButton 的上方,因此它们之间需要有一个垂直空间。因此,可以设置 Modifier 的高度以应用于 Spacer。尝试将高度设置为 16.dp。通常,dp 尺寸会以 4.dp 的增量进行更改。

MainActivity.kt

Spacer(modifier = Modifier.height(16.dp))
  1. Preview 面板中,点击 Build & Refresh

您应该看到类似于此图片的内容

73eea4c166f7e9d2.png

7. 构建掷骰子逻辑

现在所有必要的可组合项都已到位,您将修改应用,以便点按按钮即可掷骰子。

使按钮可交互

  1. DiceWithButtonAndImage() 函数中,在 Column() 函数之前,创建一个 result 变量并将其设置为等于值 1
  2. 看看 Button 可组合项。您会注意到它正在被传递一个 onClick 参数,该参数被设置为一对花括号,括号内有注释 /*TODO*/。在这种情况下,花括号代表了所谓的 lambda,花括号内的区域是 lambda 主体。当函数作为参数传递时,它也可以被称为“ 回调”。

MainActivity.kt

Button(onClick = { /*TODO*/ })

Lambda 是函数字面量,它与其他函数一样,但不是用 fun 关键字单独声明,而是以内联方式编写并作为表达式传递。Button 可组合项期望将函数作为 onClick 参数传递。这是使用 lambda 的绝佳位置,您将在本节中编写 lambda 主体。

  1. Button() 函数中,从 onClick 参数的 lambda 主体的值中删除 /*TODO*/ 注释。
  2. 掷骰子是随机的。为了在代码中体现这一点,您需要使用正确的语法来生成随机数。在 Kotlin 中,您可以在数字范围内使用 random() 方法。在 onClick lambda 主体中,将 result 变量设置为 1 到 6 之间的范围,然后在该范围上调用 random() 方法。请记住,在 Kotlin 中,范围由范围中第一个数字和最后一个数字之间的两个点指定。

MainActivity.kt

fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
    var result = 1
    Column(
        modifier = modifier,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Image(
            painter = painterResource(R.drawable.dice_1),
            contentDescription = "1"
        )
        Spacer(modifier = Modifier.height(16.dp))
        Button(onClick = { result = (1..6).random() }) {
            Text(stringResource(R.string.roll))
        }
    }
}

现在按钮可以点击了,但点击按钮还不会引起任何视觉变化,因为您还需要构建该功能。

为骰子应用添加条件判断

在上一节中,您创建了一个 result 变量并将其硬编码为 1。最终,当点击掷骰按钮时,result 变量的值会被重置,并且它应该决定显示哪张图片。

可组合项默认是无状态的,这意味着它们不保存值,并且可以随时被系统重新组合,从而导致值被重置。但是,Compose 提供了一种方便的方法来避免这种情况。可组合函数可以使用 remember 可组合项将对象存储在内存中。

  1. result 变量设为一个 remember 可组合项。

remember 可组合项需要传递一个函数。

  1. remember 可组合项主体中,传入一个 mutableStateOf() 函数,然后向该函数传递参数 1

mutableStateOf() 函数返回一个 observable(可观察对象)。您将在稍后了解更多关于 observable 的信息,但现在这基本上意味着当 result 变量的值发生变化时,会触发重组,反映结果的值,并且 UI 会刷新。

MainActivity.kt

var result by remember { mutableStateOf(1) }

现在,当按钮被点击时,result 变量将更新为随机数值。

现在可以使用 result 变量来确定显示哪张图片。

  1. result 变量的实例化下方,创建一个不可变的 imageResource 变量,并将其设置为接受 result 变量的 when 表达式,然后将每个可能的结果设置为其对应的 drawable。

MainActivity.kt

val imageResource = when (result) {
    1 -> R.drawable.dice_1
    2 -> R.drawable.dice_2
    3 -> R.drawable.dice_3
    4 -> R.drawable.dice_4
    5 -> R.drawable.dice_5
    else -> R.drawable.dice_6
}
  1. 将传递给 Image 可组合项的 painterResource 参数的 ID 从 R.drawable.dice_1 drawable 更改为 imageResource 变量。
  2. Image 可组合项的 contentDescription 参数更改为反映 result 变量的值,方法是将 result 变量使用 toString() 转换为字符串,并将其作为 contentDescription 传递。

MainActivity.kt

Image(
   painter = painterResource(imageResource),
   contentDescription = result.toString()
)
  1. 运行您的应用。

您的骰子应用现在应该完全正常工作了!

3e9a9f44c6c84634.png

8. 获取解决方案代码

要下载已完成 Codelab 的代码,您可以使用此 git 命令

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-dice-roller.git

或者,您可以将仓库下载为 zip 文件,解压并在 Android Studio 中打开。

如果您想查看解决方案代码,请在 GitHub 上查看

  1. 导航到提供的项目 GitHub 仓库页面。
  2. 验证分支名称是否与 Codelab 中指定的分支名称一致。例如,在以下截图中,分支名称是 main

1e4c0d2c081a8fd2.png

  1. 在项目的 GitHub 页面上,点击 Code 按钮,这将弹出一个窗口。

1debcf330fd04c7b.png

  1. 在弹出的窗口中,点击 Download ZIP 按钮将项目保存到您的计算机。等待下载完成。
  2. 在您的计算机上找到该文件(很可能在下载文件夹中)。
  3. 双击 ZIP 文件将其解压。这将创建一个包含项目文件的新文件夹。

在 Android Studio 中打开项目

  1. 启动 Android Studio。
  2. Welcome to Android Studio 窗口中,点击 Open

d8e9dbdeafe9038a.png

注意:如果 Android Studio 已经打开,则选择 File > Open 菜单选项。

8d1fda7396afe8e5.png

  1. 在文件浏览器中,导航到解压后的项目文件夹所在的位置(很可能在您的下载文件夹中)。
  2. 双击该项目文件夹。
  3. 等待 Android Studio 打开项目。
  4. 点击 Run 按钮 8de56cba7583251f.png 构建并运行应用。确保它按预期构建。

9. 结论

您使用 Compose 为 Android 创建了一个交互式骰子应用!

总结

  • 定义可组合函数。
  • 使用 Composition 创建布局。
  • 使用 Button 可组合项创建按钮。
  • 导入 drawable 资源。
  • 使用 Image 可组合项显示图片。
  • 使用可组合项创建交互式 UI。
  • 使用 remember 可组合项将对象存储到 Composition 的内存中。
  • 使用 mutableStateOf() 函数使 UI 可观察并刷新 UI。

了解详情