开发体验

第19章 数据列表编程

如你所见,应用包含了对事件的处理,以及处理过程中的决策。这些处理过程构成了程序的基础,但只有这些是远远不够的,构成程序的另一个非常重要的基础就是数据——程序所要处理的信息。程序中的数据可以划分为简单数据和复杂数据两种类型。简单数据只占有一个单独的存储空间(如数字变量、字符串变量、逻辑变量等),像游戏中分数这样的数据就属于简单数据。不过只有少量应用中只涉及简单数据,更普遍的情况是,大多数应用中都会使用复杂数据。复杂数据是由多个甚至多种简单数据以某种特定结构组织起来的数据列表,必须像设计应用的功能一样,非常细心地组织这些数据。

本章将探讨App Inventor中的数据处理方式。我们先对复杂数据(数据列表)进行分类,按照数据的可变性,将其分为静态数据和动态数据。凡是在程序运行过程中保持不变的数据,我们称之为静态数据;反之,在程序运行过程中会发生变化的数据,如由最终用户输入的数据,称为动态数据。我们将学习如何针对列表数据编程,然后再探索一种更为复杂的结构化数据——包含列表的列表,并在问答类应用中实现多选答案功能。

许多应用中都会涉及对列表的处理,如Facebook中的好友列表及状态列表,问答类应用中的问题及答案列表,游戏中的角色列表以及最高历史记录列表,等等。

在App Inventor中,用变量来定义列表数据,但与简单数据不同的是,列表数据的存储空间不再只是单一的存储单元,而是一组相互关联的存储单元。对于静态数据列表,可以使用“列表”块来设置列表变量的值,如图19-1所示,电话簿列表中包含了3个电话号码;对于动态数据的列表,可以使用“空列表”块来设置列表变量的初始值。

图19-1 声明列表变量,并设初始值

创建列表变量

在编程视图中,使用“声明全局变量”块来创建列表变量,并用列表块来初始化该列表变量。在内置块的列表类分组中,可以找到列表块。默认情况下,列表块有两个插槽, 点击其左上角的蓝色标记,可以扩展出任意多个插槽,以便向列表中添加更多的项,如图19-2所示。

图19-2 点击列表块上的蓝色标记,可以为列表添加更多的项

在列表块的插槽中,可以填入任意类型的数据,例如,在电话簿列表中,列表项的数据类型为文本,而非数字,因为电话号码中包含了破折号或其他无法用数字表示的符号。当然,电话号码也就不能用于数学运算(数学运算符只能接受数字对象)。

选择列表项

在应用运行过程中,需要从列表中选取指定的项。例如,在问答类应用中,用户要浏览每一个问题;在随机拨打电话应用中,要选取某个电话号码。为了从列表中选取指定的项,需要引入“索引值”的概念。由于列表中数据的排列顺序是相对固定的,因此,一个列表项在列表中的位置与列表项本身是一一对应的,这个排列位置被称为该列表项的索引值,我们就使用这个索引值来访问指定的项。假设某个列表中有三个项,则这些项的索引值分别是1、2、3,我们就用这些数字以及选取列表项块来获取指定的列表项。如图19-3所示,我们选中的是电话簿列表中的第二项。

图19-3 从列表中选取第二项

使用选择列表项块需要提供两个参数,第一个参数是将要从中进行选择的列表,填充到选择块的第一个插槽中;第二个参数是索引值,填充到第二个插槽中。在上述例子中,从电话簿列表中选中的第二项是“222-2222”。

使用索引值遍历列表

在许多应用中,定义列表的目的是让用户可以遍历列表(逐个查看或操作每一个列表项)。第8章总统问答就是一个很好的例子:用户点击下一题按钮,程序从问题列表中选中并显示下一题。

前面例子中选中的是列表中的第二项,但是如何才能选中下一项呢?在遍历列表的过程中,每次选中的列表项不同,因此列表项对应的索引值也不同,即选中项在列表中的位置不同。因此,我们需要定义一个变量来表示这个位置,通常以索引值作为变量名,初始值通常设为1(列表中的第一个位置),如图19-4所示。

