开发体验

第8章 总统问答

“总统问答”是一个关于美国前总统的趣味问答游戏。虽然问答的内容与总统有关,但你可以把它当作模板,将应用扩展为对任何话题的问答。

在前几章中,我们介绍了一些编程的基本概念。现在,你应该有能力应对更大的挑战了。在完成本章的学习后,你会发现,无论是编程技巧,还是抽象思维能力,都将产生一个质的飞跃。特别需要强调的是,本章将使用两个列表变量来存储数据——问题列表及答案列表,并使用索引值变量来跟踪用户正在回答的题目。学完本章,你所掌握的知识,将足以创建这种问答类应用,以及其他需要使用列表的应用。

本章假设你已经熟悉了App Inventor的基础知识:在设计视图中创建用户界面,在编程视图中定义事件处理程序,以便为组件添加行为。如果你对于上述知识还不甚熟悉,请在继续学习之前,复习前面几章的内容。

在本章的问答应用中,用户通过点击下一题按钮,按顺序回答问题。每回答一题,应用将告知用户他的答案是否正确。

学习要点

应用的外观如图8-1所示,它涵盖以下内容。

  • 定义列表变量:用来存储列表中的问题和答案。
  • 使用索引值遍历列表,用户每次点击下一题按钮时,显示下一个问题。
  • 使用条件语句(如果……则)控制行为:只有在特定条件下才能执行某些操作;在用户回答完最后一题时,使用“如果......则”块来处理程序。
  • 每一道题对应一张图片,切换题目的同时要切换图片,以使题目与图片保持一致。
图8-1 “总统问答”应用正在运行

准备开始

登录App Inventor网站,创建新项目“总统问答”,并设置屏幕的标题为“总统问答”,连接设备或模拟器来进行实时测试。

从appinventor.org网站下载应用中用到的图片,并在下一节中将这些图片上传到项目中。

设计组件

“总统问答”应用的界面很简单:显示问题并允许用户来回答。图8-2显示了应用在设计视图中的截图,按图来创建组件。

图8-2 设计视图中的“总统问答”

首先将下载的图片上传到项目中:点击素材区域的“上传文件”按钮,选择一个文件(如roosChurch.gif),点击打开、确定。其他三张图片也是如此操作。然后添加表8-1中列出的组件。

表8-1 “总统问答”应用中所需的组件

组件类型 所属类别 名称 作用
图片 用户界面 图片1 显示与问题相对应的图片
标签 用户界面 问题标签 显示等待回答的问题
水平布局 组件布局 水平布局1 放置输入答案的文本框及提交答案的按钮
文本输入框 用户界面 答案输入框 用户在其中输入答案
按钮 用户界面 回答按钮 供用户点击以提交答案
标签 用户界面 对错标签 显示“正确”或“错误”
按钮 用户界面 下一题按钮 点击进入下一道问题
  1. 设置图片1的图片属性为roosChurch.gif(最先出现的图片);宽度为“充满”,高度为200。
  2. 设问题标签的显示文本属性为“问题…...”(稍后在编程视图中设置为第一题)。
  3. 设答案输入框的提示属性为“请输入回答”,显示文本为空,放在水平布局1中。
  4. 设回答按钮的显示文本为“回答”,并放在水平布局1中。
  5. 设下一题按钮的显示文本为“下一步”。
  6. 设对错标签的显示文本为空。

为组件添加行为

应用将实现以下行为。

  • 在应用启动时,显示第一个问题以及相应的图片。
  • 点击下一题按钮时,显示第二题。再次点击,显示第三题,以此类推。
  • 当显示最后一题时,点击下一题按钮将转回到第一题。
  • 在用户回答问题之后,显示答案是否正确。

通过编程来逐一实现上述行为,边做边测试。

定义问题及答案列表

首先按照表8-2的提示,定义两个列表变量:问题列表用来保存问题,答案列表用来保存答案。图8-3中显示了需要在编程视图中创建的两个列表。

表8-2 用于保存问题和答案的列表变量

