创建交互式 Dice Roller 应用

1. 准备工作

在此 Codelab 中,您将创建一个交互式 Dice Roller 应用,让用户可以通过点按 Button 可组合函数的相应界面元素来掷骰子。掷骰子的结果将通过 Image 可组合函数显示在屏幕上。

您可以将 Jetpack Compose 与 Kotlin 搭配使用来构建应用布局,然后编写业务逻辑来处理点按 Button 可组合函数时发生的操作。

前提条件

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

学习内容

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

构建内容

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

所需条件

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

完成此 Codelab 后,应用将如下所示:

524ad07a9b61f729.png

2. 建立基准

创建项目

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

此时,系统会在对话框中显示一个列表,其中包含 Android 项目模板。每个模板都会显示一张基础模板的图片,后跟模板的名称。

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

f59332f7db364338.png

3. 创建布局基础架构

预览项目

如需预览项目,请执行以下操作:

  • 点击 SplitDesign 窗格中的 Build & Refresh

c367df1b2c82b224.png

现在,您应该会在 Design 窗格中看到预览。如果它看起来很小,请不要担心,因为在您修改布局后,它会随之更改。

c968f0707e081b8f.png

重构示例代码

您需要更改一些生成的代码,使其更贴近 Dice Roller 应用的主题。

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

如需重构示例代码,请执行以下操作:

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

该可组合函数代表布局的界面组件,还包含按钮点击和图片显示逻辑。

  1. 移除 Greeting(name: String) 函数。
  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 界面元素行为的元素的集合。您将使用该对象来设置 Dice Roller 应用组件的界面组件样式。

如需添加修饰符,请执行以下操作:

  1. DiceWithButtonAndImage() 函数修改为接受 Modifier 类型的 modifier 实参,并为其分配默认值 Modifier

MainActivity.kt

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

前面的代码段可能会让您感到困惑,下面我们来详细介绍一下。该函数允许传入 modifier 形参。modifier 形参的默认值为 Modifier 对象,因此是方法签名的 = Modifier 部分。借助某个形参的默认值,未来调用该方法的任何调用方都可以决定是否为该形参传递值。如果它们传递自己的 Modifier 对象,则可以自定义界面的行为和装饰。如果它们选择不传递 Modifier 对象,系统会假定使用默认值,即普通的 Modifier 对象。您可以将这种做法应用于任何形参。如需详细了解默认实参,请参阅默认实参

  1. 现在,DiceWithButtonAndImage() 可组合函数具有修饰符参数,请在调用该可组合函数时传递修饰符。由于 DiceWithButtonAndImage() 函数的方法签名已更改,因此在调用该方法时,应传入包含所需装饰的 Modifier 对象。Modifier 类负责对 DiceRollerApp() 函数中的可组合对象进行装饰或向其添加行为。在本例中,需要向传递到 DiceWithButtonAndImage() 函数的 Modifier 对象添加一些重要的装饰。

您可能会好奇,既然有默认值,为什么还要传递 Modifier 实参。原因在于可组合函数可能会进行重组,这实质上意味着 @Composable 方法中的代码块会再次执行。如果在代码块中创建了 Modifier 对象,系统可能会重新创建该对象,并且这种方式效率不高。此 Codelab 稍后将介绍重组。

MainActivity.kt

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

该方法会指定组件应填满可用空间。在此 Codelab 的前面部分,您看到了 Dice Roller 应用的最终界面的屏幕截图。该截图中有一个值得注意的特征,那就是骰子和按钮在屏幕上居中。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() 函数是一种可组合函数布局,它会按垂直序列放置其子级。在预期的应用设计中,您可以看到骰子图片垂直显示在掷骰子按钮的上方:

524ad07a9b61f729.png

如需创建垂直布局,请执行以下操作:

  1. DiceWithButtonAndImage() 函数中,添加一个 Column() 函数。
  2. modifier 实参从 DiceWithImageAndButton() 方法签名传递给 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. 添加图片

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

下载骰子图片

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

在计算机上找到相应文件。该文件可能位于 Downloads 文件夹中。

  1. 解压缩该 ZIP 文件以创建一个新的 dice_images 文件夹,其中包含 6 个骰子图片文件,其骰子点数分别为 1 到 6。

向应用中添加骰子图片

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

