创建交互式掷骰子应用

1. 开始之前

在这个 codelab 中,您将创建一个交互式的**掷骰子**应用,用户可以通过点击Button 可组合元素来掷骰子。掷骰子的结果将通过屏幕上的Image 可组合元素显示。

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

先决条件

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

您将学到什么

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

您将构建什么

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

您需要什么

  • 安装了 Android Studio 的计算机。

以下是完成此 codelab 后应用的外观

3e9a9f44c6c84634.png

2. 建立基线

创建项目

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

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 对象,则可能会重新创建它,这效率不高。重新组合将在本 codelab 的后面部分介绍。

MainActivity.kt

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

此方法指定组件应填充可用空间。在本 codelab 的前面,您看到了掷骰子应用最终 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 文件中,将Text() 函数添加到函数的 lambda 体中的Button() 中。
  2. roll 字符串的字符串资源 ID 传递给stringResource() 函数,并将结果传递给Text 可组合元素。

MainActivity.kt

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

6. 添加图像

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

下载骰子图像

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

在您的电脑上找到该文件。它可能位于您的**下载**文件夹中。

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

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

  1. 在 Android Studio 中,点击**查看 > 工具窗口 > 资源管理器**。
  2. 点击**+ > 导入可绘制对象**以打开文件浏览器。

12f17d0b37dd97d2.png

  1. 找到并选择六个骰子图像文件夹,然后上传它们。

上传的图像将如下所示。

4f66c8187a2c58e2.png

  1. 点击**下一步**。

688772df9c792264.png

将出现**导入可绘制对象**对话框,并显示资源文件在文件结构中的位置。

  1. 点击**导入**以确认您要导入这六个图像。

这些图像应该会出现在**资源管理器**窗格中。

c2f08e5311f9a111.png

做得不错!在下一个任务中,您将在应用中使用这些图像。

添加`Image` 可组合项

骰子图像应该出现在**掷骰子**按钮上方。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. 在**预览**窗格中,点击**构建并刷新**。

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

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

MainActivity.kt

var result by remember { mutableStateOf(1) }

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

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

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

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. 运行您的应用。

您的**掷骰子**应用现在应该可以完全工作了!

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 页面上,点击**代码**按钮,这将调出一个弹出窗口。

1debcf330fd04c7b.png

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

在 Android Studio 中打开项目

  1. 启动 Android Studio。
  2. 在**欢迎使用 Android Studio**窗口中,点击**打开**。

d8e9dbdeafe9038a.png

注意:如果 Android Studio 已经打开,请选择文件 > 打开菜单选项。

8d1fda7396afe8e5.png

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

9. 结论

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

摘要

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

了解更多