代码块 所在抽屉 作用
声明全局变量(问题列表) 变量 保存问题的列表
声明全局变量(答案列表) 变量 保存答案的列表
列表 列表 为问题列表创建列表项
文本(共三段) 文本 三个问题
列表 列表 为答案列表创建列表项
文本(共三段) 文本 三个答案
图8-3 定义问题及答案列表

定义索引值变量

在应用运行过程中,用户每次点击下一题按钮,用户界面中将显示当前要回答的问题。我们的程序需要跟踪当前问题,以便进行后续的操作。定义变量“当前问题索引值”,作为问题列表及答案列表的共同索引值。表8-3列出了所需的块,图8-4显示了变量的定义。

表8-3 创建索引值变量需要的块

代码块 所在抽屉 作用
声明全局变量(当前问题索引值) 变量 保存当前问题在问题列表中的索引值(位置)
数字:1 数学 设该索引值的初始值为1(第一道题)
图8-4 将索引值变量初始化为1

显示第一个问题

定义了这些变量,就可以为应用设定交互行为了。无论是开发哪种类型的应用,渐进式的开发方法都是非常重要的,而且最好每一步只定义一个行为。我们首先考虑与问题相关的行为,具体来说,就是当应用启动时,首先显示列表中的第一道题。稍后再回来处理与图片及答案相关的行为。

代码块的设定应该与列表中的具体问题无关。这样,如果需要更换问题或创建新的问答类应用,只需改变列表中的具体问题,而不必修改事件处理程序。

鉴于上述考虑,对于第一道题,不要直接使用“哪位总统在大萧条时期实施了‘新政’?”这样的题目内容,而是采用“问题列表的第一个插槽中的内容”这样抽象的形式(与具体问题无关)。这样,即使第一个插槽中的问题改变了,这些程序块仍然有效。

使用代码块“列表()中的第()项”,可以从列表中选取指定的列表项,这需要为代码块指定两个参数:列表及索引值(项在列表中的位置)。如果列表中有三个项,则索引值可以是1、2或3。

我们要定义的第一个行为是,当应用启动时,选择问题列表中的第一道题,并用问题标签显示出来。还记得“安卓,我的车在哪儿?”应用吧,如果想让某件事发生在应用启动时,可以将有关指令放在Screen1的初始化事件处理程序中。表8-4中列出了所需的块。

表8-4 应用启动后显示第一个问题需要的块

代码块 所在抽屉 作用
当Screen1初始化时 Screen1 当应用启动时,触发该事件(执行其事件处理程序)
设问题标签的显示文本为 问题标签 让问题标签显示第一道题
列表()中的第()项 列表 从问题列表中选择第一题
global 问题列表 变量 从中选择问题的列表
数字:1 数学 使用索引值1从问题列表中选择第一题

块的作用

应用启动时触发Screen1的初始化事件。如图8-5所示,问题列表中的第一项被选中,并被设为问题标签的显示文本。因此,应用启动时,用户会看到第一道题。

图8-5 选择并显示第一题

测试:连接测试设备或模拟器,进行实时测试。当应用启动后,是否在用户界面上看到问题列表中的第一道题:“哪位总统在大萧条时期实施了‘新政’?”

遍历所有问题

下面针对下一题按钮的行为进行编程。之前定义的“当前问题索引值”,用来记住用户正在回答的问题。当用户单击下一题按钮时,需要为当前问题索引值加1(即,从1变为2,或从2变为3,以此类推),并根据这个值来选择并显示新的问题。挑战一下你自己,看看是否可以自己搭建这些块。完成之后,与图8-6进行对照。

图8-6 转到下一道题

块的作用

第一行代码块让变量当前问题索引值递增。如果当前值为1则加到2,如果是2则加到3,以此类推。利用已经更新的当前问题索引值,从问题列表中选择新的问题并显示。首次单击下一题按钮时,当前问题索引值从1变为2,应用将选择并显示问题列表中的第二道题:“哪位总统在1979年实现中美建交?”第二次单击下一题按钮时,当前问题索引值从2变为3,应用将选择并显示问题列表中的第三道题:“哪位总统因水门事件而辞职?”