图19-4 将变量索引值的初始值设为1

当用户设法移动到下一项时,可以在当前的索引值上加1,来实现变量的递增,并使用递增后的索引值在列表中进行选择。图19-5中显示了具体的实现方法。

图19-5 用递增之后的索引值访问列表中的下一项

举例:遍历颜色列表

设想有这样一个应用,用户在粉刷房子前要选择涂料的颜色:在颜色列表中保存了若干种备选颜色,用户点击按钮,可以浏览列表中的颜色;每次点击,按钮的颜色都会变化;当用户查阅完全部颜色时,再重新回到第一种颜色。

例子中使用的是三原色,你也可以修改代码,替换成任何一组颜色。

第一步,定义一个颜色列表,并将列表项设置为某些颜色,如图19-6所示。

图19-6 用三原色初始化颜色列表

接下来,定义变量索引值来跟踪当前选中项的位置,设初始值为1。可以给变量一个更有意义的名字,如“当前颜色索引值”,但如果应用中没有其他的列表索引值需要跟踪,那么用“索引值”就可以了,如图19-4所示。

用户通过点击颜色按钮,浏览列表中的下一个颜色,此时,索引值递增,而按钮的背景颜色变为当前选中的颜色,如图19-7所示。

图19-7 每次点击键钮,按钮的颜色都会改变

假设我们已经在设计视图中将按钮的背景颜色设为红色。第一次点击按钮时,索引值从初始的1变为2,按钮颜色变为列表中的第二项——绿色;第二次点击按钮时,索引值从2变为3,按钮变为蓝色。

想象一下,下一次点击会出现什么情况?

被你说中了,程序会出错!索引值将变成4,程序试图在颜色列表中选择第四项,但列表中只有三个列表项,因此程序会出错,用户将在开发环境的编程视图中看到一段错误提示信息,如图19-8所示。

图19-8 错误提示:试图从仅有3个列表项的列表中选择第四项

显然,你不想让用户看到这样的信息{![在App Inventor早期版本中,使用AI伴侣测试这个应用,错误信息会显示在用户界面上,但现在只显示在开发环境里,用户界面上没有反应,按钮会一直显示为第三项的蓝色。——译者注]}。为了避免出现这样的问题,需要添加一个“如果...则”块来判断是否到达了列表中的最后一项。如果是,将索引值设回1,来显示第一种颜色,如图19-9所示。

图19-9 判断索引值是否大于列表长度

用户点击按钮时,索引值递增,然后判断索引值是否过大。与索引值进行比较的是颜色列表的长度,而不是3,因此,即便是列表中添加了更多的颜色,程序也能正常运行。通过判断索引值是否大于列表长度(而不是固定的数字3),可以消除程序中的硬编码。所谓硬编码是一个编程术语,表示程序中的某些代码过于具体,使得程序缺乏适应性。举例说明,硬编码可能导致这样的结果:当你修改某一处的代码时,可能会遗漏对相关代码的同步修改,从而导致整个程序运行错误。以现在的例子来说,如果判断的条件不是列表长度,而是具体数字3,那么当你在颜色列表中添加更多的颜色时,就不得不同时修改判断条件(将3改为新的颜色列表长度),如果程序中有更多的地方使用了颜色列表,还必须对这些代码进行逐一修改。

正如你所想象的,这些硬编码会让程序在短时间内变得混乱不堪,也会产生更多潜在的错误。事实上,在“粉刷房屋”应用的设计中,就在我们刚刚完成的程序中,还存在另外一处硬编码,你能找出是什么吗?

如果将颜色列表中的第一项由红色改为其他颜色,应用的运行结果就不再正确,除非你能记得在设计视图中修改颜色按钮背景颜色的初始值。消除这种硬编码的方法是,用代码将颜色按钮的初始值设定为颜色列表中的第一项,而不是某个特定的颜色。由于这一修改涉及程序启动时的行为,因此需要在Screen1的初始化事件处理程序中进行这一设定,如图19-10所示。

图19-10 设按钮的背景颜色为列表中的第一种颜色

