项目:Lemonade 应用

1. 准备工作

此 Codelab 将介绍如何自行构建一个名为 Lemonade 的新应用,并且会逐步引导您在 Android Studio 完成该项目,包括设置和测试。

此 Codelab 与本课程中的其他 Codelab 不同。与之前 Codelab 的不同点在于,此 Codelab 的目的不是提供关于如何构建应用的分步教程,而是设置一个将由您独立完成的项目,从而为您提供有关如何完成应用及自行检查您的工作成果的说明。

我们在您要下载的应用中提供了测试套件,而不是解决方案代码。您将在 Android Studio 中运行这些测试(此 Codelab 稍后会对此进行演示),并查看您的代码是否通过测试。您可能需要进行多次尝试;即使是专业开发者,也很少有人能在第一次尝试时就通过所有测试!只有在您的代码通过所有测试后,您才可以将此项目视为已完成。

我们理解,您可能只是想对照解决方案代码来检查自己的代码。我们故意不提供解决方案代码,因为我们希望您像一名专业开发者一样进行练习。这可能需要您运用一些平时练得不多的其他技能,例如:

  • 使用 Google 搜索引擎搜索应用中出现的您不理解的术语、错误消息和代码段;
  • 测试代码,查看错误,然后更改代码并再次测试;
  • 复习前面学过的“Android 基础知识”第 1 单元的内容;
  • 将您知道可以正常运行的代码(即项目中给出的代码,或您之前在第 1 单元中学过的其他应用的解决方案代码)与您编写的代码进行比较。

一开始您可能会感到有些望而却步,不过如果您能够完成第 1 单元,那就说明您已经为完成此项目做好了准备。对此,我们有十足的信心。慢慢来,不要放弃。您是可以完成此项目的。

前提条件

  • 此项目的适用对象为已经完成《使用 Kotlin 进行 Android 开发的基础知识》课程第 1 单元的用户。

构建内容

  • 您将使用在第 1 单元中所学的技能,构建一个简单的 Lemonade 应用。

所需条件

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

2. 应用概览

欢迎参与“项目:Lemonade 应用”!

欢迎加入我们的团队,协助我们把制作数字柠檬汁的愿景变为现实。我们的目标是构建一款简单的互动式移动应用,让您使用该应用榨一杯柠檬汁。当然,这里所说的榨汁只是打个比方,您也可以把构建该应用作为一种打发时间的消遣方式!

完成后的 Lemonade 应用由单个画面构成。用户首次启动该应用时,系统会提示他们通过点按柠檬树图片来选择一个柠檬。

1ce5b75b513d63c9.png

用户点按柠檬树后,会看到一个柠檬,点按该柠檬便可以“榨汁”。压榨一定次数(所需的确切压榨次数是随机生成的)后,会转到下一个画面。

fb63b41d58a83af7.png

用户点按柠檬进行榨汁达到正确的次数后,系统会显示一杯柠檬汁的图片,让用户“饮用”。

f8882c1688a0e3e7.png

用户点按杯子“饮用”柠檬汁后,杯子会变空。用户可以再次点按杯子以返回第一个画面,并从树上选择其他柠檬。

951918f0c2d0464.png

这款应用的构建理念在于简单易用,全程只用到一个 activity。应用的不同状态(无论是用户选择柠檬、将柠檬榨汁、饮用柠檬汁还是最后杯子变空)均使用“状态机”来表示。这听起来像是一个花哨的术语,但其实想表达的意思是:应用的状态(即向用户显示的是哪些文字和图片)由一个包含应用状态(selectsqueeze 等)的变量来确定。系统会更新应用的状态以及所需的任何其他变量,并在完成所有更新后单独对界面进行配置(设置图片和文字)。

我们已为您定义应用状态的所有变量。您的任务是构建应用的布局并实现相应逻辑,以便界面按预期在各状态之间转换。

测试您的代码

对于 Lemonade 应用(以及今后的项目),我们会为您提供一些自动化测试,供您验证您的代码是否一切正常。