提示:花一分钟的时间来比较一下两个事件处理程序的差别:下一题按钮的点击事件与Screen1的初始化事件。在Screen1的初始化事件中,用具体数字1来选择列表项。在下一题按钮的点击事件中,用变量值来选择列表项,即并非选择第1、第2或第3项,而是选择“当前问题索引值”项,因此每次点击下一题按钮,都将选择不同的项。这是索引值最常见的用法——通过递增的索引值来选择并显示列表项。

测试:点击下一题按钮,看看应用运行是否正常。在手机上点击下一题按钮,是否显示第二题“哪位总统在1979年实现中美建交?”?应该是的。再次点击下一题按钮,应该出现第三题。但如果再次点击,就会看到错误提示:“Attempting to get item 4 of a list of length 3.”(试图从只有3个列表项的列表中选取第4项。)这就是程序的bug!知道原因吗?在继续阅读之前试试看自己来解决它。

我们来分析代码中存在的问题:每次点击下一题按钮,只是简单地将索引值递增,而没有考虑到问题的数量是有限的(3个)。因此,当索引值等于3时,用户点击下一题按钮,索引值增加到4,程序试图从仅有3个列表项的问题列表中选取第4项,于是导致程序的逻辑错误,出现了上述的错误提示信息。了解到这一点,我们解决问题的方法也就不言自明:检测当前显示的问题是否为最后一题。

当下一题按钮被点击时,程序需要询问一个问题,并根据问题的答案执行不同的操作 。既然已知问题列表中只包含三个问题,那么程序的问题可以是这样的:“当前问题索引值是否大于3?”如果是,将索引值设为1,这样就回到了第一题。表8-5中列出了实现此项功能需要的块。

表8-5 检查索引值是否到了列表的结尾所需的块

代码块 所在抽屉 作用
如果......则 控制 检查用户是否正在回答最后一道题
大于 数学 用于判断当前问题索引值是否大于3
global 当前问题索引值 从声明变量块中拖出 将其放在“大于”的左侧
数字:3 数学 放在“大于”的右侧,因为列表中仅有3个列表项
设当前问题索引值为 从声明变量块中拖出 将其值设为1,即,转到第一题
数字:1 数学 将当前问题索引值设为1

具体的代码如图8-7所示。

图8-7 检测索引值是否超过最后一题

块的作用

当用户点击下一题按钮时,程序依然会将索引值递增,但是如图8-7所示,程序会判断当前问题索引值是否大于3(3是问题列表中题目的数量):如果索引值大于3,则将其设置为1,并显示第一题;如果索引值小于或等于3,则不执行“如果......则”中的代码,直接显示索引值对应的问题。

测试:应用启动后,单击手机上的下一题按钮,会照常出现第二题“哪位总统在1979年实现中美建交?”。继续点击“下一题”,将显示第三题。下面才是你真正想测试的:如果再次点击,将出现第一题(“哪位总统在大萧条时期实施了‘新政’?”)。

让程序易于修改

如果下一题按钮的点击事件处理程序能够正常运行,恭喜你,你正在成为一名合格的程序员!但是,如果需要在问答应用中添加新题目(及答案),该怎么办?这些块还能正常运行吗?为了验证这一点,先在问题列表中添加第四道题,并在答案列表中添加第四个答案,如图8-8所示。

图8-8 为两个列表增加列表项

测试:多次点击下一题按钮,你会发现无论点击多少次,第四题始终不出现。知道问题所在吗?在继续阅读之前,尝试做些修改,以便让第四题出现。

问题出在“最后一题”的判断条件太具体:当前问题索引值是否大于3。如果把3改为4,程序又可以正确运行。但问题是,每次增减问题和答案时,都要留心修改判断条件。

计算机程序中的这种强相关性最容易导致错误,特别是当程序变得复杂时。好的对策是让程序的设计与列表中的问题数量无关。有一天你可能会创建一个其他主题的问答应用,而对于一个程序员来说,程序的通用性可以让程序的移植更加容易,尤其是那些需要处理动态列表的应用,这种通用性是必需的。例如,在有些应用中,用户可以动态地添加新问题(见第10章)。一个通用性好的程序不该与3这样的具体数字相关联,因为这只对那些有三个问题的问答应用有效。

