创建交互式骰子摇骰器应用

1. 开始之前

在本代码实验室中,您将创建一个交互式 **骰子摇骰器** 应用,用户可以通过点击 Button 可组合项来摇骰子。摇骰结果将通过屏幕上的 Image 可组合项显示。

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

先决条件

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

您将学到什么

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

您将构建什么

  • 一个名为骰子摇骰器的交互式 Android 应用,允许用户摇骰并显示摇骰结果。

您需要什么

  • 安装了 Android Studio 的电脑。

以下是完成本代码实验室后应用的外观

3e9a9f44c6c84634.png

2. 建立基线

创建项目

  1. 在 Android Studio 中,点击 **文件 > 新建 > 新建项目**。
  2. 在 **新建项目** 对话框中,选择 **空活动**,然后点击 **下一步**。

39373040e14f9c59.png

  1. 在 **名称** 字段中,输入 Dice Roller
  2. 在 **最低 SDK** 字段中,从菜单中选择 24(牛轧糖)的最低 API 级别,然后点击 **完成**。

8fd6db761068ca04.png

3. 创建布局基础结构

预览项目

要预览项目

  • 在 **拆分** 或 **设计** 窗格中点击 **构建并刷新**。

9f1e18365da2f79c.png

现在您应该在 **设计** 窗格中看到预览。如果它看起来很小,不用担心,因为在您修改布局时它会发生变化。

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 参数。原因是可组合项可能会经历 *重新组合*,这基本上意味着 @Composable 方法中的代码块会再次执行。如果在代码块中创建了 Modifier 对象,则它可能会被重新创建,这效率不高。重新组合将在本代码实验室的后面部分介绍。

MainActivity.kt

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

此方法指定组件应填充可用空间。在本代码实验室的前面,您看到了骰子摇骰器应用最终 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. modifier 参数从 DiceWithButtonAndImage() 方法签名传递到 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 文件中,在函数的 lambda 体中的 Button() 中添加 Text() 函数。
  2. roll 字符串的字符串资源 ID 传递到 stringResource() 函数,并将结果传递到 Text 可组合项。

MainActivity.kt

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

6. 添加图像

应用的另一个重要组成部分是骰子图像,当用户点击**Roll**按钮时,它会显示结果。您可以使用`Image` 可组合项添加图像,但它需要一个图像资源,因此您首先需要下载为此应用提供的某些图像。

下载骰子图像

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

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

  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` 可组合项

骰子图像应该出现在**Roll**按钮的上方。Compose 本身会按顺序放置 UI 组件。换句话说,首先声明的可组合项会先显示。这可能意味着第一个声明显示在第二个声明的可组合项的上方或之前。`Column` 可组合项内的可组合项将彼此上下显示在设备上。在此应用中,您使用`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. 将`painter`参数传递给`Image()`函数,然后为其分配一个`painterResource`值,该值接受一个可绘制资源 ID 参数。目前,传递以下资源 ID:`R.drawable.dice_1`参数。

MainActivity.kt

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

MainActivity.kt

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

现在所有必要的 UI 组件都已存在。但是`Button`和`Image`彼此之间有点拥挤。

54b27140071ac2fa.png

  1. 要解决此问题,请在`Image`和`Button`可组合项之间添加一个Spacer可组合项。`Spacer`将`Modifier`作为参数。在这种情况下,`Image`位于`Button`的上方,因此它们之间需要有垂直空间。因此,可以设置`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`变量的值在点击**Roll**按钮时会重置,它应该决定显示哪个图像。

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

  1. 使`result`变量成为`remember`可组合项。

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

  1. 在`remember`可组合项体中,传入`mutableStateOf()`函数,然后将`1`参数传递给该函数。

`mutableStateOf()`函数返回一个可观察对象。稍后您将了解有关可观察对象的更多信息,但目前这基本上意味着当`result`变量的值更改时,将触发重新组合,结果值将被反映,并且 UI 将刷新。

MainActivity.kt

var result by remember { mutableStateOf(1) }

现在,当点击按钮时,`result`变量将使用随机数更新。

`result`变量现在可以用来确定要显示哪个图像。

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

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`可绘制对象更改为`imageResource`变量。
  2. 将`Image`可组合项的`contentDescription`参数更改为反映`result`变量的值,方法是使用`toString()`将`result`变量转换为字符串并将其作为`contentDescription`传递。

MainActivity.kt

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

您的**Dice Roller**应用现在应该可以完全正常工作了!

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. 在您的计算机上找到该文件(可能在**Downloads**文件夹中)。
  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. 点击**运行**按钮 8de56cba7583251f.png以构建并运行应用程序。确保其按预期构建。

9. 结论

您使用 Compose 创建了一个用于 Android 的交互式**掷骰子**应用程序!

摘要

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

了解更多