第8章 总统问答
“总统问答”是一个关于美国前总统的趣味问答游戏。虽然问答的内容与总统有关,但你可以把它当作模板,将应用扩展为对任何话题的问答。
在前几章中,我们介绍了一些编程的基本概念。现在,你应该有能力应对更大的挑战了。在完成本章的学习后,你会发现,无论是编程技巧,还是抽象思维能力,都将产生一个质的飞跃。特别需要强调的是,本章将使用两个列表变量来存储数据——问题列表及答案列表,并使用索引值变量来跟踪用户正在回答的题目。学完本章,你所掌握的知识,将足以创建这种问答类应用,以及其他需要使用列表的应用。
本章假设你已经熟悉了App Inventor的基础知识:在设计视图中创建用户界面,在编程视图中定义事件处理程序,以便为组件添加行为。如果你对于上述知识还不甚熟悉,请在继续学习之前,复习前面几章的内容。
在本章的问答应用中,用户通过点击下一题按钮,按顺序回答问题。每回答一题,应用将告知用户他的答案是否正确。
学习要点
应用的外观如图8-1所示,它涵盖以下内容。
- 定义列表变量:用来存储列表中的问题和答案。
- 使用索引值遍历列表,用户每次点击下一题按钮时,显示下一个问题。
- 使用条件语句(如果……则)控制行为:只有在特定条件下才能执行某些操作;在用户回答完最后一题时,使用“如果......则”块来处理程序。
- 每一道题对应一张图片,切换题目的同时要切换图片,以使题目与图片保持一致。
准备开始
登录App Inventor网站,创建新项目“总统问答”,并设置屏幕的标题为“总统问答”,连接设备或模拟器来进行实时测试。
从appinventor.org网站下载应用中用到的图片,并在下一节中将这些图片上传到项目中。
- http://appinventor.org/bookFiles/PresidentsQuiz/roosChurch.gif
- http://appinventor.org/bookFiles/PresidentsQuiz/nixon.gif
- http://appinventor.org/bookFiles/PresidentsQuiz/carterChina.gif
- http://appinventor.org/bookFiles/PresidentsQuiz/atomic.gif
设计组件
“总统问答”应用的界面很简单:显示问题并允许用户来回答。图8-2显示了应用在设计视图中的截图,按图来创建组件。
首先将下载的图片上传到项目中:点击素材区域的“上传文件”按钮,选择一个文件(如roosChurch.gif),点击打开、确定。其他三张图片也是如此操作。然后添加表8-1中列出的组件。
表8-1 “总统问答”应用中所需的组件
组件类型 | 所属类别 | 名称 | 作用 |
---|---|---|---|
图片 | 用户界面 | 图片1 | 显示与问题相对应的图片 |
标签 | 用户界面 | 问题标签 | 显示等待回答的问题 |
水平布局 | 组件布局 | 水平布局1 | 放置输入答案的文本框及提交答案的按钮 |
文本输入框 | 用户界面 | 答案输入框 | 用户在其中输入答案 |
按钮 | 用户界面 | 回答按钮 | 供用户点击以提交答案 |
标签 | 用户界面 | 对错标签 | 显示“正确”或“错误” |
按钮 | 用户界面 | 下一题按钮 | 点击进入下一道问题 |
- 设置图片1的图片属性为roosChurch.gif(最先出现的图片);宽度为“充满”,高度为200。
- 设问题标签的显示文本属性为“问题…...”(稍后在编程视图中设置为第一题)。
- 设答案输入框的提示属性为“请输入回答”,显示文本为空,放在水平布局1中。
- 设回答按钮的显示文本为“回答”,并放在水平布局1中。
- 设下一题按钮的显示文本为“下一步”。
- 设对错标签的显示文本为空。
为组件添加行为
应用将实现以下行为。
- 在应用启动时,显示第一个问题以及相应的图片。
- 点击下一题按钮时,显示第二题。再次点击,显示第三题,以此类推。
- 当显示最后一题时,点击下一题按钮将转回到第一题。
- 在用户回答问题之后,显示答案是否正确。
通过编程来逐一实现上述行为,边做边测试。
定义问题及答案列表
首先按照表8-2的提示,定义两个列表变量:问题列表用来保存问题,答案列表用来保存答案。图8-3中显示了需要在编程视图中创建的两个列表。
表8-2 用于保存问题和答案的列表变量
代码块 | 所在抽屉 | 作用 |
---|---|---|
声明全局变量(问题列表) | 变量 | 保存问题的列表 |
声明全局变量(答案列表) | 变量 | 保存答案的列表 |
列表 | 列表 | 为问题列表创建列表项 |
文本(共三段) | 文本 | 三个问题 |
列表 | 列表 | 为答案列表创建列表项 |
文本(共三段) | 文本 | 三个答案 |
定义索引值变量
在应用运行过程中,用户每次点击下一题按钮,用户界面中将显示当前要回答的问题。我们的程序需要跟踪当前问题,以便进行后续的操作。定义变量“当前问题索引值”,作为问题列表及答案列表的共同索引值。表8-3列出了所需的块,图8-4显示了变量的定义。
表8-3 创建索引值变量需要的块
代码块 | 所在抽屉 | 作用 |
---|---|---|
声明全局变量(当前问题索引值) | 变量 | 保存当前问题在问题列表中的索引值(位置) |
数字:1 | 数学 | 设该索引值的初始值为1(第一道题) |
显示第一个问题
定义了这些变量,就可以为应用设定交互行为了。无论是开发哪种类型的应用,渐进式的开发方法都是非常重要的,而且最好每一步只定义一个行为。我们首先考虑与问题相关的行为,具体来说,就是当应用启动时,首先显示列表中的第一道题。稍后再回来处理与图片及答案相关的行为。
代码块的设定应该与列表中的具体问题无关。这样,如果需要更换问题或创建新的问答类应用,只需改变列表中的具体问题,而不必修改事件处理程序。
鉴于上述考虑,对于第一道题,不要直接使用“哪位总统在大萧条时期实施了‘新政’?”这样的题目内容,而是采用“问题列表的第一个插槽中的内容”这样抽象的形式(与具体问题无关)。这样,即使第一个插槽中的问题改变了,这些程序块仍然有效。
使用代码块“列表()中的第()项”,可以从列表中选取指定的列表项,这需要为代码块指定两个参数:列表及索引值(项在列表中的位置)。如果列表中有三个项,则索引值可以是1、2或3。
我们要定义的第一个行为是,当应用启动时,选择问题列表中的第一道题,并用问题标签显示出来。还记得“安卓,我的车在哪儿?”应用吧,如果想让某件事发生在应用启动时,可以将有关指令放在Screen1的初始化事件处理程序中。表8-4中列出了所需的块。
表8-4 应用启动后显示第一个问题需要的块
代码块 | 所在抽屉 | 作用 |
---|---|---|
当Screen1初始化时 | Screen1 | 当应用启动时,触发该事件(执行其事件处理程序) |
设问题标签的显示文本为 | 问题标签 | 让问题标签显示第一道题 |
列表()中的第()项 | 列表 | 从问题列表中选择第一题 |
global 问题列表 | 变量 | 从中选择问题的列表 |
数字:1 | 数学 | 使用索引值1从问题列表中选择第一题 |
块的作用
应用启动时触发Screen1的初始化事件。如图8-5所示,问题列表中的第一项被选中,并被设为问题标签的显示文本。因此,应用启动时,用户会看到第一道题。
测试:连接测试设备或模拟器,进行实时测试。当应用启动后,是否在用户界面上看到问题列表中的第一道题:“哪位总统在大萧条时期实施了‘新政’?”
遍历所有问题
下面针对下一题按钮的行为进行编程。之前定义的“当前问题索引值”,用来记住用户正在回答的问题。当用户单击下一题按钮时,需要为当前问题索引值加1(即,从1变为2,或从2变为3,以此类推),并根据这个值来选择并显示新的问题。挑战一下你自己,看看是否可以自己搭建这些块。完成之后,与图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所示,程序会判断当前问题索引值是否大于3(3是问题列表中题目的数量):如果索引值大于3,则将其设置为1,并显示第一题;如果索引值小于或等于3,则不执行“如果......则”中的代码,直接显示索引值对应的问题。
测试:应用启动后,单击手机上的下一题按钮,会照常出现第二题“哪位总统在1979年实现中美建交?”。继续点击“下一题”,将显示第三题。下面才是你真正想测试的:如果再次点击,将出现第一题(“哪位总统在大萧条时期实施了‘新政’?”)。
让程序易于修改
如果下一题按钮的点击事件处理程序能够正常运行,恭喜你,你正在成为一名合格的程序员!但是,如果需要在问答应用中添加新题目(及答案),该怎么办?这些块还能正常运行吗?为了验证这一点,先在问题列表中添加第四道题,并在答案列表中添加第四个答案,如图8-8所示。
测试:多次点击下一题按钮,你会发现无论点击多少次,第四题始终不出现。知道问题所在吗?在继续阅读之前,尝试做些修改,以便让第四题出现。
问题出在“最后一题”的判断条件太具体:当前问题索引值是否大于3。如果把3改为4,程序又可以正确运行。但问题是,每次增减问题和答案时,都要留心修改判断条件。
计算机程序中的这种强相关性最容易导致错误,特别是当程序变得复杂时。好的对策是让程序的设计与列表中的问题数量无关。有一天你可能会创建一个其他主题的问答应用,而对于一个程序员来说,程序的通用性可以让程序的移植更加容易,尤其是那些需要处理动态列表的应用,这种通用性是必需的。例如,在有些应用中,用户可以动态地添加新问题(见第10章)。一个通用性好的程序不该与3这样的具体数字相关联,因为这只对那些有三个问题的问答应用有效。
因此,当前问题索引值的判断条件应该是问题列表的长度(项数),而不是具体的数字3。当条件更具通用性时,即使是添加或删除问题列表中的项,程序也能正常运行。现在修改下一题按钮点击事件处理程序,替换掉具体数字3。表8-6中列出了所需要的块。
表8-6 检查列表长度所需的块
|代码块|所在抽屉|作用| |||| |列表()的长度|列表|求问题列表中有多少个列表项| |global 问题列表|从声明变量块中拖出|填充在求列表长度块的插槽中|
块的作用
“如果......则”块中将当前问题索引值与问题列表的长度进行比较,如图8-9所示。如果当前问题索引值为5,而问题列表的长度为4,则当前问题索引值将被重新设置为1。值得注意的是,由于程序不再与3或任何具体数字相关联,因此无论列表中有多少项,程序都将正常运行。
测试:当单击下一题按钮时,程序是否在四个问题间循环?在显示第四题后是否又回到第一题?
为问题匹配图片
现在程序已经可以遍历所有的问题(由于代码摆脱了对具体值的依赖,因此程序变得更加智能,也更加灵活),下面的任务是为问题匹配图片。眼下无论显示什么问题,图片都是同一个。我们希望当用户单击下一题按钮时,图片与问题相匹配。此前上传了四张素材图片,现在用图片的文件名来创建第三个列表——图片列表。然后修改下一题按钮的点击事件处理程序,同时切换问题与图片。(如果你能联想到当前问题索引值,说明你已经开窍了!)首先创建图片列表,用图片文件名初始化列表项,要保证列表中的文件名与先前上传的图片文件名完全相同。图8-10种显示了图片列表的样子。
下面来修改下一题按钮点击事件处理程序,以便图片可以随当前问题索引值的改变而改变。图片组件的图片属性用于指定要显示的图片。表8-7中列出了修改下一题按钮点击事件所需的块。
表8-7 显示与问题相匹配的图片所需的块
代码块 | 所在抽屉 | 作用 |
---|---|---|
设图片1的图片属性为 | 图片1 | 设置要显示的图片 |
列表()中的第()项 | 列表 | 选择一个与当前问题相匹配的图片 |
global 图片列表 | 从声明变量块中拖出 | 从列表中选择一个文件名 |
global 当前问题索引值 | 从声明变量块中拖出 | 选择第“索引值”项 |
块的作用
当前问题索引值同时充当问题列表及图片列表的索引值,实现对问题及图片的选择。这样做的前提是,两个列表具有一致的排列顺序,如,第一题对应第一张图,第二题对应第二张图,以此类推,这样一个索引值才能用于两个列表,如图8-11所示。举例说明:第一张图roosChurch.gif是富兰克林·德拉诺·罗斯福总统的图片(与英国首相丘吉尔在一起),而“罗斯福”也是第一个问题的答案。
测试:多次点击下一题按钮,每次点击是否出现不同的图片?
判断答案对错
现在应用已经可以遍历所有的题目及答案(以及与答案匹配的图片),这是列表应用的极好案例。但是,现实中的问答应用需要对用户的回答给出评判,下面添加一些代码实现这一功能。用户界面上为用户提供了输入答案的答案输入框,用户输入答案,并点击回答按钮提交答案。程序将使用“如果......则......否则”块,将用户输入的答案与正确答案做比较,然后根据比较结果,给出不同的评价,并显示在对错标签中。表8-8列出了程序中用到的块。
表8-8 用于显示答案是否正确所需的块
代码块 | 所在抽屉 | 作用 |
---|---|---|
当回答按钮被点击时 | 回答按钮 | 当用户点击回答按钮时,触发该事件 |
如果......则......否则 | 控制 | 如果答案正确,执行一个分支;否则,执行另一个分支 |
等于 | 数学 | 判断答案是否正确 |
答案输入框的显示文本 | 答案输入框 | 其中包含了用户输入的答案 |
列表()中的第()项 | 列表 | 从答案列表中选择与当前问题对应的答案 |
global 答案列表 | 从声明变量块中拖出 | 从中选择列表项 |
当前问题索引值 | 从声明变量块中拖出 | 当前问题(及答案)的序号 |
设对错标签的显示文本为 | 对错标签 | 显示对答案的评价 |
文本“正确” | 文本 | 对正确答案的评价 |
设对错标签的显示文本为 | 对错标签 | 显示对答案的评价 |
文本“错误” | 文本 | 对错误答案的评价 |
块的作用
在图8-12中,“如果......则......否则”块用来检验用户的输入(答案输入框的显示文本)是否等于答案列表中与问题相对应的项。如果当前问题索引值等于1,程序将用户的回答与答案列表中的第一项“罗斯福”对比;同样,如果当前问题索引值等于2,则与答案列表中的第二项“卡特”对比,等等。如果对比结果相同,执行“则”分支,即让对错标签显示“正确!”;如果对比结果不同,执行“否则”分支,即让对错标签显示“错误!”。
测试:尝试回答一道题,程序会显示你的答案是否正确。分别测试正确的和错误的回答。你会发现,给出正确的答案并非易事,因为你的输入必须与答案列表中的答案完全匹配(包括大小写、标点及空格)。继续测试后面的问题,并确认运行正常。
应用运行正常,不过当回答完一道题,点击下一题按钮后,虽然图片和问题都切换到下一题,但“正确!”或“错误!”的文本以及前一题中输入的回答仍然显示在屏幕上,如图8-13所示。尽管这些小问题看起来无关紧要,但用户肯定会发现这些明显的瑕疵。纠正这些小瑕疵很容易,将对错标签及答案输入框中的显示文本清空即可,在下一题按钮的点击事件处理程序中添加几个块,表8-9列出了所需的块。
表8-9 清除瑕疵需要的块
代码块 | 所在抽屉 | 作用 |
---|---|---|
设对错标签的显示文本为 | 对错标签 | 需要清空内容的标签 |
文本“” | 文本 | 当用户点击下一题按钮时,清空对上一题答案的判断 |
设答案输入框的显示文本为 | 答案输入框 | 用户对上一题的回答 |
文本“” | 文本 | 当用户点击下一题按钮时,清空对上一题的回答 |
块的作用
如图8-14所示, 用户单击下一题按钮时,用户界面上显示下一题。在下一题按钮的点击事件处理程序中,前两行代码用于清空对错标签以及答案输入框的显示文本。
测试:回答一个问题,点击回答按钮,再单击下一题按钮,上一题的答案及反馈是否消失了?
完整的“总统问答”应用
图8-15中显示了“总统问答”应用中全部代码的最终版本。
改进
当应用运行起来之后,通常你会发现它还可以变得更好,于是总会有些后续的改进,举例如下。
- 现在应用中只显示与问题有关的图片,也可以尝试播放与问题相关的录音或视频片段。使用声音素材,你甚至可以开发出一款“辩声识曲”(Name That Tune)的应用。
- 本应用对正确答案的要求过于苛刻,有几种改进方法:一是利用文本抽屉中的文本处理块。例如,在将用户答案与正确答案比较之前,将两者都转化为大写字母;又比如,利用“文本中是否包含子串”的功能,来判断用户提供的答案是否包含在正确答案中{![这两种方法仅适用于英文,对中文无效。——译者注]}。另一种方法是给每道题提供多个正确答案,并通过遍历(针对列表中的每一项)来检查用户的答案是否与其中的某个正确答案相匹配。
- 另一个对判断正误环节的改进思路是,将应用改为多选题,这需要使用另一个列表来保存每个问题的可选答案。答案列表将是一个二级列表,第二级列表中保存着某一问题的可选答案。使用列表选择框组件,让用户来选择答案。更多关于列表的内容请参见第19章。
小结
下面是本章中所涉及的一些概念。
- 许多稍微复杂一点的应用中都会涉及数据(通常保存在列表中)处理及行为设定(事件处理程序)。
- 使用“如果......则”块来做条件判断。有关条件语句的更多信息,请参见第18章。
- 在事件处理程序中,尽可能使用抽象的变量来指代列表项及列表长度。这样,当列表数据发生变化时,程序依然可以正常运行。
- 索引值变量可以跟踪当前项在列表中的位置。当索引值递增时,要小心列表的末尾,使用“如果......则”块来作判断,解决此类问题。