因此,当前问题索引值的判断条件应该是问题列表的长度(项数),而不是具体的数字3。当条件更具通用性时,即使是添加或删除问题列表中的项,程序也能正常运行。现在修改下一题按钮点击事件处理程序,替换掉具体数字3。表8-6中列出了所需要的块。

表8-6 检查列表长度所需的块

|代码块|所在抽屉|作用| |||| |列表()的长度|列表|求问题列表中有多少个列表项| |global 问题列表|从声明变量块中拖出|填充在求列表长度块的插槽中|

块的作用

“如果......则”块中将当前问题索引值与问题列表的长度进行比较,如图8-9所示。如果当前问题索引值为5,而问题列表的长度为4,则当前问题索引值将被重新设置为1。值得注意的是,由于程序不再与3或任何具体数字相关联,因此无论列表中有多少项,程序都将正常运行。

图8-9 判断索引值是否大于列表长度(而不是3)

测试:当单击下一题按钮时,程序是否在四个问题间循环?在显示第四题后是否又回到第一题?

为问题匹配图片

现在程序已经可以遍历所有的问题(由于代码摆脱了对具体值的依赖,因此程序变得更加智能,也更加灵活),下面的任务是为问题匹配图片。眼下无论显示什么问题,图片都是同一个。我们希望当用户单击下一题按钮时,图片与问题相匹配。此前上传了四张素材图片,现在用图片的文件名来创建第三个列表——图片列表。然后修改下一题按钮的点击事件处理程序,同时切换问题与图片。(如果你能联想到当前问题索引值,说明你已经开窍了!)首先创建图片列表,用图片文件名初始化列表项,要保证列表中的文件名与先前上传的图片文件名完全相同。图8-10种显示了图片列表的样子。

图8-10 由图片文件名组成的图片列表

下面来修改下一题按钮点击事件处理程序,以便图片可以随当前问题索引值的改变而改变。图片组件的图片属性用于指定要显示的图片。表8-7中列出了修改下一题按钮点击事件所需的块。

表8-7 显示与问题相匹配的图片所需的块

代码块 所在抽屉 作用
设图片1的图片属性为 图片1 设置要显示的图片
列表()中的第()项 列表 选择一个与当前问题相匹配的图片
global 图片列表 从声明变量块中拖出 从列表中选择一个文件名
global 当前问题索引值 从声明变量块中拖出 选择第“索引值”项

块的作用

当前问题索引值同时充当问题列表及图片列表的索引值,实现对问题及图片的选择。这样做的前提是,两个列表具有一致的排列顺序,如,第一题对应第一张图,第二题对应第二张图,以此类推,这样一个索引值才能用于两个列表,如图8-11所示。举例说明:第一张图roosChurch.gif是富兰克林·德拉诺·罗斯福总统的图片(与英国首相丘吉尔在一起),而“罗斯福”也是第一个问题的答案。

图8-11 选择与当前问题索引值相匹配的图片

测试:多次点击下一题按钮,每次点击是否出现不同的图片?

判断答案对错

现在应用已经可以遍历所有的题目及答案(以及与答案匹配的图片),这是列表应用的极好案例。但是,现实中的问答应用需要对用户的回答给出评判,下面添加一些代码实现这一功能。用户界面上为用户提供了输入答案的答案输入框,用户输入答案,并点击回答按钮提交答案。程序将使用“如果......则......否则”块,将用户输入的答案与正确答案做比较,然后根据比较结果,给出不同的评价,并显示在对错标签中。表8-8列出了程序中用到的块。

表8-8 用于显示答案是否正确所需的块

代码块 所在抽屉 作用
当回答按钮被点击时 回答按钮 当用户点击回答按钮时,触发该事件
如果......则......否则 控制 如果答案正确,执行一个分支;否则,执行另一个分支
等于 数学 判断答案是否正确
答案输入框的显示文本 答案输入框 其中包含了用户输入的答案
列表()中的第()项 列表 从答案列表中选择与当前问题对应的答案
global 答案列表 从声明变量块中拖出 从中选择列表项
当前问题索引值 从声明变量块中拖出 当前问题(及答案)的序号
设对错标签的显示文本为 对错标签 显示对答案的评价
文本“正确” 文本 对正确答案的评价
设对错标签的显示文本为 对错标签 显示对答案的评价
文本“错误” 文本 对错误答案的评价