到底什么是自动化测试?在软件开发中,您可以将“测试”视为用于验证其他代码能否正常发挥作用的代码。具体方法是,检查输出(例如屏幕上界面元素的内容),看看对于输入而言是否有意义,这称为“测试用例”。Lemonade 应用的起始项目中包含几项测试,您可以运行这些测试,以确保正确实现了相应逻辑。稍后我们将详细介绍这些测试。现在,请下载起始代码并开始构建 Lemonade 应用。

3. 开始

下载项目代码

请注意,文件夹名称为 android-basics-kotlin-lemonade-app。在 Android Studio 中打开项目时,请选择此文件夹。

如需获取此 Codelab 的代码并在 Android Studio 中打开它,请执行以下操作。

获取代码

  1. 点击提供的网址。此时,项目的 GitHub 页面会在浏览器中打开。
  2. 检查并确认分支名称与 Codelab 中指定的分支名称匹配。例如,在以下屏幕截图中,分支名称为 main

8cf29fa81a862adb.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. 在文件浏览器中,前往解压缩的项目文件夹所在的位置(很可能在 Downloads 文件夹中)。
  2. 双击该项目文件夹。
  3. 等待 Android Studio 打开项目。
  4. 点击 Run 按钮 8de56cba7583251f.png 以构建并运行应用。请确保该应用按预期构建。

请花点时间熟悉一下入门级项目。要特别注意 MainActivity.kt 文件。

4181c13884715771.png

MainActivity.kt 中,您会看到几个用于表示应用当前状态的变量。您在后续步骤中会用到这些变量,以让应用具有互动性。虽然此处的代码量看起来非常大,但您无需修改任何未使用 TODO 进行标记的代码。如需具体说明,请参阅后续页面。

此外,您会发现项目中还包含另一个软件包,即 com.example.lemonade (androidTest)

a0c593c77b323c15.png

该软件包中包含一些自动化测试。您将使用这些测试来验证在 MainActivity.kt 中实现的功能是否可以正常运行。我们稍后会再次对其进行详细介绍。现在,您可以开始构建应用了,我们从界面开始。

4. 构建界面

Lemonade 应用仅需要基本的布局;您只需使用两个视图即可实现它的所有功能。

  1. 一个 TextView,用于为用户提供说明。
  2. 一个 ImageView,用于根据应用的当前状态(例如,将柠檬榨汁)显示图形。

您将在 activity_main.xml 中构建此布局。

554c5e1ae9ec2e42.png

您的目标是,运用您掌握的布局编辑器知识,构建一个如下所示的布局,其中这两个视图都处于屏幕中心位置,并且 TextView 位于 ImageView 上方。

54581304e678827c.png

5. 让应用具有互动性

构建好布局后,打开 MainActivity.kt。您将在此处实现应用的所有逻辑。您会看到已有相当多的代码。此外,还有许多标有 // TODO: 的注释(示例如下)。这些是需要您完成的任务

b6c5858a42dec80.png

您需要完成三项基本任务,才能使 Lemonade 应用正常运行。

  1. 配置 lemonImage ImageView,以响应用户输入。
  2. 实现 clickLemonImage(),以更新应用的状态。
  3. 实现 setViewElements(),以根据应用的当前状态更新界面。

下面我们依次看一下每项任务。

第 1 步:配置 ImageView

点按图片视图应将应用从一种状态转换为另一种状态。在 onCreate() 末尾,请注意有两个监听器需要设置。

  1. setOnClickListener() 应更新应用的状态。可通过 clickLemonImage() 方法做到这一点。
  2. setOnLongClickListener() 应对用户长按图片的事件做出响应(例如,用户点按图片后没有立即松开手指)。对于长按事件,屏幕底部会显示一个名为“信息条”的 widget,告知用户他们已压榨柠檬的次数。这是通过 showSnackbar() 方法实现的。

b07b78c6b607e94d.png

在下一步中,您将实现用于更改应用状态的逻辑。

第 2 步:实现 clickLemonImage()

