“总统问答”是一个关于美国前总统的趣味问答游戏。虽然问答的内容与总统有关,但你可以把它当作模板,将应用扩展为对任何话题的问答。
在前几章中,我们介绍了一些编程的基本概念。现在,你应该有能力应对更大的挑战了。在完成本章的学习后,你会发现,无论是编程技巧,还是抽象思维能力,都将产生一个质的飞跃。特别需要强调的是,本章将使用两个列表变量来存储数据——问题列表及答案列表,并使用索引值变量来跟踪用户正在回答的题目。学完本章,你所掌握的知识,将足以创建这种问答类应用,以及其他需要使用列表的应用。
在本章的问答应用中,用户通过点击下一题按钮,按顺序回答问题。每回答一题,应用将告知用户他的答案是否正确。
学习要点
应用的外观如图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显示了应用在设计视图中的截图,按图来创建组件。
表8-1 “总统问答”应用中所需的组件
- 为组件添加行为设置图片1的图片属性为roosChurch.gif(最先出现的图片);宽度为“充满”,高度为200。
- 设问题标签的显示文本属性为“问题……”(稍后在编程视图中设置为第一题)。
- 设答案输入框的提示属性为“请输入回答”,显示文本为空,放在水平布局1中。
- 设回答按钮的显示文本为“回答”,并放在水平布局1中。
- 设下一题按钮的显示文本为“下一步”。
- 设对错标签的显示文本为空。
应用将实现以下行为。
- 在应用启动时,显示第一个问题以及相应的图片。
- 点击下一题按钮时,显示第二题。再次点击,显示第三题,以此类推。
- 当显示最后一题时,点击下一题按钮将转回到第一题。
- 在用户回答问题之后,显示答案是否正确。
通过编程来逐一实现上述行为,边做边测试。
定义问题及答案列表
首先按照表8-2的提示,定义两个列表变量:问题列表用来保存问题,答案列表用来保存答案。图8-3中显示了需要在编程视图中创建的两个列表。
表8-2 用于保存问题和答案的列表变量
表8-3 创建索引值变量需要的块
显示第一个问题
代码块的设定应该与列表中的具体问题无关。这样,如果需要更换问题或创建新的问答类应用,只需改变列表中的具体问题,而不必修改事件处理程序。
鉴于上述考虑,对于第一道题,不要直接使用“哪位总统在大萧条时期实施了‘新政’?”这样的题目内容,而是采用“问题列表的第一个插槽中的内容”这样抽象的形式(与具体问题无关)。这样,即使第一个插槽中的问题改变了,这些程序块仍然有效。
使用代码块“列表()中的第()项”,可以从列表中选取指定的列表项,这需要为代码块指定两个参数:列表及索引值(项在列表中的位置)。如果列表中有三个项,则索引值可以是1、2或3。
我们要定义的第一个行为是,当应用启动时,选择问题列表中的第一道题,并用问题标签显示出来。还记得“安卓,我的车在哪儿?”应用吧,如果想让某件事发生在应用启动时,可以将有关指令放在Screen1的初始化事件处理程序中。表8-4中列出了所需的块。
表8-4 应用启动后显示第一个问题需要的块
应用启动时触发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 检查索引值是否到了列表的结尾所需的块
图8-7 检测索引值是否超过最后一题具体的代码如图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-11所示。举例说明:第一张图roosChurch.gif是富兰克林·德拉诺·罗斯福总统的图片(与英国首相丘吉尔在一起),而“罗斯福”也是第一个问题的答案。
判断答案对错
现在应用已经可以遍历所有的题目及答案(以及与答案匹配的图片),这是列表应用的极好案例。但是,现实中的问答应用需要对用户的回答给出评判,下面添加一些代码实现这一功能。用户界面上为用户提供了输入答案的答案输入框,用户输入答案,并点击回答按钮提交答案。程序将使用“如果……则……否则”块,将用户输入的答案与正确答案做比较,然后根据比较结果,给出不同的评价,并显示在对错标签中。表8-8列出了程序中用到的块。
表8-8 用于显示答案是否正确所需的块
在图8-12中,“如果……则……否则”块用来检验用户的输入(答案输入框的显示文本)是否等于答案列表中与问题相对应的项。如果当前问题索引值等于1,程序将用户的回答与答案列表中的第一项“罗斯福”对比;同样,如果当前问题索引值等于2,则与答案列表中的第二项“卡特”对比,等等。如果对比结果相同,执行“则”分支,即让对错标签显示“正确!”;如果对比结果不同,执行“否则”分支,即让对错标签显示“错误!”。块的作用
应用运行正常,不过当回答完一道题,点击下一题按钮后,虽然图片和问题都切换到下一题,但“正确!”或“错误!”的文本以及前一题中输入的回答仍然显示在屏幕上,如图8-13所示。尽管这些小问题看起来无关紧要,但用户肯定会发现这些明显的瑕疵。纠正这些小瑕疵很容易,将对错标签及答案输入框中的显示文本清空即可,在下一题按钮的点击事件处理程序中添加几个块,表8-9列出了所需的块。
如图8-14所示, 用户单击下一题按钮时,用户界面上显示下一题。在下一题按钮的点击事件处理程序中,前两行代码用于清空对错标签以及答案输入框的显示文本。
完整的“总统问答”应用
图8-15中显示了“总统问答”应用中全部代码的最终版本。
改进
当应用运行起来之后,通常你会发现它还可以变得更好,于是总会有些后续的改进,举例如下。
- 现在应用中只显示与问题有关的图片,也可以尝试播放与问题相关的录音或视频片段。使用声音素材,你甚至可以开发出一款“辩声识曲”(Name That Tune)的应用。
- 本应用对正确答案的要求过于苛刻,有几种改进方法:一是利用文本抽屉中的文本处理块。例如,在将用户答案与正确答案比较之前,将两者都转化为大写字母;又比如,利用“文本中是否包含子串”的功能,来判断用户提供的答案是否包含在正确答案中{![这两种方法仅适用于英文,对中文无效。——译者注]}。另一种方法是给每道题提供多个正确答案,并通过遍历(针对列表中的每一项)来检查用户的答案是否与其中的某个正确答案相匹配。
- 另一个对判断正误环节的改进思路是,将应用改为多选题,这需要使用另一个列表来保存每个问题的可选答案。答案列表将是一个二级列表,第二级列表中保存着某一问题的可选答案。使用列表选择框组件,让用户来选择答案。更多关于列表的内容请参见第19章。
小结
下面是本章中所涉及的一些概念。
- 许多稍微复杂一点的应用中都会涉及数据(通常保存在列表中)处理及行为设定(事件处理程序)。
- 使用“如果……则”块来做条件判断。有关条件语句的更多信息,请参见第18章。
- 在事件处理程序中,尽可能使用抽象的变量来指代列表项及列表长度。这样,当列表数据发生变化时,程序依然可以正常运行。
- 索引值变量可以跟踪当前项在列表中的位置。当索引值递增时,要小心列表的末尾,使用“如果……则”块来作判断,解决此类问题。
本文来自投稿,不代表本站立场。作者:17coding,如若转载,请注明出处:《App Inventor编程教程-第9课-总统问答》https://www.shaoerbc.org/code-course/appinventor-course/1145.html