创建输入表单及动态数据

前面的“粉刷房屋”应用涉及一个静态列表:程序员(也就是你)定义了列表中的元素,且除非你亲自动手,没有人能修改这些列表项。然而,在多数情况下,应用中的列表是动态的:数据可能来源于最终用户的输入,也可能来源于数据库或网络信息源,因此列表的内容可能会随时改变。本节将讨论一个“随手记”应用:用户在应用中,通过表单输入笔记,并可浏览此前的笔记内容。

定义动态列表

像“随手记”这类应用,列表内容全部由用户输入,因此在应用开始运行时,要将列表变量设置为空列表,使用空列表块来定义列表的初始值,如图19-11所示。

图19-11 动态列表的初始值不包含任何预置项

添加数据项

当第一次启动应用时,笔记本列表是空的。当用户在表单中输入数据并点击“保存”按钮时,新的笔记内容将被添加到笔记本列表中。表单的设置非常简单,如图19-12所示。

图19-12 用户输入笔记的表单

当用户输入一段笔记并点击保存按钮时,应用将使用添加列表项块将新输入的内容添加到笔记本列表中,如图19-13所示。

图19-13 用户点击保存按钮时,向笔记本列表中添加一条新笔记

在内置块的列表抽屉中,可以找到添加列表项块。但要特别注意:还有另一个“将列表追加到列表”块,它的功能是向一个列表的末尾追加另一个列表,这个块极少使用。

显示列表

对用户来说,变量笔记本列表的内容是不可见的。还记得之前讲过,应用中的变量用来保存那些不需要被用户看到的信息。图19-13中的块实现了向笔记本列表添加新项的功能,但是,如果程序中没有添加显示列表内容的功能,用户是看不到任何反馈的。

要在用户界面中显示列表内容,最简单的方法莫过于将列表变量直接设置为标签的显示文本属性,就像显示数字和文本一样,如图19-14所示。

图19-14 在标签中直接显示列表内容

可惜这种简单的显示方式看起来不够美观,列表中所有的项被放置在一对小括号内,没有分行,项之间用空格分隔。举例来说,假如用户先输入了第一条笔记“我究竟能不能读完这本书呢?”,然后又输入第二条“我忘了我儿子长什么样!”,那么在应用中,笔记在用户界面上的效果将如图19-15所示。

图19-15 列表直接显示在标签中的默认样式

在第20章中,将学习如何用更加复杂的方式来显示列表内容。

删除列表项

使用“删除列表项”块可以从列表中删除某一项,如图19-16所示。

图19-16 删除列表项

图19-16中的块从笔记本列表中删除了第2项,但通常我们希望不只是能删除某个固定的项(如第2项),而是让用户来选择需要删除的项。

列表选择框组件是一个用户界面组件,非常适合用来删除列表项。在默认状态下,列表选择框显示为一个关联按钮,当用户点击该按钮时,列表选择框会显示出所有的列表项,并允许用户选择其中的一项。当用户选中某一项后,就可以将该项删除掉。

列表选择框有两个关键事件:准备选择事件和完成选择事件。此外,列表选择框还有两个重要的属性:列表和选中项。如表19-1所示,只要理解了列表选择框的两个事件及两个属性,对该组件的编程就变得非常容易。

表19-1 列表选择框组件的两个关键事件及属性

事件 属性
准备选择:点击关联按钮时触发该事件 列表:可供选择的数据列表
完成选择:用户做出选择时触发该事件 选中项:用户选中的项
选中项索引值:选中项在列表中的位置

当用户点击列表选择框的关联按钮时,触发准备选择事件。在准备选择事件处理程序中,可以设置列表选择框的列表属性,可以将列表属性设置为一个列表变量,以便用户可以从中进行选择。在“随手记”应用中,将列表属性设置为笔记本列表,如图19-17。

图19-17 将列表选择框的列表属性设置为笔记本列表

上述代码的作用是将笔记本列表的内容显示在列表选择框中。如果列表中有两条笔记,其显示效果将如图19-18所示。

图19-18 显示在列表选择框中的笔记本列表