块的作用

在图8-12中,“如果......则......否则”块用来检验用户的输入(答案输入框的显示文本)是否等于答案列表中与问题相对应的项。如果当前问题索引值等于1,程序将用户的回答与答案列表中的第一项“罗斯福”对比;同样,如果当前问题索引值等于2,则与答案列表中的第二项“卡特”对比,等等。如果对比结果相同,执行“则”分支,即让对错标签显示“正确!”;如果对比结果不同,执行“否则”分支,即让对错标签显示“错误!”。

图8-12 对答案进行判断

测试:尝试回答一道题,程序会显示你的答案是否正确。分别测试正确的和错误的回答。你会发现,给出正确的答案并非易事,因为你的输入必须与答案列表中的答案完全匹配(包括大小写、标点及空格)。继续测试后面的问题,并确认运行正常。

应用运行正常,不过当回答完一道题,点击下一题按钮后,虽然图片和问题都切换到下一题,但“正确!”或“错误!”的文本以及前一题中输入的回答仍然显示在屏幕上,如图8-13所示。尽管这些小问题看起来无关紧要,但用户肯定会发现这些明显的瑕疵。纠正这些小瑕疵很容易,将对错标签及答案输入框中的显示文本清空即可,在下一题按钮的点击事件处理程序中添加几个块,表8-9列出了所需的块。

图8-13 界面中遗留了上一题的答案和正误判断

表8-9 清除瑕疵需要的块

代码块 所在抽屉 作用
设对错标签的显示文本为 对错标签 需要清空内容的标签
文本“” 文本 当用户点击下一题按钮时,清空对上一题答案的判断
设答案输入框的显示文本为 答案输入框 用户对上一题的回答
文本“” 文本 当用户点击下一题按钮时,清空对上一题的回答

块的作用

如图8-14所示, 用户单击下一题按钮时,用户界面上显示下一题。在下一题按钮的点击事件处理程序中,前两行代码用于清空对错标签以及答案输入框的显示文本。

图8-14 前两行代码用于清空上一题的遗留信息

测试:回答一个问题,点击回答按钮,再单击下一题按钮,上一题的答案及反馈是否消失了?

完整的“总统问答”应用

图8-15中显示了“总统问答”应用中全部代码的最终版本。

图8-15 “总统问答”应用中的全部代码块

改进

当应用运行起来之后,通常你会发现它还可以变得更好,于是总会有些后续的改进,举例如下。

  • 现在应用中只显示与问题有关的图片,也可以尝试播放与问题相关的录音或视频片段。使用声音素材,你甚至可以开发出一款“辩声识曲”(Name That Tune)的应用。
  • 本应用对正确答案的要求过于苛刻,有几种改进方法:一是利用文本抽屉中的文本处理块。例如,在将用户答案与正确答案比较之前,将两者都转化为大写字母;又比如,利用“文本中是否包含子串”的功能,来判断用户提供的答案是否包含在正确答案中{![这两种方法仅适用于英文,对中文无效。——译者注]}。另一种方法是给每道题提供多个正确答案,并通过遍历(针对列表中的每一项)来检查用户的答案是否与其中的某个正确答案相匹配。
  • 另一个对判断正误环节的改进思路是,将应用改为多选题,这需要使用另一个列表来保存每个问题的可选答案。答案列表将是一个二级列表,第二级列表中保存着某一问题的可选答案。使用列表选择框组件,让用户来选择答案。更多关于列表的内容请参见第19章。

小结

下面是本章中所涉及的一些概念。

  • 许多稍微复杂一点的应用中都会涉及数据(通常保存在列表中)处理及行为设定(事件处理程序)。
  • 使用“如果......则”块来做条件判断。有关条件语句的更多信息,请参见第18章。
  • 在事件处理程序中,尽可能使用抽象的变量来指代列表项及列表长度。这样,当列表数据发生变化时,程序依然可以正常运行。
  • 索引值变量可以跟踪当前项在列表中的位置。当索引值递增时,要小心列表的末尾,使用“如果......则”块来作判断,解决此类问题。