完成上一步后,现在每当用户点按图片时,系统都会调用 clickLemonImage() 方法。该方法负责将应用从当前状态转换到下一个状态,并根据需要更新任何变量。有四种可能的状态:SELECTSQUEEZEDRINKRESTART;当前状态由 lemonadeState 变量表示。对于每种状态,此方法需要执行不同的操作。

  1. SELECT:转换为 SQUEEZE 状态,通过调用 pick() 方法并将 squeezeCount(用户已压榨柠檬的次数)设置为 0 来设置 lemonSize(所需的压榨次数)。
  2. SQUEEZE:以 1 为增量递增 squeezeCount,并以 1 为减量递减 lemonSize。请记住,柠檬需要压榨不定次,应用才能转换其状态。仅当新的 lemonSize 等于 0 时,应用才会转换为 DRINK 状态。否则,应用应保持 SQUEEZE 状态。
  3. DRINK:转换为 RESTART 状态,并将 lemonSize 设置为 -1。
  4. RESTART:转换回 SELECT 状态。

处理完所有更新以及状态之间的转换后,请务必调用 setViewElements() 以基于新的状态更新界面。

第 3 步:实现 setViewElements()

setViewElements() 方法负责根据应用的状态更新界面。应使用下方显示的值更新文字和图片,使其与 lemonadeState 相符。

SELECT

  • 文字:点击以选择柠檬!
  • 图片:R.drawable.lemon_tree

SQUEEZE

  • 文字:点击以将柠檬榨汁!
  • 图片:R.drawable.lemon_squeeze

DRINK

  • 文字:点击以饮用柠檬汁!
  • 图片:R.drawable.lemon_drink

RESTART

  • 文字:点击以重新开始!
  • 图片:R.drawable.lemon_restart

如何使用字符串资源

在 Android 中,几乎所有内容都是资源。定义您之后可以在应用中访问的资源是 Android 开发的重要环节。

资源可用于定义颜色、图片、布局、菜单和字符串值等任何内容。使用资源的好处是,没有任何内容采用硬编码。所有内容都在资源文件中定义,并且您可以在应用的代码中引用定义好的内容。字符串资源是最简单(也最常见)的资源,可用于支持灵活的本地化文字。

字符串或静态文本可以存储在名为 strings.xml 的单独文件中,该文件位于 res 文件夹的 values 子文件夹中。

b65ed762eea87f2f.png

对于要在应用中显示的每段文本(例如按钮的标签或 TextView 内的文本),您应先在 res/values/strings.xml 文件中定义相应文本。每个条目是一个键(表示文本的 ID)和一个值(文本本身)。例如,如果您希望按钮显示“提交”,请将以下字符串资源添加到 res/values/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello">Hello!</string>
    <string name="submit_label">Submit</string>
</resources>

如需直接在代码中访问该资源,只需使用 getResources.getString()getString() 方法基于资源 ID 访问值 R.string.submit_label

val submitText = getResources().getString(R.string.submit_label)

如要将字符串资源中的文本直接设置为 TextView,可以对 TextView 对象调用 setText(),并传入资源 ID。

val infoTextView: TextView = findViewById(R.id.info_textview)

infoTextView.setText(R.string.info_text)

字符串资源还可以包含用于设置文本格式的特殊字符。例如,您可能有一个字符串资源,允许您将另一段文本插入字符串中。

<string name="ingredient_tablespoon">%1$d tbsp of %2$s</string>

在代码中,您可以通过传入参数来访问字符串资源并设置其格式。

getResources().getString(R.string.ingredient_tablespoon, 2, "cocoa powder")

声明字符串资源时,每个参数都会按照其显示顺序进行编号(12 等),并用字母来标识类型(十进制数为 d,字符串为 s 等)。如果参数的类型正确,则可以传递给对 getString() 的调用。

2 tbsp of cocoa powder

如需了解详情,请参阅有关字符串资源的文档

6. 运行您的应用

构建应用的界面并实现主 activity 后,您就可以实际查看工作成果了。使用 Run > Run ‘app' 菜单运行应用,模拟器将启动。

1bb3bc95726dde79.png

应用现在可全面互动,您应该能够通过点按图片视图转换状态。