Resource Manager 中的“add resource”下拉菜单会显示用于导入可绘制对象的选项。

  1. 找到并选中上述 6 个骰子图片文件夹,然后继续上传它们。

上传的图片将如下所示。

系统会显示“Import Drawables”窗口,以及可供导入的资源。

  1. 点击 Next

系统会显示“Import confirmation”对话框,以及资源文件会进入文件结构中的什么位置。

此时,系统会显示 Import Drawables 对话框,以及资源文件会进入文件结构中的什么位置。

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

这些图片应显示在 Resource Manager 窗格中。

Resource Manager 面板显示此项目中包含的资源。

非常棒!在下一个任务中,您将在应用中使用这些图片。

添加 Image 可组合函数

骰子图片应显示在 Roll 按钮上方。Compose 本身会依序放置界面组件。也就是说,哪个可组合函数声明在先,就会先行显示。这意味着,首先声明的可组合函数会显示在之后声明的可组合函数的上面或前面。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. Image() 函数传递一个 painter 实参,然后为其分配接受可绘制资源 ID 实参的 painterResource 值。目前,请传递以下资源 ID:R.drawable.dice_1 实参。

MainActivity.kt

Image(
    painter = painterResource(R.drawable.dice_1)
)
  1. 每当您在应用中创建图片时,都应该提供所谓的“内容说明”。内容说明是 Android 开发的重要组成部分。内容说明能够向各界面组件添加说明,以提高无障碍性。如需详细了解内容说明,请参阅描述每个界面元素。您可以将内容说明作为形参传递到图片。

MainActivity.kt

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

现在,所有必要界面组件均已存在。但是,ButtonImage 略显拥挤。

92a1023933ee638a.png

  1. 如需解决此问题,请在 ButtonImage 可组合函数之间添加一个 Spacer 可组合函数。Spacer 接受 Modifier 作为形参。在本例中,ImageButton 上方,因此它们之间需要一定的垂直空间。因此,可以设置 Modifier 的高度以应用于 Spacer。尝试将高度设为 16.dp。通常,dp 尺寸以 4.dp 为增量进行更改。

MainActivity.kt

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

您应该会看到如下图所示的内容:

d893fc8fccb05813.png

7. 构建掷骰子逻辑

现在所有必要的可组合函数均已存在,接下来您可以修改应用,以便用户能点按按钮来掷出骰子。

将 Button 设为可交互

  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(imageResource), contentDescription = result.toString())
        Button(onClick = { result = (1..6).random() }) {
            Text(stringResource(R.string.roll))
        }
    }
}

现在,该按钮可点按了,但点按该按钮并不会产生任何可见的变化,因为您仍需构建该功能。

为 Dice Roller 应用添加条件

在上一部分中,您创建了一个 result 变量并将其硬编码为 1 值。最终,result 变量的值会在点按 Roll 按钮时重置,并且该变量应确定要显示的图片。

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

  1. result 变量设为 remember 可组合函数。

remember 可组合函数需要传递函数。

  1. remember 可组合函数正文中,传入 mutableStateOf() 函数,然后向该函数传递 1 实参。

mutableStateOf() 函数会返回一个可观察对象。稍后,您会详细了解可观察对象,但目前这基本上意味着,当 result 变量的值变化时,系统会触发重组、反映结果值并刷新界面。

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. 通过将 result 变量转换为包含 toString() 的字符串并将其作为 contentDescription 传递,更改 Image 可组合函数的 contentDescription 形参以反映 result 变量的值。

MainActivity.kt

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

现在,Dice Roller 应用应该完全可以正常运行了!

524ad07a9b61f729.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

2301510b78db9764.png

  1. 在项目的 GitHub 页面上,点击 Code 按钮,以打开一个弹出式窗口。

5844a1bc8ad88ce1.png

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

在 Android Studio 中打开项目

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

4711318ba1db18a2.png

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

e400aad673cc7e28.png

  1. 在文件浏览器中,转到解压缩的项目文件夹所在的位置(很可能在 Downloads 文件夹中)。
  2. 双击该项目文件夹。
  3. 等待 Android Studio 打开项目。
  4. 点击 Run 按钮 1b472ca0dcd0297b.png 以构建并运行应用。请确保该应用按预期构建。

9. 总结

您已使用 Compose 创建了一款交互式 Dice Roller Android 应用!

总结

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

了解更多内容