当用户从列表中选择一项时,将触发完成选择事件。在该事件的处理程序中,可以利用列表选择框的选中项属性来获取用户的所选项。

不过,在这个例子中,选择的目的是从列表中删除该项,而在删除列表项的块中,需要的参数是索引值(第几项),而不是具体的项。选中项属性中保存的是实际数据(一条笔记),并不是索引值,因此需要使用选中项索引值属性,它提供了选中项在列表中的索引值,可以直接用于删除列表项块,作为它的索引值参数,如图19-19所示。

图19-19 用选中项索引值属性删除列表项

列表中的列表

列表项可以是任何类型的数据,包括数字、文本、颜色、布尔值(真、假),也可以是列表本身。这是一种常见的数据结构。例如,一个列表的列表可以将第8章的“总统问答”应用转变为提供多选答案的应用。我们来重温一下“总统问答”中数据的基本结构:一个问题列表和一个答案列表,如图19-20所示。

图19-20 问题及答案列表

每当用户回答完一个问题,程序将用户的回答与答案列表中对应的当前项进行对比,从而判断用户的回答是否正确。

为了提供多选答案,需要额外添加一个列表,为每个问题提供若干个可供选择的答案。将三个列表块放在一个外层的列表块中,来定义这个列表变量,如图19-21所示。

图19-21 创建一个列表的列表:在外层列表块中插入内层列表块

变量多选答案中的每个数据项本身也是一个由三个数据项组成的列表。如果从多选答案列表中选择一项,那么选择的结果将是一个列表。现在已经准备好了多选答案列表,该如何向用户显示这些数据呢?

在“随手记”应用中,使用了一个列表选择框来显示可供选择的列表数据。假如索引值被命名为当前问题索引值,则准备选择事件的处理程序将如图19-22所示。

图19-22 用列表选择框向用户呈现可供选择的答案

这些块将选取并显示多选答案列表中与当前问题对应的子列表,供用户选择。如果当前问题索引值为1,则列表选择框将显示第一题对应的备选答案,如图19-23所示。

图19-23 向用户展示第一题的多选答案

当用户选中一个答案之后,判断用户的选择是否正确,如图19-24所示。

图19-24 判断用户选择的答案是否正确

在上述块中,用户从列表选择框中选择的答案将与正确答案进行比较,而正确答案保存在另一个列表——答案列表中(注意多选答案列表只提供选项而不代表正确答案)。

小结

几乎你能想到的每个应用中都会用到列表,所以理解列表的运行机制是编程的基础。本章探索了一种最常用的编程模式:使用索引值变量,从列表的第一项开始,通过变量的递增实现对每个列表项的逐一处理。如果能理解并在自己的程序中加以运用,那么你就成为一名真正的程序员了。

然后我们讲到了操作列表的其他方式,包括一个典型的表单,用户可以在其中添加、删除列表项。这种编程方式需要开发者具备更强的抽象能力,你要假想数据已经存在,毕竟在用户输入信息之前这些列表都是空的。如果你能理解这一点,你甚至可以考虑辞掉现在的日常工作了(在美国,软件工程师是高薪职业。在Glassdoor网站发布的2015年美国十大高薪职业中,软件架构师位列第三,软件开发经理位列第四,系统分析师位列第六。这些都是与软件技术相关的职业。Glassdoor是美国一家雇员或前雇员匿名评价老板及晒薪水的网站。——译者注)。

最后我们介绍了复杂的数据结构——列表的列表。这显然是一个不太容易理解的概念,但我们利用一些固定的数据对问题进行了探索:提供多选答案的问答类应用。如果你对此以及本章的其余部分都有所掌握,就来挑战一下我们的期末考试:使用列表的列表来创建一个应用,但要求使用动态数据!提示一下,你可以使用此前的例子——第10章的“出题”应用,但要扩展原有功能,允许用户在应用中自己提供多选答案。祝你好运!

在你思考如何处理这些列表时,要知道我们的探索还没有结束。在下一章中,我们将继续讨论并重点讲解另一个版本的列表循环:对列表中的每一项实施一系列的操作。