42feefe9ebcf879c.png

当屏幕上显示柠檬时,您还可以尝试长按(按住)ImageView,以查看底部显示的信息条,了解已压榨柠檬的总次数。请花些时间多次运行应用并经历所有状态。然后,花一点时间祝贺自己的辛苦工作取得了不错的结果!

7. 测试说明

测试应用

您已完成 Lemonade 应用的实现,但在专业的软件开发中,仅编写代码一般不会是最后一步。除了应用代码,专业的优质应用还包括测试代码,用于验证应用代码能否按预期运行,以及确定对应用代码所做的更改不会引入任何新的 bug,此过程称为“自动化测试”。虽然介绍如何进行自动化测试不在此项目的涵盖范围内,但 Lemonade 应用捆绑了一些测试,可帮助您验证是否正确实现了项目。您可以将这作为一种形式的自我评估,看看您是否已满足所有项目要求,以及是否需要对应用进行任何更改。

到底什么是“测试”?测试只是您的 Android Studio 项目中包含的一段代码,它会运行应用的一部分代码,可能“通过”,也可能“失败”,具体取决于应用代码的行为是否符合预期。

在哪里可以找到并运行应用的测试?您可以在测试“目标”中找到 Lemonade 应用的测试。目标仅仅是一个软件开发术语,表示一组绑定在一起的类。例如,Lemonade 应用位于名为“app”的目标中,而测试位于名为“LemonadeTests”的目标中。虽然 LemonadeTests 目标可以访问应用目标中的代码,但它们是完全独立的,应用的代码不包含任何测试代码。

55f884303707e1c3.png

在“Android”视图中查看文件时,测试目标将采用与应用相同的软件包名称,但带有 (androidTest)。

提到测试代码,有几个关键术语需要了解。

  • 测试套件 - 包含您的所有“测试用例”的目标。
  • 测试用例 - 包含用于相关功能的各个测试的类(Lemonade 应用只有一个测试用例,但较大的应用通常具有更多测试用例)。
  • 测试 - 用于测试特定内容的函数。

一个测试用例可以有多个测试,项目的测试套件可以有多个测试用例。

运行测试

如需运行测试,您可以执行以下任一操作。

对于单个测试用例,打开测试用例类,并点击类声明左侧的绿色箭头。然后,从菜单中选择“Run”选项。这样会运行测试用例中的所有测试。

6c7b133bcc38ce87.png

很多时候您可能只需要运行单个测试,例如,其他测试都通过了,只有一个测试失败。您可以像运行整个测试用例一样运行单个测试。点击绿色箭头并选择 Run 选项。

59690e06230bf1e4.png

如果您有多个测试用例,也可以运行整个测试套件。就像运行应用一样,您可以在 Run 菜单中找到此选项。

ed1e07d2488ac446.png

请注意,Android Studio 默认运行您运行的最后一个目标(应用目标、测试目标等),因此如果菜单仍然显示 Run > Run ‘app',您可以通过选择 Run > Run 运行测试目标。

13bd962d134241aa.png

然后从弹出式菜单中选择测试目标。

903c7ed01fa7cebf.png

运行测试的结果显示在 Run 标签页中。在左侧的窗格中,您会看到失败测试的列表(如有)。如果测试失败,其函数名称旁边会显示红色感叹号。如果测试通过,则测试名称旁边会显示绿色对勾标记。

37c73c4a656622af.png

如果测试失败,文本输出会提供相关信息,帮助您修复导致测试失败的问题。

92f3c8219c03651d.png

例如,在上述错误消息中,测试检查 TextView 是否有使用特定字符串资源。但是,测试失败了。“Expected”和“Got”后面的文本不一致,也就是说,测试预期的值与从正在运行的应用中获取的值不一致。在此示例中,TextView 中使用的字符串不是测试所预期的实际上的 squeeze_count

8. 可选:分享您的应用!

在享用多杯柠檬汁后,截取您喜欢的屏幕的截图,并在 Twitter 上分享,以展示您学到的知识。分享时请添加 @AndroidDev 标签,以及 #AndroidBasics 标签。