app inventor 编程实例及指南


简介 翻译说明翻译说明 本书原著为《App Inventor—Create Your Own Android Apps》,2011年由O'Reilly出版社出版,作者 是来自旧金山大学的David Wolber教授、App Inventor发明人、MIT的Hal Abelson教授、谷歌工程师 Ellen Spertus以及Liz Looney。 本书针对零基础的编程学习者,前13章为案例教学,通过对13个简单应用的逐步讲解,学习者可以对编程 的过程及基本概念有所了解,并掌握使用App Inventor编程的方法。后面的11章是指南或手册,从专业角 度对编程的概念加以梳理,并针对Android应用特有的传感器及Web访问做了系统的介绍,使学习者对编 程的认识上升到理论的高度。 本书的英文版来自BOOK2网站,该作品采用“署名-非商业性使用-相同方式共享3.0 未本地化版本”的版 权许可协议,而译文遵从同样的4.0版国际许可协议。中文版322555现场开奖,香港马会开奖结果直播:http://www.17coding.net 作者简介作者简介 本书的主创作者David Wolber来自美国旧金山大学,是一位计算机科学专业的教授,他喜欢篮球、阅读、 政治、哲学、心理学。他使用Python语言讲授计算机科学导论,最近又在一个特殊课程中,使用App Inventor for Android讲授计算机编程入门,旨在让那些非计算机专业的学生也能创造出自己的应用软件 作品。 App Inventor 编程实例及指南 - 1 -本文档使用 看云 构建 译者简介译者简介 译者在新浪微博及博客上自称“老巫婆”,本科为物理学,做过大学教师、创业者、销售主管、程序员、 开发项目经理、IT培训教师等,喜爱游戏及编程,最近发现喜欢翻译工作,因此才有了这本《App Inventor——Create Your Own Adroid Apps》的中文译本。 背景知识背景知识 身世身世 App Inventor for Android简称App Inventor,最初是谷歌公司提供的一个开源的web应用,现在由麻省 理工学院(MIT)负责维护及运营。 历史历史 该应用由谷歌公司的Hal Abelson创建,于2010年7月12日上线运行,2010年12月15日公开发布。2011 年下半年,谷歌公司公布了应用的源码,关闭了服务器,投资创建了MIT移动学习中心。该中心负责App Inventor的后续开发及运营维护,并于2012年3月发布了App Inventor的MIT版本,此后,又于2013年12 月6日发布了App Inventor 2,并将此前的版本命名为“经典App Inventor”。 创建背景创建背景 App Inventor的创建依据是结构主义的学习理论,该理论强调主动学习,认为编写程序将成为激发强大思 想的有效工具。在此理论的影响下,从20世纪60年代开始,在MIT的Logo小组以及Logo语言发明人 Seymour Papert的积极努力下,一系列与计算机及教育有关的活动在整个美国相继发展起来,并一直持续 至今。包括乐高头脑风暴机器人(Lego Mindstorms)、StarLogo语言以及App Inventor在内的一些列与 学习有关的发明,都是这些活动的组成部分。 开发准备开发准备 开发环境开发环境 任何一台可以通过浏览器浏览器访问互联网互联网的计算机计算机,都可以作为开发的硬件环境,App Inventor的运行环境为 浏览器,请使用非IE非IE浏览器。 App Inventor 编程实例及指南 - 2 -本文档使用 看云 构建 账户申请账户申请 使用App Inventor需要用谷歌账户进行登录,以便进行个人作品的保存。因此首先访 问http://www.google.com,点击右上角“登录”,进入登录页面后,选择“创建账户”,填写相关的注 册信息之后,还需要进行今晚六彩现场开奖结果短信验证,儿童最后由父母来代为申请。账户申请成功后,会自动转为登录 状态。 首次进入App Inventor开发环境首次进入App Inventor开发环境 在浏览器中输入http://ai2.appinventor.mit.edu,进入登录页面,要求再次输入密码。输入密码后点 击“登录”,出现提示页面,告知你登录App Inventor需要使用你的谷歌账户,但不会将你的个人信息和 密码开放给App Inventor。 选择“Allow(允许)”,之后进入App Inventor的欢迎页面,要求你填写一份简短的自愿调查,以便了 解用户并改进产品。可以选择“立即参与(Take Survey Now)”、“稍后参与(Take Survey Later)”或“不参与(Never Take Sruvey)”。无论是否参与调查,最终将进入另一个欢迎页面(每次进入开 发环境之前,都会看到这个欢迎页面)。该页面的两个链接会分别打开一个窗口,说明如何设置开发的测试 设备(Android设备或模拟器),这个页面有两个信息值得注意:①模拟器和USB连接的测试设备目前只支持 苹果和windows操作系统,不支持Linux;②开发环境不久将实现对IE的支持,言外之意现在不支持IE。 点击“continue”按钮,进入最后一个欢迎页面,提示你尚未建立任何项目,并提示你如何建立新项目; 另一个提示是,如果你曾经建立过项目,但在这里看不到,推测你可能之前使用的是版本1.0,并提供了 1.0版本的链接。鼠标点击任意位置,正式进入开发环境。 开发测试之一:WiFi连接开发测试之一:WiFi连接 开发过程中可以用Android设备对应用进行实时测试,但前提是在Android设备上安装“AI伴侣”软件, 扫描下面图01的条码可实现软件的下载及安装: 图 01 用条码扫描软件扫描下载AI伴侣图 01 用条码扫描软件扫描下载AI伴侣 测试设备具体连接方法如下: App Inventor 编程实例及指南 - 3 -本文档使用 看云 构建 1. 在Android设备上运行AI伴侣,如图02; 图 02 测试连接:香港老钱庄868525,(六合娃娃上的操作图 02 测试连接:香港老钱庄868525,(六合娃娃上的操作 2. 在电脑上App Inventor开发环境中点击“connect--AI Companion>”,系统自动生成一个二维码,以 及对应的六个字母的编码,如图03所示; 图 03 测试连接:电脑上的二维码及编码图 03 测试连接:电脑上的二维码及编码 3. 在香港老钱庄868525,(六合娃娃上可以直接输入编码,并点击“connect with code”,或扫描二维码,即可建立连接; 4. 开发中的应用将在香港老钱庄868525,(六合娃娃上运行。 开发测试之二:USB连接开发测试之二:USB连接 电脑端的操作电脑端的操作 1. 下载aiStarter; App Inventor 编程实例及指南 - 4 -本文档使用 看云 构建 2. 以administration身份登录windows;将aiStarter安装在默认的C:盘上; 3. 运行aiStarter。 香港老钱庄868525,(六合娃娃端设置香港老钱庄868525,(六合娃娃端设置 1. 下载并安装AI伴侣; 2. 设置香港老钱庄868525,(六合娃娃的USB调试模式:设置->开发者选项->选中USB调试; 3. 运行AI伴侣; 连接香港老钱庄868525,(六合娃娃与电脑连接香港老钱庄868525,(六合娃娃与电脑 在App Inventor开发环境里选择“连接->USB”,稍等片刻即可。经测试Nexus S可以成功连接。 提示提示 使用USB连接进行实时测试,整个安装连接过程是否顺利,取决于很多因素,列举如下: 1. 安卓设备的型号(MIT网站提供): Nexus One Nexus S 2. 安装aiStarter时必须以administration身份登录windows; 3. 香港老钱庄868525,(六合娃娃上打开USB调试模式。 开发体验开发体验 为了解决国内用户连接App Inventor可能出现的限制,我们正在尝试将App Inventor的开发环境移植到非 谷歌的服务器上,并对环境进行了汉化。目前这项工作正在进行中,为了让用户尽早体验到使用App Inventor开发Android应用的快乐,我们将测试版本向用户开放。测试版本暂时没有做用户登录设定,您 所创建的应用对全体用户可见。点击本页面右上角的“开发体验”按钮即可进入开发环境。 屏幕切换案例屏幕切换案例 SwitchScreen.apk SwitchScreen.aia 用AI2开发的俄罗斯方块用AI2开发的俄罗斯方块 Tetris_singleListItem.apk App Inventor 编程实例及指南 - 5 -本文档使用 看云 构建 序言 消费文化为我们创造了各种娱乐、消遣有时甚至是学习的机会,但总体来看,这些活动都是被动的。当 然,我们的生活中不能没有休闲娱乐,但也不能只有这些,在满足于消费之外,还要有生产和创造带来的 乐趣:绘画、制作航模,或烤面包,同样带给我们喜悦和自豪。 今天我们使用的高科技产品(如香港老钱庄868525,(六合娃娃、平板电脑、电视等),对大多数人来说都是一个黑箱系统,内部的 运作机制复杂且难以捉摸。某些产品虽然具有绘画或录制视频等功能,但产品本身并不是一种创造型工 具,也就是说,大多数使用者无法为这些终端增添新的功能。 现在假设我们可以改变这种现状,我们可以创造性地控制我们手边的数码产品,如香港老钱庄868525,(六合娃娃;或者假如创建手 机上的应用,就像绘画或烤面包那样容易;再或者,假如这些文化消费品本身又是创作工具,那么事情会 怎样呢? 首先,让这些产品成为可被理解的开放系统,可以对其做小修小改,而不是让人迷惑的黑箱系统。当我们 能够实实在在地为他们增添某些功能时,我们将更主动、更富创造性地面对它们,也将以更深入、更有意 义的方式来使用它们。 当Hal Abelson首次跟我提起要做App Inventor的想法时,我们谈到了一个独特的动机:香港老钱庄868525,(六合娃娃可以用于教 育。他想知道,这种动机是否可以帮助学生理解计算机科学的概念。当这个成果开始在Dave Wolber教授 的课堂上进行试验时,我们开始意识到,它的能量超乎想象:App Inventor把学生从消费者转变为创造 者。能够亲手为自己的香港老钱庄868525,(六合娃娃创建应用,这让学生感到有趣和振奋!当Dave的学生创建了那个简单且功能强 大的应用“开车不发短信”时,我们真地开始想象,假如每个人,而不只是专职软件工程师,都能创建应 用,事情会怎样呢。 于是我们加倍努力,使App Inventor更易用、更有趣,也更强大(但依然简单)。我们会继续努力,因为 App Inventor仍然是一个测试产品,关于它,我们有更加令人兴奋的计划。 本书的作者是一位真正的世界级教育工作者及软件工程师。对于他们为产品App Inventor for Android所 做的开发、测试及文案整理工作,我深表谢意,当然,还有这本奇妙的书。 现在,该去发挥你的创造力,创建一个应用了! —Mark Friedman Google公司App Inventor for Android项目技术主管及经理 App Inventor 编程实例及指南 - 6 -本文档使用 看云 构建 前言 你正在一条熟悉的路上慢跑,突然被一个念头击中——下一款杀手级的移动应用。一路上你甚至不再关心 时间,只想让你的想法立即变为现实。但究竟如何下手呢?你还不是一名程序员,要想成为程序员,也得 需要几年的时间,而时间就是金钱,而且......甚至,有人可能已经把它做出来了。就这样,你的设想胎死腹 中了。 现在想象一个不同的世界,在那里,创建应用无需多年的编程经验,艺术家、科学家、人道主义者、卫生 保健工作者、律师、消防员、马拉松运动员、足球教练,以及社会各界人士都可以自己创建应用。想象一 下,在这个世界里,不必雇用程序员,就可以将想法转化为应用的原型;你可以创建自己专属的应用,利 用移动计算技术来满足你个人的需求。 这就是App Inventor的世界,谷歌公司的新型可视化编程工具,用于构建移动应用。事实证明,基于可 视“块”语言的编程方法,即便是对孩子来说,也是成功的。App Inventor大大降低了为Android香港老钱庄868525,(六合娃娃和 设备开发应用的门槛。想象一下,视频游戏里的角色变成你和你的朋友;或者一款“买牛奶”的应用,当 你在下午3点以后路过某个超市时,它会提醒你;或者一款测验应用,其实是一种别出心裁的求婚形式,发 给你的另一半“问题4:你愿意嫁给我吗?如果愿意,按下按钮发送短信。”真的有人用这种App Inventor应用来求婚,而对方居然说是! 移动今晚六彩现场开奖结果专用的块语言移动今晚六彩现场开奖结果专用的块语言 App Inventor是一个可视化,可拖拽的编程工具,用于在Android平台上构建移动应用。利用基于web的 图形化的用户界面生成器,可以设计应用的用户界面(外观),然后像玩拼图玩具一样,将“块”语言拼 在一起,来定义应用的行为。 插图0-1显示了一款应用的早期版本中使用的块语言,创作者Daniel Finnegan是一名从未学过编程的大学 生。你能说出这个应用的功能吗? 图 0-1 用App Inventor块语言来定义应用的功能图 0-1 用App Inventor块语言来定义应用的功能 这是一款短信“应答机”应用。开车时启动它,就可以对收到的短信进行自动回复。 可以看到,比起传统的程序代码,这些块语言更易于理解,因此你立即受到吸引,结合自己的实际经验, 你可能会问:能不能把收到的短信大声读出来?我可以定制我的回复吗?我能否建一个应用,像“美国偶 像”那样,让人们用短信来投票?以上所有问题的答案都是肯定的。这本书将要告诉你怎样做到。 App Inventor 编程实例及指南 - 7 -本文档使用 看云 构建 用App Inventor做什么?用App Inventor做什么? 玩玩 为香港老钱庄868525,(六合娃娃写应用充满了乐趣,而App Inventor更增加了探索和发现的乐趣。只需在Web浏览器中打开App Inventor,连上香港老钱庄868525,(六合娃娃,并像图0-1中那样把一些块拼在一起,立即就能在香港老钱庄868525,(六合娃娃上看到你的应用,并与之交互 了。于是你开始编程,你会发邮件给朋友们,让他们发短信来测试你的应用;或者用刚写好的应用来控制 一个LEGO NXT机器人;再或者拔下香港老钱庄868525,(六合娃娃,走到户外,去验证一下应用中是否正确地使用了位置传感器。 建立原型建立原型 对应用有想法了,是吗?快速地创建一个原型,而不是随手记在餐巾纸上,或干脆让它随风飘散。原型是 想法的模型,不够完整,也不够精致。用文字来表达一个想法,就像写一篇散文给朋友或爱人;而建一个 App Inventor的原型,就像写首诗歌给风险投资人。这样一来,对于移动应用的开发来说,App Inventor 就像一张电子餐巾纸。 构建个性化应用构建个性化应用 在当前的移动应用世界里,我们被迫接受那些推送过来的应用。你没抱怨过吗?我们期待个性化的应用, 或者至少让我们能够调整它的功能。使用App Inventor,可以创建贴近自己需求的应用。例如,第三章的 MoleMash(打地鼠)游戏中,有一个随机移动的地鼠,每次触碰到它都可以得分,你可以把地鼠的形象 替换成你喜欢的,比如你兄弟姐妹的照片,而不必在乎别人是否喜欢;第八章的测验应用,询问与美国总 统有关的问题,但你可以轻松地修改问题,任何话题都可以,从你最喜爱的音乐到家族史。 开发完整的应用开发完整的应用 App Inventor不只是一个原型系统或界面设计器,也可以用于创建各类完整的应用。它所使用的块语言提 供了所有基础的编程指令,如循环及条件,只是以“块”的方式来呈现。 教学教学 无论你是中学生还是大学生,App Inventor都是一个伟大的教学工具。它的伟大不仅仅是对计算机科学而 言,对与数学、物理、创业以及几乎任何其他学科来说,它都是一个了不起的工具。重要的是在创造中学 习,而不是死记公式,例如,你创建了一个寻找最近医院(或商场)的应用;又比如,用马丁•路德•金和 马尔科姆•X的视频或演讲片段来创建一个多媒体测验应用,远比写一段黑人历史的文章来得生动。我们坚 信App Inventor以及本书将成为你学习中贯穿始终的伟大工具。 为什么要用App Inventor为什么要用App Inventor 很多人说App Inventor之所以易用,是因为它可视化的操作界面,以及可拖拽的块语言。但这究竟意味着 什么呢?为什么App Inventor会易于使用? App Inventor 编程实例及指南 - 8 -本文档使用 看云 构建 无需记忆并输入指令无需记忆并输入指令 对于新手来说,编程最大的挫折在于两点,一是要输入代码,二是面对计算机弹出的令人费解的错误消 息。这种挫折让很多初学者来不及体会解决逻辑性问题的乐趣,就中途放弃了。 你有多种选择的可能性你有多种选择的可能性 在App Inventor中,组件和块被分门别类地放在不同的抽屉中,触手可得。编程的过程,就是找到这些 块,并把它们拖到程序中,来实现你预设的功能,无需记住那些指令或查阅手册。 限定块之间的匹配限定块之间的匹配 与那些挫败程序员的神秘的错误信息相比,App Inventor的块语言从一开始就排出了很多犯错的机会。例 如,某功能块要求输入数字,就无法输入文字。这虽然不能消除所有的错误,但肯定是有帮助的。 直接处理事件直接处理事件 使用传统编程语言时,程序的执行就像照着菜谱做菜一样,是顺序执行一系列的指令。但使用图形界面的 应用,特别是移动应用,事件可能随时发生(例如,接收短信或今晚六彩现场开奖结果),多数程序都不采用菜谱的形式, 取而代之的是对事件的处理。事件处理程序的工作方式是:“当某事件发生时,程序要做这件事。”在传 统的语言如Java中,你要了解类、对象,以及一种叫做侦听器的特殊对象,每个侦听器代表一个事件。在 App Inventor中,用“when”块来表示事件,像“当用户点击按钮...”或“当收到短信时...”这样的事 件。 你可以创建怎样的应用?你可以创建怎样的应用? 用App Inventor可以创建各种不同类型的应用。发挥你的想象力,就可以创建出各种既有趣又实用的应 用。 游戏游戏 人们往往从简单的应用开始,像第3章的“打地鼠”游戏,或第2章的在朋友脸上绘画的应用。随着不断进 步,可以开始按照自己的想法做一些更复杂的游戏,如吃豆人与太空侵略者等。你甚至可以使用香港老钱庄868525,(六合娃娃的传 感器,通过倾斜香港老钱庄868525,(六合娃娃让游戏中的角色移动(第5章)。 教育软件教育软件 App Inventor不仅限于制作简单的游戏,也可用于创建信息和教育类应用。第8章的“测验”应用可以帮 助学生们在考试前更好地复习,第10章的“出题”应用,允许用户为自己出一份考卷(想想那些长途旅行 的家长们会多么喜欢这个应用!)。 位置感知应用位置感知应用 因为App Inventor提供了访问GPS位置传感器的功能,因此可以构建一个定位应用——知道自己在哪儿; App Inventor 编程实例及指南 - 9 -本文档使用 看云 构建 也可以建一个停车应用,帮你记住停车位置(第7章);或者一个找人应用,在音乐会或大型会议时,显示 你的朋友或同事的位置;或者一个定制的游览应用,为你所在学校、工作场所或博物馆预置游览路线。 高科技应用高科技应用 您可以创建以下应用:扫描条码、交谈、倾听(文字识别)、播放音乐、制作音乐(第9章)、播放视频、 检测香港老钱庄868525,(六合娃娃的方向和加速度、拍照以及拨打今晚六彩现场开奖结果。从技术上讲,智能香港老钱庄868525,(六合娃娃就像一把瑞士军刀,并且谷歌的工 程师们一直致力于让App Inventor的技术更易于掌握。 短信息应用短信息应用 “开车不发短信”(第4章)只一个短信处理类应用的简单案例,还可以编写应用,定时向亲友们发送“想 念你”一类的问候,或像“广播中心”(第11章)那样的应用,帮助协调大型活动。需要这样的应用吗? 让你的朋友用短信来投票,就像“美国偶像”节目那样。这些应用都可以用App Inventor来完成。 控制机器人的应用控制机器人的应用 第12章展示了如何创建应用来充当LEGO机器人的控制器。把香港老钱庄868525,(六合娃娃当做遥控器,或者为机器人编写一 个“大脑”伴随它到处游走。机器人与香港老钱庄868525,(六合娃娃之间依靠蓝牙通信,App Inventor的蓝牙组件也可以创建类似 的应用,来控制其他的蓝牙设备。 复杂应用复杂应用 App Inventor大大降低了编程的门槛,几小时内就可以创建出很炫的高科技应用,但这门语言的功能并不 简单,它同样提供了循环、条件以及其它程序及逻辑结构,来实现逻辑较为复杂的应用。在尝试创建应用 的过程中,你会惊奇地发现这些逻辑问题是多么的有趣。 基于web的应用基于web的应用 App Inventor也提供了应用与Web之间的通信手段。可以写一个应用从Twitter或RSS订阅上抓取数据, 或者打开亚马逊书店的Web页面,通过扫描条码来查询一本书的线上价格。 什么人能够创建应用?什么人能够创建应用? App Inventor免费提供给任何人使用。它在线运行(不是桌面程序),可以在任何浏览器中访问。你甚至 不需要香港老钱庄868525,(六合娃娃:内置的Android模拟器可用于应用的测试。截至2011年1月,App Inventor已经拥有了几万 个活跃用户以及几十万个应用。 是谁创建了这些应用?他们是程序员吗?有些人是,但大多数人不是。其中最有说服力的例子是David Wolber教授的一门课程。Wolber教授是本书的作者之一。在旧金山大学(USF),App Inventor是计算 机科学通识课的一部分,主要针对商务和人文学院的学生。许多参加这门课的学生对数学是既恨又怕,而 这门课恰恰满足了学生们惧怕数学的核心需求,绝大多数学生连做梦也没想到他们会编写计算机程序。 尽管毫无经验可言,但学生们依然学会了App Inventor并成功地创建了伟大的应用。英语专业的学生首创 App Inventor 编程实例及指南 - 10 -本文档使用 看云 构建 了“开车不发短信”应用;两个通信专业的学生创建了“Android,我的车在哪儿?”;而一个国际研究 专业的学生创建了“广播中心”应用(第11章)。有一天晚上,在下班后,一个艺术专业的学生去敲 Wolber教授办公室的门,询问怎么写一个while循环,此时此刻他意识到,App Inventor已经极大地改变 了技术的格局。 媒体也开始关注这一意义非凡的变化。《纽约时报》称App Inventor为“DIY应用创建软件”;《旧金山 大学记事》撰文报到了USF学生们的工作:“Google让大正成为应用的生产者”。《无线》杂志描写了 Daniel Finnegan——“开车不发短信”的作者,并写到“Finnegan的故事有力地说明:编程普及的时代 正在来临。” 如他们所说,这只猫已经跳出来(第一章应用里有一只小猫)。现在App Inventor已经在高中开课; 在“挑战技术创新”的课后项目中(面向旧金山湾区的高中女生),在西雅图湖畔学校,以及几所大学的 入门课上,都有App Inventor的一席之地。有数千名爱好者、商人、婚介人以及能工巧匠们正漫游在App Inventor的网站和论坛上(http://appinventor.googlelabs.com/forum/)。想要开始行动吗?不必拥有编 程经验! 本书中使用的惯例本书中使用的惯例 本书采用了如下的通用惯例: 粗体绿色文本:代表程序块 斜体:表示email322555现场开奖,香港马会开奖结果直播、URL322555现场开奖,香港马会开奖结果直播、文件名、路径名,强调首次出现的术语。 等宽字体:表示Python代码,组件、属性、变量及函数的名称。  这个图标表示:测试环节以及测试说明。  这个图标表示:提示、建议或一般性注释。 如何使用本书如何使用本书 本书可作为初高中及大学课程的教科书,或有志向的开发者的入门书。全书分为两部分:第一部分是一整 套创建具体应用的教程,第二部分是App Inventor指南(手册),后者的编排更像是一部经典的编程教科 书。随着学习的不断深入,教程的复杂性也在增加,从第一章的“Hello,猫咪!”——每次点击都让小猫 发出叫声,到一个支持Web的应用:通过扫描书上的条码,就可以从Amazon Web Service上获得相关信 息(第13章)。 从理论上将,最好是按教程的顺序来学习,但如果你觉得很轻松,那么也可以跳着看。本教程手把手地教 你创建应用的每一个具体步骤,并提供块语言的截图来帮助理解,你还可以参考App Inventor指南部分的 章节,将有助于巩固对概念的理解。 App Inventor 编程实例及指南 - 11 -本文档使用 看云 构建 手头最好有本参考书,因为App Inventor的开发环境占满了电脑屏幕,留给显示教程的空间极其有限。我 们设想人们将书放在手边,并跟随教程完成每个应用的学习和创建过程。然而我们同样希望人们能在远离 电脑的情况下,花时间来系统地阅读更多App Inventor指南中的章节。 对于教师和学生来说,这本书可以作为计算机科学入门课程的教科书,或者任何一门靠创造来学习的课程 的参考资料。依我们的经验,“阅读教程→讨论→创作”这样的顺序会取得最好的效果。所以,第一步先 让学生完成教程中的一两个应用,不必要求太高,只要能按部就班地完成就好;第二步可以指定App Inventor指南中的某个章节,在课堂上进行讨论和演说,来减缓学习的进度;第三步要鼓励学生探索:让 学生按照每一章末尾的改进建议,在没有具体指导的前提下,对应用做出修改;最后,指定一个创造性的 任务,让学生对应用提出自己的想法,然后实现它们。 每章的文件及例子的完整代码都可从这里下载:http://examples.oreilly.com/0636920016632/。 致谢致谢 创建App Inventor开发工具的动机是教育,基于这样的动机,我们坚持认为,通过主动学习,程序可以成 为那些闪光的强大思想的载体。因此,App Inventor是不断发展的计算机及教育事业的一部分,这一事业 始于20世纪60年代Seymour Papert及MIT的Logo小组的努力,他们设计了众多的活动和计划,来支持计 算思维,其影响一直延续至今。 App Inventor的设计借鉴了此前的计算机辅助教育的研究成果,并立足于谷歌在线开发环境。可视化编程 框架与MIT的scratch编程语言密切相关,在具体实现上依赖于Open Blocks,它由MIT的Scheller教师培 育项目发布,并源自MIT 的Ricarose Roque的研究论文。我们感谢Scheller项目的Eric Klopfer与Daniel Wendel让Open Blocks成为现实,并感谢他们在工作中所提供的协助。将视觉化的块语言翻译为Android 上的实现的编译器使用了Kawa语言框架,而Kawa是Scheme编程语言的方言,由Per Bothner开发,并由 自由软件基金会发布,它是GNU操作系统的一部分。 作者要感谢谷歌和App Inventor团队在USF、米尔斯学院及MIT所给予我们的工作的支持以及教学上的努 力。特别感谢App Inventor技术主管Mark Friedman,项目经理Karen Parker,与工程师Sharon Perl和 Debby Wallach。 我们还需特别感谢O'Reilly的编辑们,Courtney Nash、Brian Jepson,还有Kathy Riutzel、Brian Kernighan、Debby Wallach以及Rafiki Cai,感谢他们的反馈和见解。 最后,我们还要感谢我们各自的配偶的支持:Ellen的丈夫Keith Golden,Hal的妻子Lynn Abelson,Liz的 丈夫Kevin Looney,David的妻子Minerva Novoa。新妈妈Ellen还要感谢保姆Neil Fullagar的帮助。 App Inventor 编程实例及指南 - 12 -本文档使用 看云 构建 第 1 章 Hello 猫咪 作者介绍作者介绍 Hal AbelsonHal Abelson 关于Abelson教授的故事很难用一段简短的文字来说明。他是MIT电子工程与计算机科学系的一名教 授,获得过MIT、IEEE以及ACM颁发的多种奖项,如果必须用一个词来概括他的贡献,那就是"教 育"!正如他在获奖时所说,“无论有多少获奖的理由,对我来说只有‘教育’是最有意义的,这也是 我在MIT给自己的定位:一名教师。” Abelson教授作为MIT计算机教育的领导者,执教已超过30年,至今仍担当重要角色。他与Gerry Sussman合著的教科书《计算机程序的构造和解释》改变了人们对计算的认识,并被世界范围内的高 等学校所采用(中译本由北京大学裘宗燕教授翻译)。书中淡化了具体编程语言的特殊性,而将抽象的 思维方法作为所有编程语言的共同基础。 在Abelson的教学实践中,更关注的是学习的本质:在与真实世界的交互中学习。App Inventor就是 这种思想的具体体现。作为App Inventor开发团队的领导者,Abelson力图让初学者在创作实践中体 会编程语言的内涵,并掌握编程的方法。用建构主义(Constructionism)论的发展者Papert的话 说,“生活在‘数学王国’里的人学习数学,就像法国人学习法语一样的顺理成章。” 除此之外,Abelson是开源运动的倡导者,是共创组织及自由软件基金会的创始领导人之一,也是推 动MIT开放课程的主要力量。 本章将开启你的创建应用之旅。这里介绍了App Inventor的关键要素——组件设计器及块编辑器,并手把 手地引导读者创建第一个应用:HelloPurr。在完成本章的学习之后,就可以开始创建自己的应用了。 App Inventor 编程实例及指南 - 13 -本文档使用 看云 构建 图 1-1 HelloPurr应用图 1-1 HelloPurr应用 每当搭建了新的开发环境,通常运行的第一个程序就是显示“Hello World”,来证明系统已经就绪。这 个传统可以追溯到20世纪70年代,从Brian Kernighan 在贝尔实验室使用C语言开始(Brian现在是谷歌 App Inventor团队的访问学者!)。使用App Inventor,即便是创建最简单的应用,也可以实现声音的播 放以及对屏幕触摸的响应,而不只是显示文字。想想都令人感到兴奋,那么,让我们马上开始吧。第一个 应用是“HelloPurr”(如图1-1),当你触摸这只猫时,它会发出“喵呜”声;当你摇晃它时,则将发出 嘟嘟的震颤。 学习要点学习要点 本章用到了以下组件和概念: 选择组件来创建应用:决定了应用 的外观; 为组件设定行为:做什么以及何时做; 使用组件设计器选择组件,在Android设备上,有些组件可以显示,有些则不可见; 从本地计算机加载媒体文件(声音或图像),并添加到应用中; 用块编辑器来组装程序块,以此来设定组件行为; 用App Inventor的实时测试功能对应用进行测试。你可以一边创建应用,一边在香港老钱庄868525,(六合娃娃上看到它们外观 以及运行情况; 将应用打包并下载到Android设备上。 App Inventor的开发环境App Inventor的开发环境 App Inventor的编程环境包括以下三个重要组成部分,如图1-2所示: 如图1-2A所示,组件设计器运行在浏览器中,创建应用过程中,用它来进行组件的选择,并进行属性 设置; 如图1-2B所示,像组件设计器一样,块编辑器也在浏览器中运行,用于创建组件的行为; App Inventor 编程实例及指南 - 14 -本文档使用 看云 构建 测试设备:在开发应用过程中,可以用Android设备对应用进行同步的运行与测试;如果你手边没有 Android设备,你可以使用系统中集成的Android模拟器来测试应用。 图 1-2A 组件设计器图 1-2A 组件设计器 App Inventor 编程实例及指南 - 15 -本文档使用 看云 构建 图 1-2B 块编辑器图 1-2B 块编辑器 在浏览器中访问ai2.appinventor.mit.edu即可启动App Inventor。如果你是第一次使用App Inventor, 你会看到弹出的项目(Projects)窗口,它多半是空的,因为你还没有创建过任何项目。单击页面左上角 的“ProjectStart new project…”创建一个项目,输入“HelloPurr”作为项目名称(注意不带空格), 然后单击OK。 打开的第一个窗口是组件设计器(Designer),你可以单击窗口右上角的Blocks按钮来切换到块编辑器。 在Project右侧的Connect下拉菜单中有三个可选项(三类测试设备),如图1-3所示。 图 1-3 单击“Connect”并选择“AI Companion”(应用开发伴侣,或简称AI伴侣)图 1-3 单击“Connect”并选择“AI Companion”(应用开发伴侣,或简称AI伴侣) 如果手边的Android设备可以通过WIFI访问互联网,用该设备访问Google Play,搜索MIT的 App Inventor 编程实例及指南 - 16 -本文档使用 看云 构建 AICompanion,下载、安装并启动它。然后在“Connect”下拉菜单中选择“AI Companion”,并按照 弹出窗口以及AI伴侣中的提示进行操作。除此之外,也可以使用Android模拟器来测试应用,选 择“ConnectEmulator”来加载Android模拟器,大约要等30秒钟。 如果一切正常,将会看到组件设计器窗口、块编辑器按钮,如果你选择了Emulator选项,你还可以看到模 拟器窗口(屏幕上看起来应该像插图1-2A和1-2B,但窗口中大部分是空的)。如果您还有问题,请重温网 站http://ai2.appinventor.mit.edu中的安装说明。 设计组件设计组件 我们使用的第一个工具就是(也只能是)组件设计器。组件是你用来创建应用的基本元素,就像菜谱中的 原料。有些组件非常简单,如“Label”(标签)组件,它用于在屏幕上显示文字;或者 如“Button”(按钮)组件,轻按它则引起一个动作。其它组件则要更复杂:一个绘图的“Canvas”(画 布)组件可以容纳静止图像或动画;“accelerometerSensor”(加速度传感器)组件是一种运动传感 器,它的工作原理类似于Wii 控制器,它可以检测到设备的移动或摇晃;还有的组件用于编写并发送短 信、播放音乐和视频以及从网站获取信息等等。 当你打开Designer时,其外观如插图1-4所示。 图 1-4 App Inventor的组件设计器图 1-4 App Inventor的组件设计器 Designer被划分为如下几个区域: App Inventor 编程实例及指南 - 17 -本文档使用 看云 构建 中部的白色区域称为预览窗口(Viewer),用于放置应用中所需的组件,你可以按照自己的喜好来安 排这些组件。预览窗口只能粗略地显示应用的外观,例如,与测试设备中的应用相比,在预览窗口 中,一行文字可能会在不同的地方换行。如果想看到应用的实际外观,可以将应用下载到测试设备上 (稍后我们会在“打包应用程序并下载”的部分详细介绍),或者下载App Inventor自带的模拟器。 预览窗口的左侧是组件面板(Palette),其中包含了可供选择的各类组件。该面板按类别划分为几个 部分,默认情况下,只有用户界面(User Interface)组件可见,可以通过点击其他类别的标题,如 Media(媒体)等,来查看其他组件。 预览窗口的右侧是组件列表(Components),显示了项目中的所有组件,拖动到预览窗口中的任何 组件都将显示在该列表中。目前,该项目中只有一个组件:Screen1,它代表设备的屏幕。 组件列表下方是媒体列表(Media),显示项目中的所有媒体资源(图像和声音)。本项目中尚未添 加任何媒体资源,不过很快就会添加。 最右边的部分用于显示组件的属性(Properties),在预览窗口中单击某个组件,将在Properties下方看 到该组件的一系列属性。属性描述了组件的详细信息(如,单击Label组件可以看到它的颜色、文字内容、 字体的属性。),可以修改属性值。当前显示的是屏幕(名为Screen1)的属性,包括背景颜色、背景图 像及标题等。 HelloPurr应用中需要两个可视组件(可以理解为应用中确实可见的组件):Label组件显示文字“宠物小 猫”,而Button组件中有一张猫的图片;还需要一个非可视的Sound(声音)组件,用来播放声音,如猫 叫声;还有一个AccelerometerSensor(加速度传感器)组件,用于检测设备的移动或摇晃。不必担心, 我们将按一步一步地教你使用这些组件。 创建一个Label(标签)创建一个Label(标签) 添加的第一个组件是Label: 1. 转到组件面板(Palette),单击Label(列表中的第五个),并将其拖动到预览窗口(Viewer)中。你 会看到一个矩形框出现在预览窗口中,框里写着Text for Label1。 2. 看组件设计器右侧的Properties(属性)框,它显示了Label的属性。在中间位置有一个Text属性,下面 是Label中显示的文字。将文字改为“宠物小猫”并按回车键。你会看到在预览窗口中的文字也改变了。 3. 单击BackgroundColor(背景色)下面的方框来改变Label的背景色,目前属性值为None(无背景 色),从显示的颜色列表中选择蓝色,并将Label的TextColor(文字颜色)属性改为黄色。最后将 FontSize(字号)属性改为20。 Designer的外观如图1-5所示: App Inventor 编程实例及指南 - 18 -本文档使用 看云 构建 图 1-5 应用中有了一个Label(标签)图 1-5 应用中有了一个Label(标签) 要确保Android测试设备或模拟器处于连接状态。在设计器中添加的Label会在测试设备上显示出来。在 App Inventor中,在设计器中为应用添加组件,等同于在设备上构建应用。这样一来,你可以随时看到应 用的外观,这就是所谓的实时测试,你很快就会看到,这样的测试也同样适用于在块编辑器中为组件添加 行为。 添加Button(按钮)组件添加Button(按钮)组件 HelloPurr应用中的猫咪用Button组件来实现:创建一个普通Button,然后将Button的图像更改为猫咪。 在组件设计器(Designer)的组件面板(Patatte)中单击Button(在列表的顶部),将它拖到预览窗口 (Viewer)中,置于Label下方。你会看到一个矩形按钮出现在预览窗口中。几秒钟后,该按钮就会出现 在Android设备上。试着轻击设备上的按钮,有什么反应吗?不会的,因为应用没有向Button发布命令。 这是理解App Inventor的第一个要点:添加到设计器中的组件,必须在块编辑器中创建相应的程序,才能 使组件产生某种行为(在设计器中添加一个组件之后要做这件事)。 我们希望当点击这个Button时,它会发出猫叫声,但我们希望这个button开起来相隔小猫,而不是一个普 通的方块,因此需要为button设置图片: 1. 首先,需要下载的小猫的图片,并保存在你的电脑上。从kitty.png下载名为kitty.png的图片文件(png 是与jpg、gif等类似的标准图像格式,在App Inventor中,所有这些都是有效的文件类型,与常用的标准 声音文件.mpg或.mp3一样),同时从meow.mp3下载声音文件(选择“网页另存为”来保存声音文 件)。 App Inventor 编程实例及指南 - 19 -本文档使用 看云 构建 2. 在预览窗口中点击该按钮,属性框中将显示其属性。点击中部Image属性(现在显示的是None)。显 示“Upload File…”按钮。 3. 点击“Upload File…”按钮,再单击弹出窗口中的“选择文件”按钮,浏览并选择之前下载的文件 kitty.png,然后单击确定。 4. 几秒钟之后,kitty.png被列为Button的Image属性的选项,单击“OK”。与此同时,ketty.png也出现 在设计器窗口组件列表下面的Media区域中。在测试设备中,也将显示猫咪的图片,此时按钮看起来像一 只小猫咪。 5. 注意到猫咪的图片上显示文字“Text for Button1”,我们不希望在应用中看到这些,因此将Button1 的Text属性改为“宠物小猫”一类的文字,或者干脆删除所有文字。 现在设计器看起来如图1-6。 图 1-6 应用中的一个Label和一个显示为图像的Button图 1-6 应用中的一个Label和一个显示为图像的Button 添加猫叫声添加猫叫声 我们希望当点击按钮时,应用会发出猫叫声。为此需要添加猫叫的声音文件,并通过设定Button的行为来 实现这一功能: 1. 如果meow.mp3文件尚未下载,现在点击链接meow.mp3下载; App Inventor 编程实例及指南 - 20 -本文档使用 看云 构建 2. 在左侧的组件面板中,单击Media类的标题打开Media组件列表。向预览窗口中拖放一个Sound组件。 无论你把它放在哪里,它都会出现在预览窗口的底部,并被标记为“Non-visible components(非可视 组件)”。非可视组件在应用中发挥特定作用,但不会显示在用户界面中; 3. 点击Sound1以显示其属性。设置其Source属性为meow.mp3。同猫咪图片一样,需要从电脑中加载这 个声音文件。加载完成后,Media列表中将出现kitty.png与meow.mp3两个文件。表1-1中列出了现有的 组件。 表1-1 HelloPurr中的组件表1-1 HelloPurr中的组件 组件类型组件类型 面板中分组面板中分组 命名命名 作用作用 Button User Interface Button1点击发出猫叫声 Label User Interface Label1 显示文本“宠物小猫” Sound Medi undefined Sound1播放猫叫声 添加组件行为添加组件行为 刚刚添加了Button、Label、以及Sound组件来构建我们的第一个应用,现在使用块编辑器来实现点击 Button产生猫叫声的功能。单击设计器右上角的“Blocks”按钮切换到块编辑器。 在块编辑器窗口中,可以为组件设定行为:做什么以及何时做。此处是让小猫按钮在用户点击它时播放声 音。如果把组件比作菜谱中的原料,那么块(Blocks)则相当于烹饪过程说明。 发出猫叫声发出猫叫声 在块编辑器窗口的左侧,“Blocks”标题下面,可以看到许多分属不同类别的按钮,其中包括了我们在设 计器中创建的所有组件:Screen1、Button1、Label1以及Sound1,点击它们就像打开抽屉,将看到一组 适用于该组件的可选程序块(Blocks)。点击Button1打开抽屉,显示了与Button有关的程序块,可以用 它们来设置Button的行为,最上面的Block就是Button1.Click,如图1-7所示。 App Inventor 编程实例及指南 - 21 -本文档使用 看云 构建 图 1-7 点击Button1时显示适用于Button组件的程序块(Blacks)图 1-7 点击Button1时显示适用于Button组件的程序块(Blacks) 单击标有Button1.Click的块并将其拖到工作区。注意,Button1.Click这个块上包含了when。凡是包含 when的块都被称为事件处理程序,用来定义当组件上发生了某种特定事件时,应用该做什么。在本例中, 当用户点击猫咪(其实是按钮)时发生了有趣的事情,如图1-8所示。下面我们将在程序中添加一些块,来 响应发生的事件。 App Inventor 编程实例及指南 - 22 -本文档使用 看云 构建 图 1-8 定义“Button.Click”块来响应用户的点击事件图 1-8 定义“Button.Click”块来响应用户的点击事件 在块编辑器中点击Sound1打开抽屉,拖出“call Sound1.Play”块(之前将Sound1的Source属性设置为 meow.pm3)。注意,块“call Sound1.Play”的形状恰好可以嵌入Button1.Click块中标有“do”的缺 口。App Inventor的这种设置,确保只有特定的块可以组合在一起,这样确保了连在一起的块可以协同工 作。标有call的块用来定义组件的行为。在本例中,这两个块结合在一起,构成一个单元,如图1-9,两个 块连接到一起时,你会听到“啪”的一声。 App Inventor 编程实例及指南 - 23 -本文档使用 看云 构建 图 1-9 点击按钮将播放猫叫声图 1-9 点击按钮将播放猫叫声 不同于传统的程序代码(通常像混乱的“天书”一般),在App Inventor中,Blocks拼出了行为。在本例 中,我们等于说,“嘿,App Inventor,当有人点击小猫时,播放猫叫声。”  测试:让我们通过检查来确保一切正常——每当向应用中添加了新东西,就要进行测试, 这非常重要。在测试设备上点击该按钮(或在模拟器上单击它)。你应该听到猫叫声。恭喜你,你的 第一个应用跑起来了!。 添加震动效果添加震动效果 当点击按钮时,让猫咪发出“Purr”声和“meow”声,这里用香港老钱庄868525,(六合娃娃的振动来模拟“Purr”声。这听起来 很难,其实非常容易,因为播放“meow”声音组件也可以使设备产生振动。App Inventor可以帮助你挖 掘设备的核心功能,而无需考虑它们如何实现振动。现在只需要向“Button1.click”块内添加第二个行 为: 1. 进入块编辑器,单击Sound1打开抽屉; 2. 选择call Sound1.Vibrate块,将其拖动到when Button1.Click 块内,置于call Sound1.Play块下,恰好 与原来的块吻合;如果不吻合,可尝试拖动它,使call Sound1.Vibrate块顶部的凹陷恰好与call Sound1.Play块底部的凸起相对。 App Inventor 编程实例及指南 - 24 -本文档使用 看云 构建 图 1-10 Click事件引发了播放声音及振动图 1-10 Click事件引发了播放声音及振动 3. 注意:在call Sound1.Vibrate块的右下角写着millisecs(毫秒)。块上的开放插槽表示需要插入其他 块,来设定行为的具体方式。本例中,需要设定call Sound1.Vibrate块的振动时长。以毫秒(千分之一 秒)为单位输入时长,毫秒是多数编程语言中惯用的时长单位。如果想让设备振动半秒钟,需要输入数字 块“500”。打开Math(数学)抽屉,其中的第一个块是“0”,这就是数字块,如图1-11所示。 App Inventor 编程实例及指南 - 25 -本文档使用 看云 构建 图 1-11 打开Math抽屉图 1-11 打开Math抽屉 4. 点击“0”块,蓝色的“0”块留在了工作区,如图1-12所示。 App Inventor 编程实例及指南 - 26 -本文档使用 看云 构建 图 11-12 选择一个数字块(0为默认值)图 11-12 选择一个数字块(0为默认值) 5. 点击数字0,输入新值“500”,如图1-13所示。 图 11-13 将默认值0改为500图 11-13 将默认值0改为500 6. 将“500”数字块插入call Sound1.Vibrate块右侧的插槽内,如图1-14所示。 图 1-14 将数字块500插入插槽图 1-14 将数字块500插入插槽  测试:试试吧!点击设备上的按钮,你会感觉到半秒钟的嘟嘟声(震动)。 摇晃香港老钱庄868525,(六合娃娃摇晃香港老钱庄868525,(六合娃娃 现在来添加最后一项,在Android设备上实现一个很酷的功能:摇晃设备时发出猫叫声。为此要用到 AccelerometerSensor(加速度传感器)组件,它可以检测到设备的摇晃或移动。 1. 在设计器中,展开组件面板中的传感器(Sensors)分类,拖出一个AccelerometerSensor(加速度传 感器)组件。不必介意把它放到哪里,像任何非可视化组件一样,无论你把它放在预览窗口的什么地方, 它都会落到预览窗口底部的“非可视组件”区域。 2. 摇晃设备的事件需要与单击按钮事件分开处理。这意味着需要一个新的事件处理程序。进入块编辑器, 打开AccelerometerSensor1抽屉,拖出AccelerometerSensor1.Shaking块。 3. 像点击按钮时播放声音一样,将Sound1.Play块插入AccelerometerSensor1.Shaking插槽,摇动设备 试试看。图1-15显示了完整的HelloPurr应用中所用的块。 App Inventor 编程实例及指南 - 27 -本文档使用 看云 构建 图 1-15 HelloPurr应用中的块图 1-15 HelloPurr应用中的块 将应用打包以供下载将应用打包以供下载 App Inventor是一种云计算工具,这意味着你用谷歌的在线服务器存储你的应用。所以当关闭App Inventor,再重新返回时,你的应用还在;你不必在个人电脑上保存任何东西,像Word文件或音乐文件那 样。此外,如果连接了测试设备,无需向设备下载任何文件,就可以轻松地测试应用(称为实时测试); 但问题是,如果设备与App Inventor断开连接,那么应用将停止运行。由于从未在设备上安装过应用,因 此无从找到应用的图标。 可以将应用下载并安装到Android设备上,以便在不连接计算机时,应用也能运行。首先,确保设备允许 从Android Market以外的地方下载应用。具体做法是:在设备上选择“设置→安全”,并勾选Unknown Source(未知来源)一项。然后回到App Inventor设计器中,单击BuildApp(provide QR code for .apk),此时窗口中出现一个进度条,这个过程大约需要一分钟。进度消失后,几秒钟后,会显示打包应 用的QR码。用条码扫描软件获取QR码之后,设备会提示输入谷歌帐户的密码(如果设备之前登陆过 google账户,此步骤不会出现);密码输入后,应用被下载到设备上。如果你的设备中没有条码扫描软 件,去Google Play搜索并下载安装一个。下载完成后,会询问你是否安装,请单击安装。(如果设备上 已经安装了MIT AI2 Companion,用其中的条码扫描功能,可以顺利实现应用的下载安装。) 安装完成后,设备上出现HelloPurr应用的图标——这就是我们刚刚创建的应用,点击让它开始运行。(请 确保运行的是新安装的应用,而不是之前与App Inventor连接的应用。)现在,你可以断开连接甚至重新 启动设备,并删除App Inventor中的所有应用,而新应用依然存在。 了解这一点很重要:打包的应用已经与App Inventor中的项目分离。你还可以像之前一样,继续在App Inventor中完善你的应用,并在测试设备上使用AI伴侣做实时测试,但这些都不会改变已经安装在设备上 的应用。如果在App Inventor中对应用进行了修改,那么修改结果必须重新打包,并下载安装新版本来替 换设备上的原有版本。 马上用Android设备下载安装HelloPurr应用吧,这样,你就可以与家人和朋友一起分享了! App Inventor 编程实例及指南 - 28 -本文档使用 看云 构建 分享应用分享应用 有两种方式可以分享应用:第一,分享可运行的应用。在App Inventor项目中单击BuildApp(save .apk to my computer),此操作将扩展名为apk的文件保存到电脑。将apk文件上传到web上,让其他人 可以下载并安装。需要强调的是,设备的安全设置中“未知来源”一项必须选中,才能安装来源于 Android Market之外的应用。 第二,与其它App Inventor开发者共享应用的代码块:点击ProjectMy Projects,选中要共享的应用 (本例中是HelloPurr),选择project→Export selected project (.aia) to my computer。此操作将扩展 名为aia的文件(HelloPurr.aia)保存到电脑上默认的下载文件夹中。可以用电子邮件把文件发给其他人, 他们打开App Inventor,选择Project→Import project,并选择.aia文件。这样,使用者获得了该应用的 完整备份,对此备份的任何修改,都不会影响原有版本。 共享应用的过程非常简单,更多有趣的作品在网站gallery.appinventor.mit.edu的分享社区中。 改进改进 现在,应用已经完成,并可以随时运行它(或许还能下载与人分享),也许还会感到什么地方有些欠缺。 来看看下面的问题,并思考如何解决它们。随着学习的进展你会发现,通常是先创建一个应用,之后设法 改进、完善它,并重新回到程序中来实现你的新想法。不必担心,这很好,这正是一名优秀开发者的必经 之路! 当摇晃设备时,猫的叫声听起来有点儿奇怪,好像有回声。这是因为在1秒钟内,加速度传感器多次触 发摇动事件,所以猫叫声是重叠的。你会发现Sound组件有一个属性Minimum interval(最小间 隔),它决定了两次声音播放之间的时间间隔。当前设置为400毫秒(约半秒钟),这个间隔小于单次 猫叫的持续时间(500毫秒)。通过改变播放的最小间隔,可以改变声音的重叠。 如果你带着安装了应用的香港老钱庄868525,(六合娃娃到处走动,每当你突然移动时,设备就会发出猫叫声,这可能让你觉得 尴尬。通常Android应用会保持在运行状态,即使你不去管它们,应用程序与加速度传感器之间的通信 也不会间断,因而猫叫声也会相继传来。要想真正退出程序,需要呼出HelloPurr应用并按下设备上的 菜单按钮,会呼出两个选项,其中stop this application用来停止并完全关闭应用。 小结小结 以下是本章中涵盖的内容: 创建应用的过程:在组件设计器中选择组件,并在块编辑器中设定它们的行为——做什么及何时做; 有些组件是可见的,有些则不可见。可见组件会出现在应用的用户界面中;不可见组件执行像播放声 音这类的事情; 通过在块编辑器中组装“块”来定义组件的行为。先拖出一个像when Button1.doClick这样的事件处 理程序,然后将call Sound.Play这样的命令块嵌入其中。这样,当用户点击该按钮时,块 App Inventor 编程实例及指南 - 29 -本文档使用 看云 构建 Button1.Click中的所有块(命令)都将被执行; 有些块(命令)需要附加特定信息才能起作用。例如震动就必须设定振动的毫秒数。这些值被称为参 数。 数字块用来表示数字。你可以将这些数字块插入到需要参数的命令块中。 App Inventor提供传感器组件,加速度传感器(Accelerometer Sensor)可以检测到设备的移动。 你可以将创建完成的应用打包并下载到香港老钱庄868525,(六合娃娃上,它们将独立于App Inventor而运行。 扩展阅读扩展阅读 MITMIT 麻省理工学院(Massachusetts Institute of Technology)麻省理工学院(Massachusetts Institute of Technology) MIT成立于1861年,校园坐落与美国马萨诸塞州的剑桥市(Cambridge),是一所私立的研究型大学。1934年 入选美国大学学会(AAU:Association of American Universities),是全球最负盛名的学府之一。 MIT校训MIT校训 Mind and Hands(手脑并用) MIT的使命MIT的使命 在科学、技术及其他学术领域中,推动知识的进步,培养教育学生,力求在21世纪更好地服务于国家及世 界。 App Inventor 编程实例及指南 - 30 -本文档使用 看云 构建 MIT致力于知识的形成、传播以及保护,并利用这些知识,与全人类共同面对未来世界的巨大挑战。MIT利 用各种各样的校园社团,来激发学生的智慧,并提供相应的支持,将严谨的治学态度与积极地探索精神相 结合,并将其融入到学生的培养教育之中。力争发展每位社团成员的能力与热情,使他们着眼于整个人类 的进步,并进行富有远见、创造性及富有成效地工作。 EECSEECS 电子工程与计算机科学系(Electrical Engineering and Computer Science Department)电子工程与计算机科学系(Electrical Engineering and Computer Science Department) EECS格言EECS格言 EECS无处不在(EECS is everywhere)。 我们兼具科学的严谨、工程的动力以及发现的震撼。我们的学生改变着世界。 IEEEIEEE 电气电子工程师学会(Institute of Electrical and Electronics Engineers),创立于1963年,是世界上最大的 国际性专业技术组织之一,拥有来自175个国家的36万名会员。其定位于科学和教育,工作方式有科学期 刊、学术会议、工业标准的开发、课程授权等。常见的IEEE标准如快速以太网标准:IEEE 802.3u。 ACMACM 计算机协会(Association of Computing Machinery),创立于1947年,是世界性的计算机从业员专业组 织,面向研究与教育,工作方式为专业期刊、兴趣小组及设立奖项。主要期刊为《计算机学会通讯 App Inventor 编程实例及指南 - 31 -本文档使用 看云 构建 (Communications of the ACM)》,在全球有35个兴趣小组,设立了8个奖项,其中的图灵奖相当于计算机 界的诺贝尔奖。 OEROER 开放教育资源(Open Educational Resources):将文档、媒体等实用的教学资源向以教学、评估及研究为目 的使用者免费开放。这是教育资源开发的基本特征,它源于人们试图抑制知识商品化的愿望。MIT作为开 放运动的先锋,开放了许多优秀的视频课程,英语好的同学真是有福了。 译者提示译者提示 本章非常重要,一个简单的例子,贯穿了一个完整的开发过程,从界面设计到代码编制,从开发环境到测 试设备,从普通媒体(图片)到香港老钱庄868525,(六合娃娃特有的摇晃、震动。对于初学者,这是一个幸福的开端 资源下载资源下载 kitty.png meow.mp3 HelloPurr.aia AI伴侣 App Inventor 编程实例及指南 - 32 -本文档使用 看云 构建 第 2 章 油漆桶 本章介绍Canvas组件,用它来生成简单的二维(2D)图形,目标是创建一个PaintPot(油漆桶)应用, 让用户在香港老钱庄868525,(六合娃娃屏幕上绘制图画,并让用户用香港老钱庄868525,(六合娃娃给自己拍照,然后在自己的照片上绘图。回顾历史,早在 20世纪70年代,PaintPot是最早运行在个人电脑上的应用之一,目的是为了证明个人电脑的潜力。那时 候,开发这样一款简单的绘图应用是一项极其复杂的工作,而且绘图效果也略显粗糙。但现在,使用App Inventor,任何人都可以快速地创建一个有趣的绘图应用,这也是创建2D游戏的起点。 如图2-1,油漆桶应用将实现下列目标: 用手指点取颜色并绘图; 用手指在香港老钱庄868525,(六合娃娃屏幕上画线; 用手指触碰香港老钱庄868525,(六合娃娃屏幕画圆点; 点击按钮来擦净屏幕; 点击按钮来改变绘制圆点的大小; 用相机拍摄照片,并在照片上画图。 App Inventor 编程实例及指南 - 33 -本文档使用 看云 构建 图 2-1 油漆桶应用图 2-1 油漆桶应用 学习内容学习内容 本章涵盖了以下内容: 使用Canvas组件来绘制图画; 处理屏幕上的触摸及拖拽事件; 使用arrangement组件来控制屏幕的外观; 使用带有参数的事件处理程序; 定义变量,来保存某些状态,如用户绘制的圆点的大小。 准备开始准备开始 首先检查测试用的Android设备是否已经为使用App Inventor做好了准备: Android设备中已经安装了“AI伴侣”; 香港老钱庄868525,(六合娃娃的WiFi连接已经打开; 再访问App Inventor网站。新建项目“PaintPot”,点击“Connect->AICompanion”,并按照提示操 作,连接测试设备。 在正式开始之前,在组件设计器右侧的“属性”面板中,将“Screen1”的“Title”属性修改为“油漆 桶”。在测试设备上可以立即看到这一改变:应用的标题栏将显示“油漆桶”。 这样做是否会混淆了项目名称与屏幕标题呢(在英文版书中,将Title改为“PaintPot”,与项目同名,因此 才有此疑问,对中文读者来说不存在这个疑问。——译者注)?别担心!在App Inventor中有三个非常重 要名称: 项目名称:同时也是应用发布时所使用的名称。提示:想修改项目名称,可以点击Project->Save App Inventor 编程实例及指南 - 34 -本文档使用 看云 构建 project as,可以将原有项目赋予新的名称,同时原有项目依然得以保留; 组件名称:一般的组件名称都可以修改,但Screen1例外,在当前版本中不能修改它的名称; 屏幕标题:出现在设备的标题栏中,是Screen组件的Title属性,默认值是Screen1,如第一章 HelloPurr中所见,可以随意修改它,如我们刚才将其改为“油漆桶”。 设计组件设计组件 创建“油漆桶”应用需要以下组件: 三个Button组件:用来选择画笔颜色:红、蓝或绿,放在HorizontalArrangement组件中; 一个Button组件用来充当橡皮; 另外两个Button组件用来改变画笔的大小; 一个Canvas组件,充当画布。Canvas具有BackgroundImage属性,我们将其设置为第一章 HelloPurr中的kitty.png,稍后还可以将背景图片设置为用户拍摄的照片。 创建颜色按钮创建颜色按钮 首先按照以下提示创建三个颜色按钮: 1. 拖一个Button组件到预览窗口,设置其Text属性为“红”,BackgroundColor属性设为红色; 2. 在组件列表中选中Button1(可能已经被选中),点击Rename按钮将组件名称改为RedButton。注意 组件名称中不允许有空格,因此通常将组件名称中每个单词的首字母大写。 3. 同样,创建另外两个按钮,分别命名为BlueButton和GreenButton,将它们垂直地放在RedButton下 方。对照图2-2,检查一下你的操作结果。 图 2-2 创建了3个按钮的预览窗口图 2-2 创建了3个按钮的预览窗口 注意:在项目中,建议为组建起一个有意义的名称,而不是像第一章那样采用默认名称。有意义的名称增 加了程序的可读性,尤其是在切换到块编辑器时,将有助于区分不同的组件。本书中,采用惯用的骆驼命 名法(如RedButton),即多单词无空格的首字母大写命名方式。 App Inventor 编程实例及指南 - 35 -本文档使用 看云 构建  测试:如果你还没有点击“Connect”来连接测试设备,那么做好连接,然后检查一下应 用在设备(如果已经连接)上的表现。 使用Arrangement组件改善布局使用Arrangement组件改善布局 现在三个按钮排成一列纵队,我们希望它们能排成一行,如图2-3所示,使用HorizontalArrangement组 件来实现组件的水平排列: 1. 在组件面板的Layout类中拖出HorizontalArrangement组件,放在按钮下方; 2. 在属性面板中,设置HorizontalArrangement的width属性为“Fill Parent”(充满父容器),以便在 水平方向上占满整个屏幕; 3. 将三个按钮移动到HorizontalArrangement中。注意,当你拖拽按钮时,会看到一条蓝色竖线,提示按 钮将会被放置在什么地方。 图 2-3 在水平布局组件内的三个按钮图 2-3 在水平布局组件内的三个按钮 此时查看组件列表,你会发现三个按钮缩进排列在HorizontalArrangement项下,以显示它们现在是次一 级的组件。同时注意到所有组件都缩进排在Screen1项下。  测试:在测试设备的屏幕上,你会看到三个按钮排列成一行,尽管看起来与预览窗口中略 有不同。如,在预览窗口中可见的HorizontalArrangement周围的轮廓线,在测试设备上则不可见。 通常采用布局组件来创建简单的垂直、水平或表格布局,也可以通过逐级插入(或嵌套)布局组件来创建 更加复杂的布局。 添加Canvas(画布)添加Canvas(画布) Canvas像一块画布,用户可以在上面绘画(画圆、画等)。添加一个Canvas,并用第一章中的kitty.png 作它的背景图片(设置BackgroundImage属性),具体步骤如下: 1. 打开组件面板中的Drawing and Amination(绘画与动画)类,将Canvas组件拖到预览窗口中,改名 为DrawingCanvas,Width设为“Fill parent”,Height设为300pixels; 2. 如果你已经完成了第一章的课程,那么文件kitty.png已经下载;如果没有,请在这里下载kitty.png。 App Inventor 编程实例及指南 - 36 -本文档使用 看云 构建 3. 将DrawingCanvas的BackgroundImage设置为kitty.png:在设计器的属性面板 中,BackgroundImage的默认值为None,点击None及Upload File来添加kitty.png文件; 4. 将DrawingCanvas的PaintColor属性设置为red,以便当用户刚启动应用但尚未点击颜色按钮时,画笔 为红色。对照图2-4检查一下你的操作。 图 2-4 背景图片设为kitty.png的DrawingCanvas组件图 2-4 背景图片设为kitty.png的DrawingCanvas组件 设置底部按钮及照相机组件设置底部按钮及照相机组件 1. 从组件面板中拖出第二个HorizontalArrangement,放在canvas下方,再拖两个Button并置于屏幕底 部的HorizontalArrangement中;将第一个按钮改名为TakePictureButton,Text属性设为“拍照”;第 二个按钮改名为WipeButton,Text属性设为“清除”; 2. 再拖两个Button组件到HorizontalArrangement中,放在“清除”按钮后面; 3. 两个Button分别命名为BigButton、SmallButton,Text属性分别设为“大圆”、“小圆”; 4. 从组件的Media类中拖出一个Camera组件放在预览窗口中,它将落在非可视组件区。 到此为止,应用外观已经设置完成,如图2-5所示。 App Inventor 编程实例及指南 - 37 -本文档使用 看云 构建 图 2-5 油漆桶应用的完整用户界面图 2-5 油漆桶应用的完整用户界面  测试:在设备上检查一下应用,猫的图片是否在顶部的一行按钮的下方?底部的按钮是否 正常显示? 为组建添加行为为组建添加行为 下一步将定义组件的行为。编写一个绘画程序的难度似乎是难以想象的,但无疑App Inventor已经承担了 大部分繁重的工作:这里有易于使用的块语言,不但可以处理用户的触摸及拖拽事件,也可以实现绘画及 拍照功能。 Canvas组件具有Touched及Dragged事件,你可以针对DrawingCanvas.Touched(触碰)事件编程,并 调用DrawingCanvas.DrawCircle(画圆)程序;也可以对DrawingCanvas.Dragged(拖拽)事件编程来 调用DrawingCanvas.DrawLine(画线)程序。然后对按钮编程,来设置DrawingCanvas.PaintColor(画 笔颜色)属性、清除DrawingCanvas,以及将DrawingCanvas的背景图片修改为照相机拍摄的图片。 添加触摸事件,绘制一个圆点添加触摸事件,绘制一个圆点 首先设置触碰行为:当用户触碰DrawingCanvas时,在接触点绘制一个圆点: 1. 在块编辑器中,打开DrawingCanvas抽屉拖出DrawingCanvas.Touched块,该块有三个参数x、y及 touchedSprite,如图2-6所示。这些参数提供了接触点的位置信息; App Inventor 编程实例及指南 - 38 -本文档使用 看云 构建 图 2-6 带有接触点位置信息的Toughed事件图 2-6 带有接触点位置信息的Toughed事件  提示:在第一章HelloPurr应用中已经熟悉了Button.Click事件,但对Canvas事件还很陌 生。Button.Click事件的发生很简单,不附带任何其他信息;但有些事件则不然,它们附带了与事件 有关的“参数”信息。DrawingCanvas.Touched事件中的x、y代表接触点在DrawingCanvas中的坐 标,而touchedSprite代表接触点所碰到的DrawingCanvas中的对象(在App Inventor中称作sprite —精灵),但在第三章之前我们不会用到它。我们将利用接触点的xy坐标来绘制圆点。 2. 从DrawingCanvas抽屉中拖出DrawingCanvas.DrawCircle命令块,放在DrawingCanvas.Touched事 件处理程序中,如图2-7所示; 图 2-7 用户触摸画布时,应用绘制一个圆点图 2-7 用户触摸画布时,应用绘制一个圆点 在DrawingCanvas.DrawCircle块的右侧有三个插槽,需要填入三个参数:x、y、r。其中x、y用于指定绘 制圆形的位置,r用于指定圆的半径。在屏幕左下角带感叹号的黄色警告显示数字“1”,表示需要填满这 些插槽。从图中看到,有两组xy,这里要区分清楚:DrawingCanvas.Touched事件中的xy表示接触点位 置(已知);而DrawingCanvas.DrawCircle命令块的xy插槽,用于设定绘制圆形的位置(待定)。我们 恰好要在用户的接触点绘制圆形,因此DrawingCanvas.Touched事件中的xy值,可以作为 DrawingCanvas.DrawCircle的x、y参数,插入到插槽中。  提示:可以从“when”块中提取事件的参数值,将鼠标悬停在参数上,将呼 出“get”及“set”块。可以将“get x”块拖出并插到x插槽中,作为DrawCircle命令的x值。如图2- 8所示。 App Inventor 编程实例及指南 - 39 -本文档使用 看云 构建 图 2-8 读取事件参数:从DrawingCanvas.Touched事件中拖出“get x”块图 2-8 读取事件参数:从DrawingCanvas.Touched事件中拖出“get x”块 3. 将鼠标悬停在x、y参数上可以唤出“get”块,将“get”块插入到DrawingCanvas.DrawCircle的插槽 中,如图2-8、2-9所示; 图 2-9 已经设定了圆的位置(x,y),尚未指定圆的大小图 2-9 已经设定了圆的位置(x,y),尚未指定圆的大小 4. 下面来设定圆的半径r。长度的单位是pixel(像素),是屏幕上能够绘制的最小的点。设r = 5:点击屏 幕的空白区域,输入5然后回车(自动创建数字块“5”)并将其插入插槽r中。再看屏幕左下角的黄色三角 形,数字由1变为0,因为所有插槽都被填满了。图2-10显示了DrawingCanvas.Touched事件处理程序最 终的样子。 图 2-10 当用户触碰DrawingCanvas时,将在(x,y)点绘制一个半径为5的圆形图 2-10 当用户触碰DrawingCanvas时,将在(x,y)点绘制一个半径为5的圆形  提示:在块编辑器中输入5然后回车,这种操作叫做输入块(typeblocking)。块编辑器 会根据你输入的字符,显示与该字符相匹配的一系列块;如果输入的是数字,那么将创建一个数字 块。  测试:看看测试设备上都有什么。触碰 DrawingCanvas,手指碰过的地方会留下一个圆 点。如果在设计器中将DrawingCanvas.PaintColor属性设置为红色,那么圆点也是红色(否则应该是 默认的黑色)。 App Inventor 编程实例及指南 - 40 -本文档使用 看云 构建 添加画线的拖拽事件添加画线的拖拽事件 下面添加拖拽事件处理程序,先看一下触碰(Toughed)事件与拖拽(Dragged)事件的区别: 触碰事件:手指在DrawingCanvas(画布)上放下再抬起,其间手指没有移动。 拖拽事件:手指在DrawingCanvas(画布)上放下,手指与屏幕保持接触并移动。 在绘图程序中,手指在屏幕上拖动,沿着手指移动的路径,将绘制出一条巨大的曲线,因为这条曲线实际 上由数百个微小的线段构成:手指每次微小的移动,都将绘制一个微小的线段。 1. 从DrawingCanvas抽屉中拖出DrawingCanvas.Dragged事件处理程序块,如图2-11所示; DrawingCanvas.Dragged事件携带了以下参数: StartX、StartY:手指开始拖动时所在的位置(整个曲线的起点); currentX、currentY:手指的当前位置(微小线段的终点); prevX、prevY:手指的上一个位置(微小线段的起点); draggedSprite:布尔值,如果用户直接拖动一个图片,则其值为真。本章不会用到这个参数。 图 2-11 比起Toughed事件,Dragged事件携带了更多参数图 2-11 比起Toughed事件,Dragged事件携带了更多参数 2. 从DrawingCanvas抽屉中拖出DrawingCanvas.DrawLine块,插入DrawingCanvas.Dragged块中,如 图2-12所示。 图 2-12 添加画线功能图 2-12 添加画线功能 DrawingCanvas.DrawLine块有四个参数,两点确定一线:设(X1,Y1)为起点,(X2,Y2)为终点。 你能确定每个参数中需要插入什么值吗?记住,当手指在DrawingCanvas上拖动时,拖动事件将被调用很 多次:在应用中,手指的每次移动都会绘制出一个微小线段,从(Prevx, prevy)到(currentX, currentY)。现在把它们填入DrawingCanvas.DrawLine块。 App Inventor 编程实例及指南 - 41 -本文档使用 看云 构建 3. 拖出“get”块来充当画线的参数。将get prevX与get prevY分别插入到x1和y1插槽;而get currentX 与get currentY插入到x2和y2插槽,如图2-13所示。 图 2-13 用户在屏幕上拖动手指,应用就在前一位置与当前位置之间画一条微小线段图 2-13 用户在屏幕上拖动手指,应用就在前一位置与当前位置之间画一条微小线段  测试:在设备上测试一下刚刚设定的行为:在屏幕上随意拖动手指,画出线段及曲线;触 碰屏幕画出一个圆点。 添加按钮事件处理程序添加按钮事件处理程序 应用已经实现了画线功能,但现在只能画红线。下面添加颜色按钮的事件处理程序,用户可以改变画笔的 颜色;同样设置清除按钮WipeButton,以便用户可以清除画面并重新开始。 在块编辑器中: 1. 展开左侧块的(Blocks)列表; 2. 打开RedButton抽屉,拖出RedButton.Click块; 3. 打开DrawingCanvas抽屉。拖出set DrawingCanvas.PaintColor块(可能需要滚动块的列表以便在抽 屉里找到它),并把它放在RedButton.Click块“do”的位置; 4. 打开Colors抽屉,拖出红色块,将其插入set DrawingCanvas.PaintColor块的插槽; 5. 重复步骤2-4,设置蓝色和绿色按钮; 6. 最后设置WipeButton按钮。从WipeButton抽屉中拖出WipeButton.Click块。再从DrawingCanvas抽 屉里拖出DrawingCanvas.Clear块,并将其放在WipeButton.Click块中。确认所有块显示如图2-14所示。 App Inventor 编程实例及指南 - 42 -本文档使用 看云 构建 图 2-14 单击颜色按钮改变DrawingCanvas的画笔颜色;单击清除按钮清空屏幕图 2-14 单击颜色按钮改变DrawingCanvas的画笔颜色;单击清除按钮清空屏幕 让用户拍照片让用户拍照片 App Inventor应用可以与Android设备的强大功能进行交互,包括相机功能。为了增加应用的趣味性,用 户可以将绘图背景设置为他们用相机拍摄的照片。 1. Camera组件有两个关键的块:Camera.TakePicture块用来启动设备上的拍照程序;拍照完成将触发 Camera.AfterPicture事件。在Camera.AfterPicture事件处理程序中,可以将刚刚拍摄的照片设置为 DrawingCanvas.BackgroundImage。打开TakePictureButton抽屉并拖出TakePictureButton.Click事件 处理程序; 2. 从Camera1抽屉拖出Camera1.TakePicture放在TakePictureButton.Click事件处理程序中; 3. 从Camera1的抽屉中拖出Camera1.AfterPicture事件处理程序; 4. 从DrawingCanvas抽屉拖出set DrawingCanvas.BackgroundImage块放在Camera1.AfterPicture事 件处理程序中; 5. Camera1.AfterPicture事件有一个名为image的参数,代表刚刚拍摄的照片,将从 Camera1.AfterPicture块中得到的get image块插入DrawingCanvas.BackgroundImage块。 所有的块如图2-15所示 。 图 2-15 拍完的照片被设置为DrawingCanvas的背景图片图 2-15 拍完的照片被设置为DrawingCanvas的背景图片  测试:在设备上点击“拍照”按钮并拍摄照片,猫的图片变成了你拍的照片。你可以在自 己的照片上进行绘画。(用Wolber教授的照片绘画是学生们的一大乐事,如图2-16。)(Wolber教 App Inventor 编程实例及指南 - 43 -本文档使用 看云 构建 授是本书的作者之一。) 改变画笔大小改变画笔大小 图 2-16 带有Wolber教授涂鸦照片的PaintPot应用图 2-16 带有Wolber教授涂鸦照片的PaintPot应用 在DrawingCanvas上画圆点,其大小由DrawingCanvas.DrawCircle块中参数r决定。改变r值可以改变圆 点的大小。试试看将5改为10,然后在测试设备上查看结果。 另一个问题是,无论开发者如何设置参数r,用户都只能用这个固定的尺寸。如何让用户来改变圆点的大小 呢?为此我们来修改程序:当用户点击“大圆”按钮时,圆点半径设为8,当点击“小圆”时半径设为2。 我们要用不同的半径画圆,但应用怎么知道我们要用哪个值呢?必须通知应用我们选定的值,而应用必须 以某种方式记住(或保存)这个值,这样才能在需要的时候使用它。之前我们所使用的值,要么设定为属 性(如画笔颜色),要么用固定的数字块(如画笔大小),现在应用需要记住一些属性之外的、不是固定 不变的东西,这就需要定义一个变量。变量是一个存储单元,可以把它想象成一个容器,里面存储着可变 的数据,如画笔的大小(有关变量的详细信息,请参见App Inventor指南第16章)。 让我们先来定义一个变量dotSize: 1. 在块编辑器中,从Variables(变量)抽屉中拖出一个initialize global name to块。将“name”改 为“dotSize”; 2. 请注意,initialize global dotSize to块有一个开放的插槽,可以在这里设定变量的初始值,或者说是应 用启动时的默认值(编程术语称为“初始化变量”)。在本应用中,用数字块2来初始化变量dotSize, (创建块“2”的方法有两种:在空白区直接输入“2”然后回车;或从Math抽屉中拖出“0”块,将0改 为2。)将其插到initialize global dotSize to块的插槽中,如图2-17所示。 图 2-17 将dotSize变量的初始值设为2图 2-17 将dotSize变量的初始值设为2 使用变量使用变量 App Inventor 编程实例及指南 - 44 -本文档使用 看云 构建 下一步,我们要修改DrawingCanvas.Touched事件处理程序,将其中DrawingCanvas.DrawCircle块的参 数r的固定值用变量dotSize来代替。(我们先将dotSize的初始值设定为“固定”的2,但稍后我们将改变 dotSize的值,并同时改变画笔的大小。) 1. 从initialize global dotSize to块中拖出一个get global dotSize块,用它来提供变量的值; 2. 转到DrawingCanvas.Touched事件处理程序,将数字块“5”拖出插槽并扔进垃圾桶,用get global dotSize块来替换(见图2-18)。当用户触摸到DrawingCanvas时,应用将根据dotSize的大小来确定圆 点的半径。 图 2-18 画笔的大小取决于变量dotSize中保存的值图 2-18 画笔的大小取决于变量dotSize中保存的值 修改变量值修改变量值 现在变量魔法登场,变量dotSize允许用户选择画笔的大小,而事件处理程序也将以dotSize为半径来画 圆。通过设计SmallButton.Click和BigButton.Click的事件处理程序来实现此功能: 1. 从SmallButton抽屉中拖出SmallButton.Click事件处理程序;再从Variables抽屉中拖出一 个“set”块,下拉选择global dotSize,并将其插入SmallButton.Click块;最后,创建一个数字 块“2”,并将其插入set global dotSize块。 2. 创建另一个类似的BigButton.Click事件处理程序,设置画笔大小为8。这两个事件处理程序显示在块编 辑器中,如图2-19所示。 图 2-19 点击SmallButton及BigButton按钮改变画笔大小,之后将以该尺寸绘制图形图 2-19 点击SmallButton及BigButton按钮改变画笔大小,之后将以该尺寸绘制图形  提示: get/set global dotSize 之中的“global”(全局)指的是该变量适用于程序中所 有的事件处理程序(全局)。与global相对的是“local”(局部)变量,适用于程序的特定部分; App Inventor 2中添加了此项功能,第12章首次使用。  测试:尝试单击“大圆”、“小圆”按钮,然后在DrawingCanvas上触碰,所绘圆点的大 小是否不同?画线呢?线没有变化,因为只有DrawingCanvas.DrawCircle块使用了变量dotSize。在 App Inventor 编程实例及指南 - 45 -本文档使用 看云 构建 此基础上,考虑修改块的设置,以使画笔的大小,对画线也同样有效。注意:DrawingCanvas有一 个“LineWidth(线宽)”的属性。 油漆桶的完整应用油漆桶的完整应用 图2-20中显示了完整的油漆桶应用。 图 2-20 油漆桶应用中块的最终设置图 2-20 油漆桶应用中块的最终设置 改进改进 可以考虑做以下改进: 1. 用户界面中没有显示当前的状态信息(如画笔大小或颜色),只能通过画图来得知这些信息。修改应 用,向用户显示当前的状态信息; 2. 让用户在TextBox组件内输入画笔的尺寸,这样一来,除了2和8之外,他还可以将其更改为其他数值。 App Inventor 编程实例及指南 - 46 -本文档使用 看云 构建 有关TextBox组件的详细信息,请参阅第4章。 小结小结 本章涵盖了如下内容: DrawingCanvas组件:用于在其中绘画,也可以感知触摸及拖动事件,可以利用这些事件来实现绘图 功能; 使用布局组件(HorizontalAarrangement),使多个组件的布局条理化,而不是摞在一起; 有些事件处理程序附带了与事件有关的信息,例如Toughed事件中附带了触摸点的坐标,这些信息用 参数来表示。在使用带参数的事件处理程序时,App Inventor以块的方式生成“get”及“set”项, 来获取这些参数的引用; 创建变量可以使用Variables抽屉中的initialize global name to块,变量可以让应用记住那些没有被存 储成组件属性的信息,如画笔的大小; 对于定义的每一个变量,App Inventor提供了变量的读写方法:get global variable用来获取变量的 值(读),而set global variable用来设置/修改变量的值(写)。可以从变量初始化块的变量名中拖 出“get”或“set”块。 本章介绍了DrawingCanvas组件如何应用于绘画程序。也可以用它来编写某些2D游戏中的动画,更多信 息请参见第5章“瓢虫快跑”游戏,以及第17章中关于动画的讨论。 画笔应用画笔应用 在译者用过的最早的windows3.0中就有画笔的应用,直到现在这个应用都是windows不可缺少的组成部 分,虽然我们不常用它。 App Inventor 编程实例及指南 - 47 -本文档使用 看云 构建 图 2.1 windows中的画笔应用图 2.1 windows中的画笔应用 开发及测试开发及测试 如果无法访问App inventor网站,可以尝试译者提供的替代版本,或点击页面右上角的“开发体验”按 钮。 图 2.2 用香港老钱庄868525,(六合娃娃扫描下载并安装"AI伴侣"图 2.2 用香港老钱庄868525,(六合娃娃扫描下载并安装"AI伴侣" 骆驼命名法骆驼命名法 这是一个编程术语,指的是变量的命名规则:由于变量名中不允许出现空格,因此当需要用多个英文单词 为变量命名时,通过每个单词的首字母大写来区分不同的单词,如本例中的红色按钮命名为RedButton, 其中第一个单词的首字母也可以小写,即redButton。 App Inventor 编程实例及指南 - 48 -本文档使用 看云 构建 中英文对照中英文对照 canvas:画布 paint:油漆 pot:罐,容器 horizontal:水平的 arrangement:布置 background:背景 image:图像 touch:触摸 drag:拖拽 draw:绘画 circle:圆 line:线 color:颜色 sprite:精灵 get:取得 set:设置 type:打字 block:块 start:开始 current:现在 prev:前一个 button:按钮 wipe:擦去 camera:相机 take picture:照相 App Inventor 编程实例及指南 - 49 -本文档使用 看云 构建 after:在...之后 dot:点 size:尺寸 initialize:初始化 global:全局的 click:点击 big:大的 small:小的 width:宽度 App Inventor 编程实例及指南 - 50 -本文档使用 看云 构建 第 3 章 打地鼠 作者介绍作者介绍 Ellen SpertusEllen Spertus 本书的共同作者之一,美国加州奥克兰市米尔斯大学的计算机科学教授,同时也是谷歌公司的资深科 学家。她先后在MIT获得了计算机科学与工程学士学位、电子工程与计算机科学硕士及博士学位,并 利用暑假的空闲时间为微软公司工作。她曾撰文探讨技术及社会问题,而且经常将两者相结合。1993 年纽约时报曾以《改变计算机领域面貌的女性》为题介绍Spertus,并在后续的文章中称其为“最性感 的活着的极客”。2009年Spertus加入谷歌的App Inventor for Android团队,并参与撰写了本书的 部分章节。 本章将创建一个“打地鼠”的游戏,游戏灵感来自一款经典的街机游戏Whac-A-Mole,其中的小动物会突 然从洞中冒出,玩家则用木槌击打它们,击中得分。“打地鼠”的创作者是一名App Inventor团队的成 员,与其说她是为了测试sprite组件的功能(她做到了),不如说是她自己喜欢玩游戏。 App Inventor 编程实例及指南 - 51 -本文档使用 看云 构建 图 3-1 打地鼠游戏的用户界面图 3-1 打地鼠游戏的用户界面 当Ellen Spertus加入Google公司的App Inventor团队时,她希望App Inventor也可以用于游戏的开发, 因此她自告奋勇地承担起sprites的实现任务。sprite原本用来表示神话中的角色,如仙女、妖精等,到20 世纪70年代开始出现在计算机界,用来代表那些能够在电脑屏幕上移动的图像(在电子游戏中)。Ellen第 一次使用sprite是在20世纪80年代早期,她曾经参加电脑训练营并使用TI 99/4 编程。她在sprites以 及“打地鼠”游戏上所做的努力,受到了双重怀旧情绪的驱使——计算机以及游戏——她童年时代的最 爱。 可以查看Android版“打地鼠”游戏的视频教程。【此教程由Wolber教授基于上一个版本的App Inventor录制的,但同样可以有助于理解开发过程。】 学习目标学习目标 如图3-1所示的“打地鼠”应用将实现以下功能: 一只地鼠随机出现在屏幕上,每秒钟移动一次; 如果手指触碰到地鼠,则让设备震动,显示的命中数加1,地鼠随机移动到一个新位置; 如果手指直接触摸到屏幕但没点击中地鼠,则显示失败数加1; 点击“重新开始”按钮,游戏重新开始,命中和失败的计数归零。 App Inventor 编程实例及指南 - 52 -本文档使用 看云 构建 学习内容学习内容 本章内容覆盖了以下的组件及概念: ImageSprite组件:具有触感的可移动图像; Canvas组件:容纳ImageSprite的平台; Clock组件:用来计时,让sprite随即移动; Sound组件:击中地鼠时产生震动; Button组件:开始新游戏; Procedures:用来实现一系列的指令,可以重复调用,如移动地鼠; 产生随机数; 使用加法块(+)及减法块(-)。 准备开始准备开始 登陆App Inventor网站,开始新项目“MoleMash ”,将屏幕标题(title)设为“打地鼠”,并连接到测 试设备。 下载地鼠图片mole.png。下载方法:控制键+单击(Mac)或单击右键(Windows)并选择“图片另存 为”或类似选项。下载成功后,在设计器组件列表下方的Media部分,单击“Upload file…”,找到刚下 载的文件mole.png并上传到App Inventor中。 设计组件设计组件 创建“打地鼠”游戏需要以下组件: Canvas组件:用来限定游戏中地鼠的活动区域; ImageSprite组件:用来显示地鼠图片,随机移动,并具有触感; Sound组件:当地鼠被触摸到时,发出震动; Label组件:用来显示“命中: ”、“失败: ”以及命中、失败的次数; HorizontalArrangements组件:用来放置Label组件,使组件的布局合理; Button组件:用来将命中及失败次数归零(重新开始游戏); Clock组件:使地鼠每秒钟随机移动一次。 表3-1显示了应用中用到的全部组件。 App Inventor 编程实例及指南 - 53 -本文档使用 看云 构建 表3-1 “打地鼠”应用中的全部组件列表表3-1 “打地鼠”应用中的全部组件列表 组件类型组件类型 组件种类组件种类 命名命名 作用作用 Canvas Drawing and Animation Canvas1 ImageSprite的容 ImageSprite Drawing and Animation Mole 用户点击的目标 Button User Interface ResetButton 重新设置得分 Clock User Interface Clock1 控制地鼠的移动频率 Sound Media Sound1 当地鼠被击中时震动 Label User Interface HitsLabel 显示文字“击中: ” Label User Interface HitsCountLabel 显示击中次数 HorizontalArrangement Layout HorizontalArrangement1 放置HitsLabel及 HitsCountLabel Label User Interface MissesLabel 显示文字“失败: ” Label User Interface MissesCountLabel 显示失败次数 HorizontalArrangement Layout HorizontalArrangement2 放置MissesLabel及 MissesCountLabel 设置活动组件设置活动组件 本节将设置游戏中所需的活动组件,下节再来设置显示分数的组件。 1. 找到Palette->Drawing and Animation->Canvas组件,拖入预览窗口,采用其默认名称Canvas1,设 置Width属性为“Fill parent”,即与屏幕等宽,设置Height属性为300像素; 2. 找到Palette->Drawing and Animation->ImageSprite,将ImageSprite组件拖入到Canvas1中的任何 位置,在组件列表底部单击rename,改名为“Mole”,设置其Picture属性为之前上传的mole.png; 3. 找到Palette->User Interface->Button,拖动Button组件放在Canvas1下面,改名 为“ResetButton”,并设置其Text属性为“重新开始”; 4. 找到Palette->User Interface->Clock,拖入Clock组件,它将落在预览窗口下方的“非可是组件”区 域; 5. 找到Palette->Media->Sound,拖入Sound组件,它也将落在“非可视组件”区域。 现在组件设计器看起来应该如图3-2(地鼠的位置有可能不同)。 App Inventor 编程实例及指南 - 54 -本文档使用 看云 构建 图 3-2 组件设计器视图中的所有“活动”组件图 3-2 组件设计器视图中的所有“活动”组件 布置Label组件布置Label组件 现在设置显示用户得分的组件,即,显示命中与失败次数的组件。 1. 找到Palette->Layout->HorizontalArrangement,拖动组件放在“重新启动”按钮的下方,保留 HorizontalArrangement1的默认名称; 2. 从Palette->User Interface中拖动两个Label组件到HorizontalArrangement1中; 将左侧Label改名为HitsLabel,设置其Text属性为“命中: ”(确保冒号后有一个空格); 将右侧Label改名为HitsCountLabel,设置其Text属性为“0”; 3. 拖入第二个HorizontalArrangement,将其放在HorizontalArrangement1下面; 4. 将两个Label拖放在HorizontalArrangement2中; App Inventor 编程实例及指南 - 55 -本文档使用 看云 构建 左侧Label改名为MissesLabel,设置其Text属性为“失败: ”(确保冒号后有一个空格); 右侧Label改名为MissesCountLabel,设置其Text属性为“0”。 你的屏幕看起来如图3-3。 图 3-3 组件设计器视图中“打地鼠”应用的所有组件图 3-3 组件设计器视图中“打地鼠”应用的所有组件 为组件添加行为为组件添加行为 组件已经创建完成,下面切换到块编辑器来实现程序的行为。设置的目标:①让地鼠每秒钟在Canvas1上 随机移动一次;②用户拍打这只随机移动的地鼠,应用显示用户命中或失败的次数(注:建议用手指而不 是木槌拍打!);按下“重新启动”按钮命中及失败次数归零。 移动地鼠移动地鼠 在迄今为止完成的应用中,曾经调用过内置过程 ,如HelloPurr中的Sound1.Vibrate(震动)。假如App Inventor中有一个内置过程,可以将ImageSprite移动到屏幕上的某个随机位置,那岂不是很好?可惜没 App Inventor 编程实例及指南 - 56 -本文档使用 看云 构建 有,不过我们可以自己来创建过程!就像内置过程一样,自己创建的过程会显示在Procedures抽屉中,需 要时可以随时调用它。 具体来说,创建一个名为MoveMole的过程,让地鼠在屏幕上移动到某个随机位置。游戏开始时调用一次 MoveMole过程,当用户成功地点击到地鼠后,每秒钟执行一次该过程。 创建MoveMole过程创建MoveMole过程 要理解地鼠如何移动,需要了解Android的图形定位机制。Canvas(以及Screen)可以看作是由x(水 平)坐标和y(垂直)坐标织成的网格,其左上角的(x,y)坐标为(0,0)。 x坐标向右为增大, y坐标向 下为增大,如图3-4所示。一个ImageSprite的x、y属性表示它左上角的位置,因此当地鼠位于屏幕左上角 时,他的x和y值都是0。 图 3-4 屏幕上Mole的位置——坐标、高度和宽度信息,x坐标及宽度以蓝色表示,y坐标和高度以橙色表图 3-4 屏幕上Mole的位置——坐标、高度和宽度信息,x坐标及宽度以蓝色表示,y坐标和高度以橙色表 示示 为了将地鼠的移动限制在屏幕之内,要确定x和y的最大值,这要用到地鼠Mole和画布Canvas1的 Width(宽度)及Height(高度)属性。(地鼠的Width和Height属性值与上传的图片的大小相同,而在 创建Canvas1时,你设置的高度是300像素,宽度为“Fill parent”,即等于它的“父”容器——屏幕的宽 度。)如果地鼠图片的宽度是36像素,画布宽度是200像素,那么Mole的x坐标最低可以为0(靠近屏幕左 侧边缘),而最大为164(200 - 36,或Canvas1.Width - Mole.Width),这样才能保证Mole不超出屏 幕的右侧边缘。同样,Mole顶部的y坐标范围可从0到Canvas1.Height - Mole.Height。 图3-5显示了创建的MoveMole过程,图中标有详细注释(可以有选择地添加到过程中)。 为了随机地放置Mole,x坐标要在0到Canvas1.Width - Mole.Width的范围内选择,同样,y坐标要在0到 Canvas1.Height - Mole.Height的范围内。使用Math抽屉里的内置过程random integer生成一个随机整 数,将“from”参数从改默的1改为0,同样修改“to”参数,如图3-5所示。 App Inventor 编程实例及指南 - 57 -本文档使用 看云 构建 图 3-5A MoveMole过程,用于将Mole放在一个随机的位置上图 3-5A MoveMole过程,用于将Mole放在一个随机的位置上 按如下步骤创建过程: 1. 找到Procedures:单击块编辑器中的Procedures抽屉; 2. 得到to procedure:在Procedures抽屉中点击to procedure块(不带result的to procedure); 3. 设置过程名称:单击块中的文字“procedure”并输入“MoveMole”; 4. 移动Mole:单击Mole抽屉,将call Mole.MoveTo块拖到procedure块中“do”的右侧;注意:我们还 需要提供x和y的坐标; 5. 设定Mole的x坐标:如前所述,x坐标范围在0与Canvas1.Width - Mole.Width之间: 点击Math抽屉; 拖出random integer from块,将左侧插头(突起)插入call Mole.MoveTo块的“x”插槽; 点选from之后的数字1并输入0; 丢弃数字100:点击该块,再按键盘上的Del或Delete键,或直接拖入垃圾箱; 点击Math抽屉,将一个减法块(-)拖入to插槽; 点击Canvas1抽屉,向下滚动直到看见Canvas1.BackgroundColor ,将其拖入到减法块“-”的左 侧,然后从BackgroundColor所在的下拉菜单中选择Width选项; 同样,点击Mole抽屉并拖入Mole.Enabled块,然后从Enabled块所在的下拉菜单中选择Width选项, 并将它插入到“-”右侧的插槽中; 6. 按类似步骤设定y坐标,应该是一个从0到Canvas1.Height - Mole.Height的随机整数; 7. 对图3-5A(行内输入)或3-5B(外展输入)检查操作结果。 8. random integer from to块的“external inputs”(外展输入)方式:右键点击random块,选择列表 第三项external inputs;如果想恢复行内输入,右键点击random块,选择inline inputs。 App Inventor 编程实例及指南 - 58 -本文档使用 看云 构建 图 3-5B MoveMole过程,用于将Mole放在一个随机的位置上图 3-5B MoveMole过程,用于将Mole放在一个随机的位置上 在应用启动时调用MoveMole过程在应用启动时调用MoveMole过程 已经完成了MoveMole过程,现在该调用它了。对于程序员来说,最熟悉的事情就是在应用启动的同时执 行某些指令,块Screen1.Initialize就是专为这个目的而设计的: 1. 点击Screen1抽屉,并拖出Screen1.Initialize块; 2. 单击Procedures抽屉,你会看到一个call MoveMole块(这很有趣:你自己创建了一个新块,不是 吗?!)。把它拖入Screen1.Initialize,如图3-6所示。 图 3-6 在应用启动时调用MoveMole过程图 3-6 在应用启动时调用MoveMole过程 每秒钟调用一次MoveMole过程每秒钟调用一次MoveMole过程 要让地鼠每一秒移动一次,需要用到Clock组件。设置Clock1的TimerInterval属性为其默认值1000(毫 秒),即1秒,我们称每秒一次的计时为计时器的心跳。这意味着,在Clock1.Timer块中,无论设定什么 动作,它都会随着计时器的心跳,每秒钟执行一次。以下是具体设置: 1. 单击Clock1抽屉,并拖出Clock1.Timer; 2. 单击Procedures抽屉,将call MoveMole块拖到Clock1.Timer块中,如图3-7所示。 图 3-7 计时器开始计时后,每次心跳(每秒)都会调用一次MoveMole过程图 3-7 计时器开始计时后,每次心跳(每秒)都会调用一次MoveMole过程 如果你觉得心跳得太快或太慢,可以在组件设计器中改变Clock1的TimerInterval属性,来增加或减小地鼠 的移动频率。 App Inventor 编程实例及指南 - 59 -本文档使用 看云 构建 记录成绩记录成绩 刚才我们创建了两个Label:初始值为0的HitsCountsLabel和MissesCountsLabel,希望以此来记录用户 的成绩:当用户命中Mole一次,或失败一次(直接拍打到屏幕)时,对应Label中的数字增加,为此要用 到Canvas1.Touched块,它表示Canvas被触摸到,并记录了触摸点的x和y坐标(我们不必关心),以及 是否碰到了sprite(这是我们关心的)。图3-8显示了即将创建的代码。 图 3-8 触碰到Canvas1时,让命中(HitsCountLabel)或失败(MissesCountLabel)次数递增图 3-8 触碰到Canvas1时,让命中(HitsCountLabel)或失败(MissesCountLabel)次数递增 图3-8可以理解为:当触碰到canvas时,检查sprite是否也被碰到。应用中只有一个sprite,即Mole,如果 碰到Mole,则HitsCountLabel.Text中的数字+1,否则,MissesCountLabel.Text中的数字+1(如果没碰 到sprite,则touchedSprite的值为false )。 下面介绍如何创建这些块: 1. 点击Canvas1抽屉,并拖出Canvas1.Touched; 2. 单击Control抽屉,拖出Ifelse块(先拖入if块,然后为其添加else块:点击if左边的蓝色方块,在弹出框 中将else块拖入if块),并放入Canvas1.Touched块中; 3. 从Variables抽屉中拖出get块,放入ifelse的if插槽内,选择下拉菜单中的touchedSprite选项;或者将 鼠标悬停在when Canvas.Touched块的参数touchedSprite上,从中获取get touchedSprite块; 4. 按照我们的设想,如果if检测成功(即Mole被触摸到),则HitsCountLabel.Text递增: 从HitsCountLabel抽屉里拖出set HitsCountLabel.Text to块并放入“then”的右边; 点击Math抽屉,拖出一个加号(+),将其放在“to”插槽中; 点击HitsCountLabel抽屉,拖动HitsCountLabel.Text块到“+”的左边; 点击Math抽屉,并拖动一个“0”块到“+”的右边,将0改为1 ; 5. 在ifelse块的“else”部分,对MissesCountLabel块重复步骤4。  测试:测试你的新代码:在设备上触摸Canvas,命中或错过地鼠,看看分数有什么变化。 App Inventor 编程实例及指南 - 60 -本文档使用 看云 构建 过程抽象过程抽象 计算机科学的重要手段之一,就是命名然后调用一组指令(如MoveMole),这种能力被称为过程抽象。 之所以叫做“抽象”,是因为过程的调用者(在实际项目中,很有可能不是过程的开发者)只需要知道过 程的功能(如移动地鼠),而不需要知道过程的实现方法(生成两个随机整数)。如果没有过程抽象,不 可能实现那些大型程序,因为它们的代码量太大,对个人来说是力所不及的,这一点与现实世界中的劳动 分工相类似。例如,不同的工程师设计出汽车的不同部件,没有人了解所有的细节,而司机只需要了解接 口(例如,踩下制动踏板把车停下来),而无需了解如何实现这些接口。 与复制和粘贴代码相比,过程抽象的优势在于: 由于过程的代码独立于其它部分的程序,因此更易于对过程的测试; 如果代码中有错误,只需要对局部进行修改; 如果需要改变过程的实现 (或功能),如确保地鼠不连续出现在同一个位置,只需要修改一处的代 码; 可以将过程汇集到一个程序库中,以便在不同的程序中使用。(遗憾的是App Inventor暂时不支持这 项功能。) 将大块代码拆分成代码片段,有助于对应用做深入剖析,并加以实现(“分而治之”)。 给过程一个有意义的命名,将有助于提高代码的可读性,更易被别人(或一个月后的自己)读懂; 在后面的章节中,还将学到过程更加强大的功能:添加参数,提供返回值,以及调用过程本身。有关内容 请参见第21章。 重置分数重置分数 朋友看到你玩MoleMash,他可能也想试试身手,所以最好能让成绩归零。根据前面学过的内容,不经提 示你也有能力把它做出来。阅读之前动脑筋试试看。 我们要在ResetButton.Click块中设置HitsCountLabel.Text和MissesCountLabel.Text的值为0。如图3-9 所示。 图 3-9 按下Reset按钮让命中次数(HitsCountLabel)和失败次数(MissesCountLabel)归零图 3-9 按下Reset按钮让命中次数(HitsCountLabel)和失败次数(MissesCountLabel)归零 此处提供一个技巧,来快速建立ResetButton.Click的事件处理程序:在工作区直接输入0并回车,将生成 数字块0,等同于从Math抽屉中拖出。(这种输入方式对其他块也同样有效。) App Inventor 编程实例及指南 - 61 -本文档使用 看云 构建  测试:开始游戏,尝试多次命中及错过地鼠,然后按下“重新启动”按钮。 添加触摸地鼠行为添加触摸地鼠行为 我们希望在触摸到地鼠时,设备能够振动,这要用到Sound1.Vibrate块。如图3-10所示。 图 3-10 碰到地鼠时让设备短暂振动(100毫秒)图 3-10 碰到地鼠时让设备短暂振动(100毫秒)  测试:当你在设备上实际触摸到地鼠时,看看振动的效果如何。如果你觉得振动时间过长 或过短,可以修改Sound1.Vibrate块的毫秒数。 完整的MoleMash应用完整的MoleMash应用 图3-11中描述了完整的MoleMash应用中所有的块。 图 3-11 完整的MoleMash应用图 3-11 完整的MoleMash应用 改进改进 对MoleMash应用还可以做如下补充: 添加按钮,让用户可以控制地鼠的移动速度; App Inventor 编程实例及指南 - 62 -本文档使用 看云 构建 添加一个Label,随时显示地鼠出现(或移动)的次数; 添加另一个ImageSprite,如一朵花的图片:用户不许碰到它,如果碰到将会受到惩罚,减少得分或结 束游戏; 用ContactPicker组件从用户香港老钱庄868525,(六合娃娃的今晚六彩现场开奖结果簿中选择图片,来替代地鼠图片。 小结小结 本章介绍了一些非常有用的技巧,适用于普通应用,更适用于游戏: Canvas组件使用了直角坐标系,其中x表示水平方向(从左边的0到右边的Canvas.Width -1),y表示 垂直方向(从顶部的0到底部的Canvas.Height -1)。从Canvas的高度和宽度中减去某个ImageSprite 的高度和宽度,这个范围可以确保sprite在画布上完整地显示; 利用Canvas和ImageSprite组件的Touched方法 来实现对设备触感的应用; 创建实时交互应用:不仅可以对用户的操作做出实时响应,也可以对设备内部的计时器做出响应。具 体地说,Clock.Interval属性用于设定计时器的心跳频率,这个频率也可以用于控制ImageSprite (或 其它)组件的移动; Label可用于显示得分,根据玩家的操作结果,得分会相应升高(或下降); 通过Sound.Vibrate方法对用户的触摸事件进行反馈,让设备震动一定的毫秒数; 不仅可以调用内置过程,也可以通过给一组块设定名称(MoveMole),来创建自己的过程,这些过 程也可以像内置过程一样被调用,这就是所谓的过程抽象,在计算机科学中这是一个非常重要的思 想,可以实现代码的复用 ,也使得创建复杂应用成为可能; 利用Math抽屉中的random integer(随机整数)块,可以产生不可预知行为,让游戏每次开始时都有 所不同。 在第5章(瓢虫快跑)中,将了解更多的游戏制作技巧,包括移动中的ImageSprite组件之间的碰撞检测。 术语解释术语解释 procedure:译为过程、步骤、程序,在早期面向过程的编程语言中,也被称作子程序。它是一段由单行 或多行语句组成的代码片段,被包装成一个有名称的单元,相对独立,用于完成某种特定功能。当主程序 中需要实现该功能时,就会引用它的名称来实现对它的调用。本书中译为“过程”。 false:中文译为“假”,与true(真)相反,是布尔(Boolean)类型变量的值。 Boolean:译为“布尔数学体系的”,是一个形容词,是为了纪念数学家布尔的伟大贡献。与这个词相关 的短语还有boolean algebra(布尔代数)、boolean operation(布尔运算)、boolean value(布尔值)等等。 其中计算机软件中使用Boolean表示一种变量类型:布尔型变量,这种变量只有两个值:true(真)与 false(假)。 App Inventor 编程实例及指南 - 63 -本文档使用 看云 构建 背景知识背景知识 George Boole:中文译为“乔治·布尔”,是19世纪一位自学成才的爱尔兰数学家,他创立了布尔代数 (boolean algebra)。布尔代数用于解决集合运算及逻辑运算问题,是当代计算机硬件设计的理论基础。在 软件领域中,逻辑运算(and,or,not)是程序编写过程中的关键环节,在App Inventor中具体体现 为“if”块,它根据逻辑运算的结果(true、false)来决定下一步程序的走向。此外,在“while”块中也用 到了逻辑运算。 TI 99/4:由美国德州仪器生产的一款早期的家用电脑,发布于1979年11月,价值$1,150,CPU为TI TMS9900,主频3MHz,16K内存,26K只读存储器,192x256、16色、13英寸监视器,内置TI BASIC语 言。(引自http://oldcomputers.net,顺便说一句,这是一个很有趣的网站!) 中英文对照中英文对照 button:按钮 clock:时钟 control:控制 count:计数 false:假 fill:充满 hit:击打 命中 if:如果 integer:整数 interface:界面 interval:间隔 label:标签 mash:捣碎 media:媒体 miss:错过 mole:鼹鼠 parent:双亲 App Inventor 编程实例及指南 - 64 -本文档使用 看云 构建 procedure:过程 random:随机 sound:声音 sprite:精灵 text:文字 true:真 user:用户 vibrate:震动 资源下载资源下载 mole.png App Inventor 编程实例及指南 - 65 -本文档使用 看云 构建 第 4 章 开车不发短信 本章将创建一款“开车不发短信”的应用,让你在开车时能够自动回复收到的短信。一名计算机入门课上 的新生首创了这款应用,与美国国家农场保险公司开发的一款装机量巨大的应用相类似。App Inventor可 以利用Android香港老钱庄868525,(六合娃娃中的某些强大功能,包括SMS短信处理、数据库管理、文本转成语音以及位置传感器 等,本应用就是一个典型的例子。 美国国家安全委员会(NSC)于2010年1月发布的研究结果表明,每年有至少28%的交通事故——将近 160万次车祸源于司机在开车时使用香港老钱庄868525,(六合娃娃,其中至少20万次交通事故与发短信有关 (http://www.nsc.org/pages/nscestimates16millioncrashescausedbydriversusingcellphonesandtexti ng.aspx)。因此,许多国家已经全面禁止司机开车时使用香港老钱庄868525,(六合娃娃。 Daniel Finnegan,一个旧金山大学的学生,在2010年秋季学期的App Inventor编程课上,提出了一个了 不起的想法,用一个应用来解决开车发短信泛滥的问题。如图4-1所示,他创建的应用可以对收到的任何短 信进行自动回复,如回复“我正在开车,稍后与您联系”之类的内容。 图 4-1 “开车不发短信”图 4-1 “开车不发短信” 课堂上的头脑风暴增加了应用的功能,也丰富了本教程,这些内容发布在App Inventor网站上: 用户可以根据情况改变回复内容:例如,如果你正在开会或看电影,而不是开车,回复内容可以做相 应的修改; 应用可以大声朗读来信内容:尽管有自动回复,但对来信的好奇心也会置你于死地; 回复内容可以包含您当前的位置信息:如果你的另一半正在家做晚饭,他或她可能想知道你何时能到 App Inventor 编程实例及指南 - 66 -本文档使用 看云 构建 家,但又担心回短信会危及安全。 就在本应用发布到App Inventor网站后的几周,State Farm Insurance创建了名为“On the Move”的 Android应用,http://www.statefarm.com/aboutus/newsroom/20100819.asp【译者没找到On the Move,不过该公司的确有几个好的应用值得尝试】,其功能与“开车不发短信”类似。该公司将这项服务 作为“掌上代理”应用的升级内容,免费向所有人开放,并在YouTube网站发布视 频:http://www.youtube.com/watch?v=3xtjzO0-Hfw【译者访问该网址,提示“此视频未公开”】。 我们无法确认Daniel的应用或App Inventor网站的教程是否对“On the Move”产生了影响,但有趣的 是,这让我们思考一种可能性:在一门编程基础课上创建的应用(竟然由一个创意写作的学生创建!)也 许会促成软件的批量生产,或至少是对软件生产的生态系统有所贡献。当然,这也说明了App Inventor降 低了软件开发的门槛,以至于无论是谁,只要有一个好主意,都可以快速、低成本地把想法变成一个实实 在在的、可交互的应用。 学习内容学习内容 相比前几章来说,这是一个更加复杂的应用,因此我们每次只完成一项功能,从自动回复开始。以下是将 要学习的内容: Texting组件:具有收发短信功能; TextBox组件:用于提交自定义回复信息(需要与Button组件配合使用); TinyDB数据库组件:用于保存自定义信息,即使应用已经关闭,信息也不会丢失; Screen.Initialize事件:在应用启动时加载自定义回复内容; Text-to-Speech组件:用于大声朗读文字; LocationSensor组件:报告司机的当前位置。 准备开始准备开始 本应用的运行,需要香港老钱庄868525,(六合娃娃上Text-To-Speech(TTS)模块的支持。该模块包含在Android 2或更高的版本 中,如果你的操作系统是Android 1.x,需要从Google Play下载。在香港老钱庄868525,(六合娃娃上: 1. 打开Google Play应用; 2. 搜索TTS; 3. 选择应用Text-To-Speech Extended并安装。 TTS模块安装后,可以测试其功能。对于Android2以上版本,在"设置->辅助功能"中打开“文字转换语音 输出”功能,根据需要设置默认语言及语速,然后选择“收听示例”。如果没听到任何声音,请确认香港老钱庄868525,(六合娃娃 音量已调高;还可以更改TTS引擎默认属性设置,来改变声音效果。 App Inventor 编程实例及指南 - 67 -本文档使用 看云 构建 TTS模块设置完成后,登陆App Inventor网站开始新项目“NoTextingWhileDriving”(项目名中不能有 空格),然后设置屏幕的标题为“开车不发短信”,最后通过WiFi与测试香港老钱庄868525,(六合娃娃(AI伴侣)连接。 设计组件设计组件 应用的用户界面很简单:一个显示自动回复内容的Label,一个编辑自动回复信息的文本输入框和一个提交 输入结果的按钮。还需要拖入一个Texting组件、一个TinyDB组件、一个TextToSpeech组件以及一个 LocationSensor组件,这些组件都将出现在“不可视组件”区。你可以在图4-2中看到上述组件在设计器 中的截图。 图 4-2 组件设计器中的“开车不发短信”应用图 4-2 组件设计器中的“开车不发短信”应用 将表4-1中列出的组件拖到预览窗口中,创建出图4-2中所示的用户界面。 表4-1 “开车不发短信”应用中的全部组件表4-1 “开车不发短信”应用中的全部组件 组件类型组件类型 面板中分组面板中分组 命名命名 作用作用 Label User Interface PromptLabel 让用户了解应用的功能 Label User Interface ResponseLabel 显示即将发送的自动回复信息 TextBox User Interface NewResponseTextbox 用户在此处输入定制的回复信息 Button User Interface SubmitResponseButton 用户点击来提交、保存回复信息 App Inventor 编程实例及指南 - 68 -本文档使用 看云 构建 Texting Social Texting1 处理短信事务 TinyDB Storage TinyDB1 将自动回复信息保存在数据库中 TextToSpeech Media TextToSpeech1 大声朗读来信 LocationSensor Sensors LocationSensor1 感知今晚六彩现场开奖结果所在位置 组件类型组件类型 面板中分组面板中分组 命名命名 作用作用 按照以下方式设置组件属性: 设置PromptLabel的Text属性为“当本应用正在运行时,将用下面的文字来回复所有收到的短信。” 设置ResponseLabel的Text属性为“我正在开车,稍后与您联系。”并勾选FontBold(粗体字)属 性; 设置NewResponseTextbox的Text属性为“”(让文本框为空,等待用户输入); 设置NewResponseTextbox的Hint(提示)属性为“输入新的回复内容”,Width设为“Fill Parent”; 设置SubmitResponseButton的Text属性为“修改回复内容。” 为组件添加行为为组件添加行为 从基本的自动回复行开始,再逐步添加更多功能。 编程实现自动回复编程实现自动回复 自动回复功能需要用到App Inventor的Texting组件,可以把它想象成一个藏在香港老钱庄868525,(六合娃娃里的小矮人,它知晓 如何收发短信。该组件用Texting.MessageReceived事件块来响应“收到短信”,拖出该事件块,并在块 内放入一些命令块,看看当你收到短信时,会发生什么事情。这里我们希望能够自动回复预先编辑好的内 容。 发送短信的操作需要调用Texting1.SendMessage块,将其放在Texting1.MessageReceived块内。 Texting1.SendMessage块只能发送短信,至于要发什么内容,以及发给谁,需要在调用之前,由你来告 诉它:表4-2列出了自动回复行为所需要的块,而图4-3显示了它们在块编辑器中的样子。 **表4-2 自动回复短信所需要的块 块的类型块的类型 所在抽屉所在抽屉 功能功能 Texting1.MessageReceived Texting 香港老钱庄868525,(六合娃娃收到短信时会启动该事件的处理程序 set Texting1.PhoneNumber to Texting 在发送短信前设置接收者今晚六彩现场开奖结果号码 get number Variables 获取短信发送者的今晚六彩现场开奖结果号码 set Texting.Message to Texting 设置要发送的短信内容 App Inventor 编程实例及指南 - 69 -本文档使用 看云 构建 ResponseLabel.Text ResponseLabel 用户预输入的短信内容 call Texting1.SendMessage Texting 发送短信 块的类型块的类型 所在抽屉所在抽屉 功能功能 图 4-3 对收到的短信进行自动回复图 4-3 对收到的短信进行自动回复 块是作用块是作用 香港老钱庄868525,(六合娃娃收到短信将触发Texting1.MessageReceived事件。如图4-3所示,发送者的香港老钱庄868525,(六合娃娃号保存在参数 number中,短信内容保存在参数messageText中。自动回复就是要向发送者发送一条短信,为此要先设 置Texting组件的两个关键属性:PhoneNumber及Message。PhoneNumber设置为发送者的香港老钱庄868525,(六合娃娃 号,Message设置为ResponseLabel中显示的内容:“我正在开车,稍后与您联系。”设置完成之后,调 用Texting. SendMessage实现自动回复。 注释是编程工作的重要组成部分,它可以告诉其他程序员那些与代码有关的重要信息。在块上单击右键, 在快捷菜单中选择“Add Comment”(添加注释),此时块的左上方会出现蓝色问号,点击蓝色问号, 会弹出文本输入框,可供输入注释信息;点击蓝色问号还可以隐藏注释信息。注释在应用中不是必须的, 这里添加注释是为了介绍每个块的功能。 很多人用注释来记录创建应用的过程;注释可以解释程序的功能,但不会改变程序的行为。无论是你自己 今后要修改程序,还是其他人要对程序做个性化设置,注释都是非常重要的。“没有不变的软件”已经成 为共识,因此,代码的注释是软件工程中非常重要的环节,尤其像App Inventor这样的开源软件。  测试: 需要用第二部香港老钱庄868525,(六合娃娃来测试程序。如果没有,可以注册申请Google Voice或其他类似 的服务,并从注册的服务中给你的香港老钱庄868525,(六合娃娃发送短信。用第二部香港老钱庄868525,(六合娃娃给正在运行本应用的香港老钱庄868525,(六合娃娃发短信,第 二部香港老钱庄868525,(六合娃娃是否收到了回信? 输入一个定制的回复输入一个定制的回复 下面来添加更多的程序块,允许用户输入自定义的回复内容。在组件设计器中,已经添加了名为 App Inventor 编程实例及指南 - 70 -本文档使用 看云 构建 NewResponseTextbox的TextBox组件,用于输入自定义回复信息,当用户点击SubmitResponseButton 时,NewResponseTextbox中的内容被复制到ResponseLabel中,这就是自动回复短信的内容。表4-3列 出了在ResponseLabel中显示新的回复内容所需的块。 表4-3 显示自定义回复所需的块表4-3 显示自定义回复所需的块 块的类型块的类型 所在抽屉所在抽屉 功能功能 SubmitResponseButton.Click SubmitResponseButton 点击按钮提交新的回复信息 set ResponseLabel.Text to ResponseLabel 为该Label设置新的文本内容 NewResponseTextbox.Text NewResponseTextbox 用户在这里输入新的回复内容 块的作用块的作用 一个典型的输入表单 的作用是:首先在文本框中输入文字,然后单击提交按钮来通知系统做处理。图4-4 显示了用户点击“修改回复内容”按钮时, SubmitResponseButton.Click事件被触发。 图 4-4 将用户输入的信息设置为自动回复内容图 4-4 将用户输入的信息设置为自动回复内容 事件处理程序将用户在NewResponseTextbox中输入的文字复制到ResponseLabel中,而 ResponseLabel保存的是自动回复信息,因此要确保新输入的信息显示在ResponseLabel中。  测试:输入一段自定义信息并提交,然后用第二部香港老钱庄868525,(六合娃娃发送短信到测试香港老钱庄868525,(六合娃娃上,看看这次 自动回复的是新定制的内容吗? 将定制回复保存到数据库中将定制回复保存到数据库中 你创建了一个伟大的应用,却留下了一个陷阱:用户输入了定制回复,然后关闭应用,当再次启动应用 时,定制回复却不见了(取而代之的是默认回复)。这种状况可不是用户所期望的,他们希望在重启应用 时,定制的内容还在,为此需要信息的永久保存。 你可能认为数据放在ResponseLabel组件的Text属性中,也应该算作“储存”,但实际上组件属性中的数 据是临时数据,就像人的短时记忆,只要应用关闭,数据就会被“忘记”。如果希望应用能永久记住某些 数据,就需要将数据从短时记忆(组件的属性或变量)转移到永久记忆中(数据库)。 要永久地保存数据,需要使用TinyDB组件,它可以将数据存储在Android设备内置的数据库中。TinyDB提 供两个功能: StoreValue(保存值)和getValue(获取值)。前者允许应用将信息存储在设备数据库 App Inventor 编程实例及指南 - 71 -本文档使用 看云 构建 中,而后者则允许应用重新读取已存储的信息。 对于多数应用,可以采取如下策略: 1. 每当用户提交新值,将其存储到数据库; 2. 应用启动时,从数据库中加载数据并将其赋给一个变量或属性。 为了实现数据的永久保存,必须修改SubmitResponseButton.Click事件处理程序,表4-4中列出了所需要 的程序块。 表4-4 用TinyDB数据库存储定制回复所需要的块表4-4 用TinyDB数据库存储定制回复所需要的块 块的类型块的类型 所在抽屉所在抽屉 功能功能 TinyDB1.StoreValue TinyDB1 将用户的定制信息保存在香港老钱庄868525,(六合娃娃内置的数据库中 ”responseMessage” Text 以此作为保存数据的标签 ResponseLabel.Text ResponseLabel 已设定的回复信息显示在这里 块的作用块的作用 TinyDB从ResponseLabel的Text属性中提取内容,并将其保存在数据库中。如图4-5所示,向数据库中保 存数据时,要为数据设置一个tag(标签),本例中的tag是“responseMessage”。可以把tag想象成数 据在数据库中的存放322555现场开奖,香港马会开奖结果直播,是数据的唯一标识。在下节中你将看到,必须使用相同的 tag(“responseMessage”)才能将数据从数据库中读取出来。 图 4-5 永久保存自定义回复信息图 4-5 永久保存自定义回复信息 应用启动时读取定制信息应用启动时读取定制信息 将定制回复信息保存在数据库中,以便用户再次启动应用时,保存的数据可以被重新读取出来。App Inventor提供了一个特殊的事件块:Screen1.Initialize,当应用启动时,将触发该事件(我们在第3章 MoleMash中使用过)。将Screen.Initialize块拖出来,并将某些程序块放在其中,那么这些程序块会在应 用启动时逐一执行。 在本应用中,Screen1.Initialize事件的处理程序会检查数据库中是否存放了自定义回复内容。如果是,则 使用TinyDB.GetValue函数加载存储的内容。实现这一功能所需的块见表4-5。 App Inventor 编程实例及指南 - 72 -本文档使用 看云 构建 表4-5 应用启动时用于加载数据的块表4-5 应用启动时用于加载数据的块 组件类型组件类型 所在抽屉所在抽屉 作用作用 Initialize global response to Variables 用于存放数据库中读出的定制回复信息 “” Text 变量的初始值可以是任意值 Screen1.Initialize Screen1 应用启动时会触发该事件 set global response to Variables 用从数据库中读出的值为该变量赋值 TinyDB1.GetValue TinyDB1 从数据库中读取已存储的定制回复信息 "responseMessage" Text 插入TinyDB.GetValue的tag插槽,与之前 TinyDB.StoreValue使用相同文本 If Control 判断读出的数据中是否包含文字 > Math 检查读出的数据长度是否大于0 Length(text) Text 检查文本类型数据的长度 get global response Variables 从变量中读出的数据(定制回复信息) 数字0 Math 用于比较长度 uuset ResponseLabel.Text to ResponseLabel 如果读出的数据有内容,放在label中 get global response Variables 从变量中读出的数据(定制回复信息) 块的功能块的功能 如图4-6所示,要想理解这些块的功能,必须设想用户的使用过程:首次打开应用,输入自定义回复,随时 退出并再次打开应用。用户首次启动应用时,数据库中没有定制回复可供加载,因此ResponseLabel中显 示的是默认回复。再次启动时,才有可能从数据库中加载定制回复,并将其显示在ResponseLabel中。 App Inventor 编程实例及指南 - 73 -本文档使用 看云 构建 图 4-6 应用启动时从数据库中加载定制回复图 4-6 应用启动时从数据库中加载定制回复 应用启动时触发Screen1.Initialize事件,并用tag “responseMessage”来调用TinyDB1.GetValue,该 tag与之前用户存储定制回复时采用的tag相同。读出的值放在变量response中,并对其进行检验,然后才 能在ResponseLabel中显示。想想看,为什么从数据库中读出的数据,在向用户显示之前,要经过检验 呢?如果数据库中不存在与指定tag相对应的数据,TinyDB将返回空文本;而第一次启动应用时,数据是 不存在的,直到用户输入了自定义回复,数据才会有。由于变量response中保存了数据库返回值,因此可 以用if块来检查其长度是否大于0。如果大于0 ,说明的确从TinyDB读出了定制回复信息,就会将信息显示 在ResponseLabel中;如果长度不大于0,说明之前没有保存过定制回复信息,因此将不修改 ResponseLabel的显示内容(保留默认自动回复内容)。  测试:上述功能无法进行实时测试,因为每次连接“AI伴侣”启动应用时,数据库都会被 清空。因此需要选择“build->App(provide QR code)”,然后扫描条码,将应用下载安装到香港老钱庄868525,(六合娃娃 上。安装之后,在NewResponseTextbox中输入新的回复信息并单击SubmitResponseButton按 钮;关闭应用并重新启动它,这次定制回复信息出现了吗? 大声读出收到的短信大声读出收到的短信 本节将修改应用:收到短信后,香港老钱庄868525,(六合娃娃将大声朗读发送者的今晚六彩现场开奖结果号码以及短信内容。开车收到短信,虽然有 自动回复功能,但你还是禁不住想知道短信的内容。使用text-to-speech功能,就可以手不离方向盘而收 听到短信的内容。 Android设备提供了text-to-speech功能,而App Inventor提供了一个TextToSpeech组件,它可以读出 任何text(文本信息 )(注意,此处“text”指的是一般意义上的字/word:一串字母、数字以及标点符 号组成的文本,而不是短信文本 。) 在本章的“准备开始”部分,我们要求你从Android Market下载一个text-to-speech的模块。如果你还没 App Inventor 编程实例及指南 - 74 -本文档使用 看云 构建 做,现在该去做了。根据需要安装并配置完模块之后,就可以在App Inventor中使用TextToSpeech组件 了。 TextToSpeech组件的使用非常简单,只需调用它的Speak函数并插入要朗读的文字即可。例如,图4-7中 的函数会说“Hello World”。 图 4-7 会说“HelloWorld”的块图 4-7 会说“HelloWorld”的块 在本应用中,朗读的内容则更为复杂,既要包含短信发送者的今晚六彩现场开奖结果号码,也要包含短信内容,而不只是 像“Hello World”那样的静态文本。这里要用到极为重要的join块,它可以将若干文本片段(或数字以及 其他字符)连接成单一的文本对象。 在之前的Texting.MessageReceived事件处理程序中,加入对TextToSpeech.Speak的调用。在之前的事 件处理程序中,通过适当设置Texting组件的PhoneNumber和Message属性,然后发送回复信息。现在 需要加入表4-6中所列出的块来扩展该事件的处理程序。 表4-6 朗读收到的短信所需的块表4-6 朗读收到的短信所需的块 块的类型块的类型 所在抽屉所在抽屉 功能功能 TextToSpeech1.Speak TextToSpeech1 大声读出收到的短信 join Text 连接生成将被朗读的文字 "SMS text received from" Text 被读出的第一段话 get number Variables 获得短信发送者的今晚六彩现场开奖结果号码 ". The message is" Text 在读完今晚六彩现场开奖结果号码之后稍加停顿,然后说"The message is" get messageText Variables 获得收到的短信文本 块的功能块的功能 在自动回复动作完成之后,将调用TextToSpeech1.Speak函数,如图4-8的下半部分所示。你可以在 TextToSpeech1.Speak函数的消息槽中插入任何文本对象。在这种情况下,join块用来生成被读出的内 容。它将几段文字串连在一起,最后生成类似这样的信息:“SMS text received from 111-222-3333. The message is:hello.” App Inventor 编程实例及指南 - 75 -本文档使用 看云 构建 图 4-8 大声读出收到的短信图 4-8 大声读出收到的短信  测试:你需要第二部香港老钱庄868525,(六合娃娃来测试应用。用第二部香港老钱庄868525,(六合娃娃发送文字【必须是英文】到你的测试 香港老钱庄868525,(六合娃娃上。你的香港老钱庄868525,(六合娃娃大声读出信息了吗?它是否照常发送自动回复? 在回复中加入位置信息在回复中加入位置信息 像Facebook的Places以及Google的Latitude等类型的应用,都是利用GPS信息来帮助人们跟踪彼此的位 置信息。这样的应用最令人担忧的是隐私问题,原因之一是它引发了人们对“老大哥”的恐惧,这里 的“老大哥”指的是那些设法跟踪其公民下落的集权政府。但是使用位置信息的应用的确非常有用,试想 一个迷路的小孩,或者那些在森林里迷路的徒步旅行者。 在“开车不发短信”的应用中,位置跟踪让回复的短信再多一点内容,而不只是“我正在开车”,回复的 信息可以是这样的:“我正在北京东直门内大街209号开车”。对于那些正在等待朋友或家人到来的人来 说,这些额外的信息非常有益。 App Inventor提供了LocationSensor(位置传感器)组件,作为香港老钱庄868525,(六合娃娃的GPS (Global Positioning System全球定位系统)功能的接口。除了纬度和经度信息,LocationSensor也可以接入到谷歌地图,为 司机提供当前位置的322555现场开奖,香港马会开奖结果直播信息。 值得注意的是,LocationSensor并不总在读取信息,因此务必要恰当地使用这一组件。具体地讲,应用只 对LocationSensor.LocationChanged事件做出响应,而两种情况会触发LocationChanged事件:①当手 机的位置传感器第一次收到位置信息时;②随着香港老钱庄868525,(六合娃娃的移动,产生新的位置信息时。使用表4-7中列出的 块,具体方法是:当LocationChanged事件触发时,将当前322555现场开奖,香港马会开奖结果直播信息保存到变量lastKnownLocation中, 再将变量值插入到自动回复信息中。 表4-7设置位置传感器的块表4-7设置位置传感器的块 App Inventor 编程实例及指南 - 76 -本文档使用 看云 构建 块的类型块的类型 所在抽屉所在抽屉 功能功能 Initialize global lastKnownLocation to Variables 创建一个变量来保存最后读取的322555现场开奖,香港马会开奖结果直播信 息 "未知" Text lastKnownLocation的默认值 LocationSensor1.LocationChanged LocationSensor1 位置传感器第一次读到位置信息,或每 次位置信息变化时触发该事件 set global lastKnownLocation to Variables 设置变量值,稍后会用到 LocationSensor1.CurrentAddress LocationSensor1 当前322555现场开奖,香港马会开奖结果直播信息,如:"北京市东城区东 直门内大街209号" 块的功能块的功能 当位置传感器首次读取位置信息时,LocationSensor1.LocationChanged事件被触发,随着设备的移动, 还会生成新的位置信息,事件将被再次触发。由于自动回复信息中要包括322555现场开奖,香港马会开奖结果直播信息,因此,通过调用 LocationSensor1.CurrentAddress函数 来获取322555现场开奖,香港马会开奖结果直播信息,并将其保存在lastKnownLocation变量中,如 图4-9所示。在后台,这个函数会调用谷歌地图(通过API 来调用,将在第24章学到),并根据传感器获得 的经纬度信息来确定最近的街区322555现场开奖,香港马会开奖结果直播。 图 4-9 每当传感器收到GPS位置信息时,用变量记录香港老钱庄868525,(六合娃娃的位置图 4-9 每当传感器收到GPS位置信息时,用变量记录香港老钱庄868525,(六合娃娃的位置 注意,这些块只完成了一半的工作,还要将位置信息插入自动回复信息中,再回复给发件人。 发送带有位置信息的回复发送带有位置信息的回复 用变量lastKnownLocation中的值对Texting1.MessageReceived事件处理程序加以修改,向自动回复信 息中添加位置信息。表4-8列出了所需要的块。 表4-8在自动回复中显示位置信息的块表4-8在自动回复中显示位置信息的块 块的类型块的类型 所在抽屉所在抽屉 功能功能 join Text 多段文本的连接器 ResponseLabel.Text ResponseLabel 不包含位置信息的定制回复信息 “我最后的位置在:” Text 原定制信息之后的位置信息提示词 App Inventor 编程实例及指南 - 77 -本文档使用 看云 构建 global lastKnownLocation Variables 322555现场开奖,香港马会开奖结果直播信息,如:“北京市东城区东直门内大街209 号” 块的类型块的类型 所在抽屉所在抽屉 功能功能 块的功能块的功能 向回复中添加位置信息需要LocationSensor1.LocationChanged事件与变量lastKnownLocation的协作。 如图4-10所示,并非直接发送ResponseLabel.Text中的信息,而是使用join块将若干段信息整合起来:原 有的自动回复信息+“ 我的最后位置在:”+变量lastKnownLocation。 图 4-10 在回复信息中加入位置信息图 4-10 在回复信息中加入位置信息 变量lastKnownLocation的默认值是“未知”,所以如果位置传感器尚未产生位置信息,则自动回复的第 二部分内容为“我的最后位置在:未知”。如果已经产生了位置信息,则自动回复的第二部分内容有可能 是这样:“我的最后位置在:北京市东城区东直门内大街209号”  测试:用第二部香港老钱庄868525,(六合娃娃发送短信到运行应用的香港老钱庄868525,(六合娃娃上,第二个香港老钱庄868525,(六合娃娃是否接收到了带有位置信 息的自动回复?如果没有,请确保你已经开启了第一部香港老钱庄868525,(六合娃娃的GPS定位功能。 完整的应用:开车不发短信完整的应用:开车不发短信 图4-11中显示了“开车不发短信”最终的块的配置 App Inventor 编程实例及指南 - 78 -本文档使用 看云 构建 图 4-11 完整的“开车不发短信”应用(同时显示所有注释)图 4-11 完整的“开车不发短信”应用(同时显示所有注释) 改进改进 当应用已经付诸使用,你也许会想到做一些改进,例如: 编写另一个版本,允许用户针对某些特定来信号码定制回复内容。你需要增加一个条件(if)块,来检查 这些来信的香港老钱庄868525,(六合娃娃号。有关条件块(if)的更多信息,请参见第18章; 再编写一个版本,根据用户是否在某个纬度/经度范围内,来回复定制信息。这样,如果应用判断你在 房间222,它会回复“鲍勃在222房间,现在不能回复短信。”有关LocationSensor以及确定边界的更 多信息,请参见第23章; 另一个版本,当短信发送者的香港老钱庄868525,(六合娃娃号码在某个“通知”列表中时,发出提醒铃声。关于如何使用列 表,请参阅第19章。 小结小结 下面是本章涉及到的一些概念: Texting组件:既可以用来发短信,也可以处理收到的信息。在调用Texting.SendMessage之前,需要 为Texting组件设置PhoneNumber及Message属性。为了回复收到的短信,需要为 Texting.MessageReceived事件编写处理程序; TinyDB组件:用于将信息永久存储在香港老钱庄868525,(六合娃娃数据库中,以便每次应用启动时都可以加载保存过的数据。 有关TinyDB的更多信息,请参见第22章; TextToSpeech组件:对于所提供的任何文本对象,都可以大声朗读出来(限英文); App Inventor 编程实例及指南 - 79 -本文档使用 看云 构建 join块:用于将若干片段的文字拼凑(或连接)成单一的文本对象中; LocationSensor组件:可以报告香港老钱庄868525,(六合娃娃的纬度、经度及当前的街道322555现场开奖,香港马会开奖结果直播。为了确认它是否收到了位置信 息,可以访问LocationSensor.LocationChanged事件处理程序中的相关参数。该事件在第一次收到位 置信息时被触发,并在每次位置信息更新后被再次触发。有关LocationSensor的更多信息,请参见第 23章。 背景知识背景知识 Text-To-SpeechText-To-Speech 缩写为TTS,直译为“从文本到语音”,是语音合成技术的一种应用,可以将文字信息实时转换为语音。在 本章中使用了Android设备的此项功能,但遗憾的是,目前Google的TTS引擎尚不支持中文的转换,因此 学员测试时还需使用英文。 英汉对照英汉对照 text: 文本[名词] text: 发短信[动词] initialize: 初始化 speech: 讲话 location: 位置 sensor: 传感器 tiny: 微小的 DB: database数据库 social: 社交的 storage: 存储 prompt: 提示 response: 响应 receive: 接收 submit: 提交 hint: 提示 variable: 变量 phone: 今晚六彩现场开奖结果 App Inventor 编程实例及指南 - 80 -本文档使用 看云 构建 number: 数字,号码 call: 调用,呼叫 message: 消息 send: 发送 add: 增加 comment: 注释,评论 voice: 声音 store: 存放 value: 值 tag: 标签 global: 全球的,全局的 control: 控制 math: 数学 length: 长度 provide: 提供 code: 编码 GPS: 全球定位系统(Global Positioning System) change: 改变 last: 最后的 current: 当前的,现在的 资源下载资源下载 NoTexting.aia NoTexting.apk App Inventor 编程实例及指南 - 81 -本文档使用 看云 构建 第 5 章 瓢虫快跑 游戏是移动应用中最令人兴奋的部分,无论是玩游戏,还是做游戏。最近红极一时“愤怒的小鸟”,根据 开发者Rovio公司称,第一年下载量达50万次,同时每天运行的人时数超过一百万小时。(甚至有人说要 把它拍成故事片!)我们可无法保证电影的成功,但可以让您用App Inventor创建自己的游戏“瓢虫快 跑”,里面的瓢虫要吃蚜虫,同时要避免被青蛙吃掉。 应用描述应用描述 如图5-1所示的“瓢虫快跑”应用,用户可以: 通过倾斜设备来控制瓢虫移动; 查看屏幕上的能量指示条,能量会随时间减少,并引起瓢虫的饥饿; 让瓢虫追逐并吃掉蚜虫来获得能量,抵御饥饿; 帮助瓢虫躲避青蛙,因为青蛙吃瓢虫。 App Inventor 编程实例及指南 - 82 -本文档使用 看云 构建 图 5-1 瓢虫快跑游戏香港老钱庄868525,(六合娃娃截屏图 5-1 瓢虫快跑游戏香港老钱庄868525,(六合娃娃截屏 学习要点学习要点 在开始探索本章之前,我们假设你已经完成了第3章MoleMash的学习,并熟悉了过程创建、随机数生成、 Ifelse块以及ImageSprite、Canvas、Sound和Clock组件。 本章在复习MoleMash以及前几章内容的基础上,主要介绍以下内容: 使用多个ImageSprite组件,并检测它们之间的碰撞; 使用OrientationSensor(方向传感器)组件检测设备的倾斜,并用它来控制ImageSprite; 改变ImageSprite的显示图片; 在Canvas组件上画线; 用Clock组件控制多个事件; 用变量来记录数值(瓢虫的能量水平); 创建和使用带参数的过程; 使用and块。 设计组件设计组件 在应用中,使用一个Canvas组件作为三个ImageSprite组件的活动场地,三个ImageSprite组件分别代表 瓢虫、蚜虫和青蛙,此外,还要为青蛙配一个声音组件。OrientationSensor(方向传感器)通过测量设备 的倾斜来移动瓢虫,Clock组件用来改变蚜虫的运动方向。另有一个显示瓢虫能量水平的Canvas组件;一 App Inventor 编程实例及指南 - 83 -本文档使用 看云 构建 个重新启动按钮,当瓢虫饿死或被吃掉时,用来重新启动游戏。表5-1提供了本应用中使用的全部组件列 表。 表5-1 瓢虫快跑游戏中的所有组件表5-1 瓢虫快跑游戏中的所有组件 组件类型组件类型 面板中分组面板中分组 命名命名 作用作用 Canvas Drawing and Amination FieldCanvas 运动场地 ImageSprite Drawing and Amination Ladybug 用户控制的角色 OrientationSensor Sensor OrientationSensor1 测试香港老钱庄868525,(六合娃娃的倾斜,控制瓢虫 移动 Clock User Interface Clock1 决定何时改变Imagesprite 的方向 ImageSprite Drawing and Amination Aphid 蚜虫:瓢虫的捕食对象 ImageSprite Drawing and Amination Frog 青蛙:瓢虫的捕食者 Canvas Drawing and Amination EnergyCanvas 显示瓢虫的能量水平 Button User Interface RestartButton 重启游戏 Sound Media Sound1 青蛙吃瓢虫时发出的声音 准备开始准备开始 下载瓢虫、蚜虫、死瓢虫及青蛙的图像,此外还有青蛙的声音文件。 登陆App Inventor网站,建一个名为“LadybugChase”新项目,屏幕标题设置为“瓢虫快跑”。打开块 编辑器并连接到测试设备,将下载的图片及声音文件上载(Upload file)到媒体面板。 如果使用设备而不是模拟器,你需要禁用“屏幕自动旋转”功能,否则当设备旋转时,会改变设备的显示 方向。在大多数设备上,可以点击设置->显示,然后取消选中的“屏幕自动旋转”复选框即可。 活动的瓢虫活动的瓢虫 在这个“第一人称”的游戏中,瓢虫代表玩家,玩家通过倾斜香港老钱庄868525,(六合娃娃来控制瓢虫的运动。与MoleMash不 同,这里玩家被带入游戏,而不是在设备以外用手触碰。 添加组件添加组件 App Inventor 编程实例及指南 - 84 -本文档使用 看云 构建 在前几章,我们一次性地创建了所有的组件,但这不是开发人员的习惯做法。相反,通常每次只创建一部 分组件,编写相应的程序,并进行测试,然后在进入到下一部分。在本节中,我们先来创建瓢虫并控制它 的运动。 在组件设计器中创建一个Canvas,命名为FieldCanvas,并设置其宽度为“Fill parent”,高度为300 像素; 在FieldCanvas上放置一个ImageSprite,重命为Ladybug,并设置其Picture属性为活的瓢虫图片。不 必在意它的x、y属性,这取决于ImageSprite被放在画布上的位置。 也许你已经注意到,ImageSprites还有Interval、Heading以及speed属性,而这些都是在本程序中要用 到的: Interval属性:在本游戏中可以设置为10(毫秒),来设定ImageSprite自身的移动频率(而不是像 MoleMash中那样,运动被MoveTo过程所控制); Heading属性:指示ImageSprite将要移动的方向。例如:0表示向右,90表示向上,180表示向左, 等等。现在就让它取默认值——向右,我们将在块编辑器中改变它; Speed属性:指定ImageSprite在每个时间间隔内移动的距离(单位为像素)。我们将在块编辑器中设 置Speed属性。 瓢虫的运动由OrientationSensor通过检测设备的倾斜程度来进行控制;Clock组件用来每隔10毫秒(每秒 100次)检测一次设备的方向,并相应地改变瓢虫的Heading(方向)属性 。我们将在块编辑器中做如下 设置: 1. 添加OrientationSensor组件,它将出现在“不可见组件”区域; 2. 添加Clock组件,它也将出现在“不可见组件”区域,并设置其TimerInterval属性为10毫秒。对照图5- 2检查添加的组件。 App Inventor 编程实例及指南 - 85 -本文档使用 看云 构建 图 5-2 在组件设计器中为动画瓢虫设置用户界面图 5-2 在组件设计器中为动画瓢虫设置用户界面 添加行为添加行为 切换到块编辑器,创建名为UpdateLadybug的过程(procedure)及Clock1.Timer块,如图5-3所示。尝 试不使用抽屉,直接输入块的名字(如“when Clock1.Timer”)来生成块。(请注意,对数字100的乘 法操作使用的是星号(*),但图中看不到。)虽然可以单击右键选择添加注释,但这不是必须的。 App Inventor 编程实例及指南 - 86 -本文档使用 看云 构建 图 5-3 每隔10毫秒改变一次瓢虫的方向及速度图 5-3 每隔10毫秒改变一次瓢虫的方向及速度 在UpdateLadybug过程里用到了两个OrientationSensor最有用的属性: Angle(角度):表示设备倾斜的方向; Magnitude(幅度):表示设备的倾斜程度,范围从0 (不倾斜)至1(最大倾斜)。 Magnitude乘以100是告诉瓢虫,在每个时间间隔(TimerInterval)内,在某个特定的方向,移动的距离 在0到100像素之间。时间间隔为之前在组件设计器中设定的10毫秒。 虽然在连接设备上可以测试瓢虫的移动,但与打包下载到设备上的运行效果相比,瓢虫的速度要么太慢, 要么太快。对于安装运行的应用,如果太慢,可以增加速度;相反,则减小速度。 显示能量水平显示能量水平 在第二个Canvas组件上用一个红色线条来显示瓢虫的能量水平。线条高度为1个像素,宽度为瓢虫的能量 值,取值范围从200(健康)到0(死)。 添加组件添加组件 在组件设计器中,在FieldCanvas下方创建一个新的Canvas组件,命名为EnergyCanvas;设置Width属性 为“Fill parent”,Height属性为1个像素。 创建变量:Energy创建变量:Energy 在块编辑器中,创建一个初始值为200的变量来记录瓢虫的能量水平。(还记得吧,在第2章PaintPot中, 第一次使用变量dotSize)以下是具体步骤: 1. 在块编辑器中,拖出一个initialize global name to块,将name改为energy; 2. 如果energy块的右侧插槽内有其他块,删掉它:选中并按Delete键或直接拖到垃圾桶; App Inventor 编程实例及指南 - 87 -本文档使用 看云 构建 3. 创建一个数组块200(直接输入数字200或拖动Math抽屉中的0块),然后插入initialize global energy to块,如图5-4所示。 图 5-4 将变量energy初始化为200图 5-4 将变量energy初始化为200 图5-5中显示了当鼠标悬浮在初始化变量块的“energy”文本上时,呼出了全局变量energy 的“get”及“set”块; 图 5-5 从初始化变量块中获得set及get块图 5-5 从初始化变量块中获得set及get块 画出能量条画出能量条 我们要在变量energy与红色线条之间建立通信,使线条长度(像素)与能量值相等。为此创建如下两个类 似的组块: 1. 在EnergyCanvas上从(0, 0)点到(energy, 0)点画一条红线,以显示当前的能量水平; 2. 在EnergyCanvas上从(0, 0)点到(EnergyCanvas.Width, 0)点画一条白线,在画新能量水平线之前,清 除当前的能量水平线。(记得前面设置EnergyCanvas.Width为“Fill parent”。) 然而,最好能创建一个过程,能用任何颜色在EnergyCanvas上画任意长度的线。为此,需要定义两个参 数:length(长度)和color(颜色),当程序被调用时,我们只需要指定参数值,就像在MoleMash一章 中调用random integer内置过程一样。下面是创建DrawEnergyLine过程的步骤,如图5-6所示。 1. 进入Procedures抽屉,拖出一个to procedure块; 2. 点击过程名(可能是“procedure” ),改为“DrawEnergyLine”; 3. 点击过程块左上角的蓝色方块,呼出两个块:input及input x; 4. 将input x块插入到input块内,将x修改为color; 5. 重复步骤4:插入第二块input x并命名为“length”; 6. 按照图5-6所示,为该过程添加的其余的块:将鼠标悬停在to DrawEnergyLine块的参数color及length 文本上,获得get color及get length块;或者从Variables抽屉中直接拖出get块,插入到to DrawEnergyLine内部的块中,点击下拉菜单选择color或length。 App Inventor 编程实例及指南 - 88 -本文档使用 看云 构建 图 5-5a 为DrawEnergyLine过程添加输入(参数)图 5-5a 为DrawEnergyLine过程添加输入(参数) 图 5-6 定义过程DrawEnergyLine图 5-6 定义过程DrawEnergyLine 现在,你已经掌握了创建过程的窍门,让我们再写一个DisplayEnergy的过程,两次调用DrawEnergyLine 过程:第一次用来擦除旧线(覆盖整个EnergyCanvas的白线),第二次用来显示新的能量线,如图5-7所 示。 图 5-7 定义过程DisplayEnergy图 5-7 定义过程DisplayEnergy DisplayEnergy过程由以下四行命令组成: App Inventor 编程实例及指南 - 89 -本文档使用 看云 构建 1. 设定画笔颜色为白色; 2. 画一条贯穿EnergyCanvas的横线(1个像素高); 3. 设定画笔颜色为红色; 4. 画一条长度等于energy值的线。  提示:将若干行代码规整到一个过程中,通过调用这个过程来取代逐行地执行这些代码, 这个过程被称作重构,这种强大的技术使得程序更易于维护,也更可靠。在这种情况下,如果我们想 改变能量线的高度或位置,我们只需对DrawEnergyLine过程做一次修改,而不必分两次来完成这一 修改。 饥饿而死饥饿而死 不同于前几章的应用,本游戏设定了结束环节:如果瓢虫吃不到足够的蚜虫,或者被青蛙吃掉,则游戏结 束。此时我们希望瓢虫不再移动(设置Ladybug.Enabled为false),并将活瓢虫图片换成死瓢虫(将 Ladybug.Picture设置为已上传的图片文件名)。GameOver过程的创建如图5-8所示。 图 5-8 定义GameOver过程图 5-8 定义GameOver过程 再按图5-9所示向UpdateLadybug(由Clock.Timer每10毫秒调用一次)添加红框内的代码: 减少瓢虫的能量(energy = energy - 1); 显示新的能量水平(call DisplayEnergy); 如果energy值为0则游戏结束。  测试:你可以在设备上测试这段代码,并验证能量水平随时间的减少,并最终导致瓢虫死 亡。重启应用可以点击“Reset Connection->AI Companion”。 App Inventor 编程实例及指南 - 90 -本文档使用 看云 构建 图 5-9 UpdateLadybug过程的第二个版本图 5-9 UpdateLadybug过程的第二个版本 添加蚜虫添加蚜虫 下面来添加蚜虫,即让蚜虫在FieldCanvas上浮动。如果瓢虫撞上蚜虫(视同“吃”掉它),则瓢虫的能量 水平升高,而蚜虫消失,且稍后会再次出现。(在用户看来,这完全是另一只蚜虫,但实际上是同一个 ImageSprite组件。) 添加一个ImageSprite添加一个ImageSprite 添加蚜虫首先要回到组件设计器,创建另一个ImageSprite,要确保它不落在瓢虫上,命名为Aphid,其属 性设置如下: 1. Picture属性:设置为已上传的蚜虫图像文件; 2. Interval属性:设置为10,即:像瓢虫一样,每10毫秒移动一次; 3. Speed属性:设置为2,因此蚜虫移动不会太快,以便让瓢虫能抓住它。 不必在意它的x、y属性(只要不是在瓢虫上)或title属性,这些可以在块编辑器中设置。 控制蚜虫控制蚜虫 实验发现,蚜虫每隔50毫秒(Clock1跳动5次)改变一次方向的效果最好。可以通过创建第二个Clock组 件,并设定其TimerInterval属性为50毫秒来实现这一效果。但是,我们希望能够尝试不同的技术:使用 random fraction(随机分数)块,每次调用,它都将返回一个≥0但<1的随机数。创建UpdateAphid过 程,并用Clock1.Timer来调用它,如图5-10所示。 App Inventor 编程实例及指南 - 91 -本文档使用 看云 构建 图 5-10 添加UpdateAphid过程图 5-10 添加UpdateAphid过程 块的作用块的作用 定时器每次跳动(每秒100次)都将调用UpdateLadybug及UpdateAphid过程。UpdateAphid过程首先 生成一个介于0到1之间的随机数,例如0.15,如果该数<0.20(在20%的时间里),蚜虫将改变方向,改 变的角度为0到360之间的随机数;如果该数≥0.20(在其余80%的时间里),蚜虫方向保持不变。 瓢虫吃掉蚜虫瓢虫吃掉蚜虫 下一步,当他们碰撞时,让瓢虫“吃掉”蚜虫。幸运的是,App Inventor提供了ImageSprite组件之间的 碰撞检测。问题是:当瓢虫与蚜虫碰撞时,会发生哪些事情?在继续阅读之前,请你停下来想想这个问 题。 为了处理瓢虫与蚜虫的碰撞,创建EatAphid过程,其具体步骤如下: 瓢虫的能量水平上升50,来模拟享受美食; 让蚜虫消失(设置其Visible属性为false); 让蚜虫停止移动(设置其Enabled属性为false); 让蚜虫移动到屏幕上任意位置(这与MoleMash中移动地鼠遵循了相同的编码方式)。 请对照图5-11检查您的块。如果你还能想到发生其他事情,比如音效,可以自行添加。 App Inventor 编程实例及指南 - 92 -本文档使用 看云 构建 图 5-11 创建EatAphid过程图 5-11 创建EatAphid过程 块的作用块的作用 每次调用EatAphid,变量energy增加50,缓解了瓢虫的饥饿。然后,蚜虫的Visible及Enabled属性都被 设置为false,看上去像是消失了。最后,产生随机的x、y坐标,并调用Aphid.MoveTo,这样,蚜虫会在 一个新位置再次出现(否则,它一出现便会被立即吃掉)。 瓢虫与蚜虫之间的碰撞检测瓢虫与蚜虫之间的碰撞检测 图5-12显示了在瓢虫与蚜虫之间做碰撞检测的代码。 图 5-12 检测并处理瓢虫与蚜虫之间的碰撞图 5-12 检测并处理瓢虫与蚜虫之间的碰撞 块的作用块的作用 当瓢虫与另一个ImageSprite碰撞时,将调用Ladybug.CollidedWith,参数“other”指向任何与瓢虫发 生相撞的ImageSprite。此时,只有蚜虫可以碰撞,但稍后会有青蛙加入进来。我们采用防御性编程方 式,即在调用EatAphid之前,要确认碰撞的对象就是蚜虫;此外还要确认蚜虫可见,否则,蚜虫在被吃掉 之后而重新出现之前,还会与瓢虫再次碰撞。如果缺少这项确认,隐形的蚜虫会被再次吃掉,并引起能量 水平的再次增加,这会让用户感到费解。  提示:防御性编程是一种避免错误的编程方式,当程序被修改时,仍然可以正常工作。在 图5-12中,对other=Aphid的检查并不是绝对必要的,因为此时瓢虫可碰撞的唯一对象就是蚜虫,但 检查可以防止后续程序的错误:当添加另一个ImageSprite(青蛙)时,如果忘记了修改 Ladybug.CollidedWith,程序就会出错。通常来说,程序员修复bug的时间要多余写新代码的时间, 所以多花一点时间尝试防御型编程是非常值得的。 蚜虫的回归蚜虫的回归 最终蚜虫要重新出现,按图5-13所示修改UpdateAphid:仅当蚜虫可见时,令其改变方向(改变一个不可 见的蚜虫岂不是浪费时间。);若蚜虫不可见(如刚刚被吃掉),将有1/20(5%)的机会重新出现,或 者说会被再吃掉。 App Inventor 编程实例及指南 - 93 -本文档使用 看云 构建 图 5-13 修改UpdateAphid使隐形蚜虫起死回生图 5-13 修改UpdateAphid使隐形蚜虫起死回生 块的功能块的功能 UpdateAphid变得有些复杂,让我们仔细推敲一下: 如果蚜虫可见(这应该是常态,除非刚刚被吃掉),UpdateAphid的行为没有变化,即有20%的几率 改变方向; 如果蚜虫不可见(刚被吃掉),则执行“else”部分。首先生成一个随机分数,如果它<0.05(在5% 的时间里),蚜虫再次变得可见并且可用,即,有资格被再次吃掉。 因为Clock1.Timer每隔10毫秒调用一次UpdateAphid,而当蚜虫隐形后,只有1/20(5%)的机会恢复可 见,因此蚜虫平均重现的时间是200毫秒(1/5秒)。 添加重新启动按钮添加重新启动按钮 在测试蚜虫被吃的新功能时,你可能已经注意到,游戏的确需要一个重新启动按钮。(在创建应用过程 中,将应用分解成小的功能模块,完成一块就测试一块,这种开发模式大有裨益。在测试过程中,经常会 发现一些被忽略了事情,一边做一边测一边改,比起整个应用完成之后再回来做修改,要容易得多。)在 组件设计器中,将Button组件添加在EnergyCanvas下方,改名为“ResetButton”,并设置其Text属性 为“重新启动”。 在块编辑器中,创建当RestartButton被点击时的代码,如图5-14所示: 1. 能量水平设置回200; 2. 重新使蚜虫可见并且可用; 3. 重新使瓢虫可用,并将其图片改为活的瓢虫(除非你想要僵尸瓢虫!)。 App Inventor 编程实例及指南 - 94 -本文档使用 看云 构建 图 5-14 按下“重新启动”按钮让游戏重新开始图 5-14 按下“重新启动”按钮让游戏重新开始 添加青蛙添加青蛙 到目前为止,让瓢虫活着并不难,因此我们需要一个捕食者。就是说我们要添加一个奔向瓢虫的青蛙,如 果发生碰撞,瓢虫被吃掉了,游戏结束。 让青蛙追捕瓢虫让青蛙追捕瓢虫 首先回到组件设计器,在FieldCanvas上添加第三个ImageSprite组件Frog,设置其Picture属性为相应的 图片,interval属性为10,Speed为1,让它的移动速度慢于其他生物。 图5-15显示了Clock1.Timer中调用了新创建的过程。 图 5-15 让青蛙向着瓢虫移动图 5-15 让青蛙向着瓢虫移动 App Inventor 编程实例及指南 - 95 -本文档使用 看云 构建 块的功能块的功能 现在你应该可以熟练地使用随机分数来控制事件的发生几率了,这里,青蛙有10%的机会直接直奔向瓢 虫。这里用到了三角函数,但别害怕,你不必明白其中的道理!App Inventor提供了大量的数学函数,也 包括三角函数。本例中使用的ATAN2(反正切)块,返回值是一个角度,该角度由一组给定的x、y值相对 应。(如果你熟悉三角函数,会发现求解ATAN2时所用的y值与你所期望的y值符号正好相反,即y的减法 顺序是反的,这是因为在Android的画布上,向下是y坐标的增加方向,这与标准的x-y坐标系正相反。) 让青蛙吃掉瓢虫让青蛙吃掉瓢虫 现在需要修改碰撞代码,如果瓢虫与青蛙碰撞,能量水平以及能量线都将变为0,且游戏结束,如图5-16 所示。 图 5-16 让青蛙吃掉瓢虫图 5-16 让青蛙吃掉瓢虫 块的功能块的功能 在第一个if(瓢虫与蚜虫的碰撞检测)块的基础上,添加了第二个if块,来检测瓢虫与青蛙的碰撞。如果瓢 虫和青蛙碰撞,将发生三件事情: 1. 变量energy降为0,瓢虫失去生命力; 2. DisplayEnergy被调用,以清除此前的能量线(并绘制新的空白线); 3. 调用前面写过的GameOver过程,以便让瓢虫停止移动,并将图片改为死瓢虫。 瓢虫回归瓢虫回归 RestartButton.Click已经用程序将死瓢虫图片替换成了活的图片。现在,需要添加代码将瓢虫移动到任意 位置。(想想看,在新游戏开始时,如果没有移动瓢虫,会发生什么?瓢虫与青蛙的位置关系如何?)图 5-17显示了游戏重新启动时用来移动瓢虫的块。 App Inventor 编程实例及指南 - 96 -本文档使用 看云 构建 图 5-17 ResetButton.Click的最终版本图 5-17 ResetButton.Click的最终版本 块的功能块的功能 两个版本的RestartButton.Click之间,唯一的差别就是Ladybug.MoveTo块及其参数。调用了两次内置的 随机整数函数,分别用于生成合法的x、y坐标。虽然没有任何设置来防止瓢虫与蚜虫、瓢虫与青蛙的位置 上的重叠,但几率起到了决定性作用。  测试:重新启动游戏,并确信瓢虫出现在一个新的任意位置。 添加音效添加音效 测试游戏时,你可能注意到:当蚜虫或瓢虫被吃掉时,缺少良好的反馈。要添加音效及触觉反馈,请执行 以下操作: 1. 在组件设计器中添加一个Sound组件。设置其Source属性为已上传的声音文件; 2. 进入块编辑器,进行如下操作: 在EatAphid过程中添加Sound1.Vibrate块,参数为100毫秒,以便在蚜虫被吃掉时,设备产生振动; 在Ladybug.CollidedWith中调Sound1.Play,位置在调用GameOver之前,以便当青蛙吃掉瓢虫时发 出叫声。 改进改进 下面这些想法目的是改进游戏,或者让游戏更个性化: 目前,当游戏结束时,青蛙和蚜虫还在移动,这与其Enabled属性有关:在GameOver中将其设置为 false,并在RestartButton.Click中重新设置为true; 设置并显示一个分数,来表示瓢虫的存活时间。你可以用Label来显示一个数值,该数值在 Clock1.Timer内不断递增; App Inventor 编程实例及指南 - 97 -本文档使用 看云 构建 将EnergyCanvas的Height属性增加为2,以便使能量条更加明显,并在DrawEnergyLine内画两条 线,一个在另一个之上。(使用一个过程,而不是复制代码先擦除再重绘能量线,这样做的另一个好 处是:如果你需要修改线的粗细、颜色或位置时,只需要修改一处的代码。) 添加背景图和更多音效来渲染气氛,比如用真声或预警声来提示瓢虫能量水平的降低; 让游戏随时间推移而变得越来越难,如增加青蛙的速度,或降低Interval属性值; 从技术上来说,被青蛙吃掉的瓢虫应该消失。改变游戏规则:如果瓢虫被吃,则隐形;如果是饿死, 则显示死瓢虫图; 将瓢虫、蚜虫和青蛙的图片换成更适合你个人口味的图片,如霍比特人、半兽人以及邪恶的巫师;或 者是反叛星际战斗机、能源舱及帝国战机。 小结小结 已经有两个游戏被你收入囊中(假设你学习了MoleMash),现在你该知道如何创建自己的游戏了,这是 许多新程序员或有志者的目标!具体来说,您学习了: 可以创建多个ImageSprite组件(瓢虫,蚜虫和青蛙),并在它们之间做碰撞检测; 用OrientationSensor可以检测设备的倾斜,而测得的值可用于控制sprite(或你能想到的任何其他对 象)的移动; 一个Clock组件可以控制多个发生频率相同(改变瓢虫和青蛙的方向),或通过使用random fraction 块来控制频率不同的事件。例如,如果你想在一个周期中,有大约1/4(25%)的时间里会发生某事 件,只要将它放在if块中,并设定条件为random fraction的结果<0.25即可; 一个应用中可以使用多个Canvas组件,我们的例子中有两个,一个用于游戏场地,另一个用于变量的 图形化显示(而不是用Label显示); 用户自定义过程可以使用参数(如在DrawEnergyLine 中使用的“color”及“length”)来控制其行 为,这极大地扩展了过程抽象的能力。 另一个游戏中常用的组件是Ball,与ImageSprite唯一不同的是,它的外观是一个被填充的圆形,而不是一 张任意的图片。 资源下载资源下载 frog.png ladybug.png deadladybug.png aphid.png App Inventor 编程实例及指南 - 98 -本文档使用 看云 构建 frog.wav 英汉对照英汉对照 orientation: 方向 field: 场地 ladybug: 瓢虫 aphid: 蚜虫 frog: 青蛙 energy: 能量 restart: 重新开始 chase: 追逐,奔跑 upload: 上载,上传 file: 文件 interval: 间隔 heading: 前进方向 speed: 速度 timer: 计时器 updat: 更新 angle: 角度 magnitude: 幅度 delete: 删除 procedure: 过程 input: 输入 display: 显示 enable: 使有效 reset: 重置 visible: 可见的 App Inventor 编程实例及指南 - 99 -本文档使用 看云 构建 eat: 吃 collide: 碰撞 with: 与... else: 否则 fraction: 分数 App Inventor 编程实例及指南 - 100 -本文档使用 看云 构建 第 6 章 巴黎地图旅游 本章将创建一个“向导”应用,带给你一次巴黎的梦幻之旅。而你的朋友,虽然不能与你同行,也能借此 做一次虚拟的巴黎之旅。创建一个完整的地图应用看似复杂,不过App Inventor提供了ActivityStarter组 件,可以为每个选定的虚拟位置打开对应的谷歌地图。创建过程分为两步,首先通过点选菜单打开埃菲尔 铁塔、卢浮宫以及巴黎圣母院的地图;然后修改有关参数,使应用同时适用于卫星视图及普通地图视图。 学习要点学习要点 本章介绍以下App Inventor组件及概念: ActivityStarter组件:可以在当前应用中打开其他Android应用。本章用它来打开带有多个参数的谷歌 地图; ListPicker组件:用户可以从地点列表中进行选择。 设计组件设计组件 首先创建一个名为“ParisMapTour”的新项目,界面中包含: Image组件——显示一张巴黎的图片; Label组件——显示文字; ListPicker组件——用一个关联按钮来打开列表; ActivityStarter组件:非可视组件。 组件设计如图6-1所示。 App Inventor 编程实例及指南 - 101 -本文档使用 看云 构建 图 6-1 设计器中的巴黎地图旅游图 6-1 设计器中的巴黎地图旅游 表6-1中列出了创建用户界面所用的组件。从Palette中拖出组件,并修改为指定的名称。 表6-1 巴黎地图旅游中用到的组件表6-1 巴黎地图旅游中用到的组件 组件类型组件类型 面板中分组面板中分组 命名命名 作用作用 Image User Interface Image1 在屏幕上显示巴黎的静态图片 Label User Interface Label1 显示文本:用Android发现巴黎 ListPicker User Interface ListPicker1 显示备选目的地列表 ActivityStarter Connectivity ActivityStarter1 选择目的地之后打开地图应用 设置ActivityStarter组件的属性设置ActivityStarter组件的属性 ActivityStarter组件用于在当前应用中,打开其他任何Android应用,如浏览器、谷歌地图,甚至你自己的 应用。当用户从你的应用中打开另一个应用时,可以通过单击“后退”按钮返回到你的应用。在 ParisMapTour应用中,将根据用户选择打开地图应用,来显示指定的地图。用户可以点击“后退”按钮返 回到你的应用中,并继续选择不同的目的地。 ActivityStarter是一个相当底层的组件,需要为它设置一些属性信息,这些信息对于Java Android SDK程 序员来说非常熟悉,但对世界上其他99.999%的人来说都很陌生。在本应用中,输入如表6-2中指定的属 性,并要小心地使用大小写字母,这非常重要。 表6-2 用ActivityStarter打开谷歌地图必须设置的属性表6-2 用ActivityStarter打开谷歌地图必须设置的属性 属性属性 值值 Action android.intent.action.VIEW App Inventor 编程实例及指南 - 102 -本文档使用 看云 构建 ActivityClass com.google.android.maps.MapsActivity ActivityPackage com.google.android.apps.maps 属性属性 值值 还有一个DataUri属性必须在块编辑器中设置,用于在谷歌地图中打开指定地图。这个属性是动态的,即根 据用户对目的地的选择而改变:埃菲尔铁塔、卢浮宫或巴黎圣母院。 现在切换到块编辑器,在编程添加组件行为之前,还有两个细节需要特别关照一下: 1. 下载文件metro.jpg并加载到项目中,将其设置为Image1的Picture属性; 2. ListPicker组件自带一个按钮,当用户点击时将列出备选项。将ListPicker1的Text属性设置为“选择巴黎 的目的地”。 为组件添加行为为组件添加行为 在块编辑器中,需要定义一个目的地列表,并设定两种行为: 当应用开始时,为ListPicker组件加载目的地列表,以供用户选择; 当用户从ListPicker中选择了一个目的地,地图应用将打开,并显示目的地地图。在应用的第一个版本 中,只需打开地图,并搜索选中的地点。 创建目的地列表创建目的地列表 打开块编辑器,用表6-3中列出的块创建一个destinations列表变量。 表6-3 创建destinations列表变量所需的块表6-3 创建destinations列表变量所需的块 块的类型块的类型 所在抽屉所在抽屉 作用作用 Initialize global destinations to Variables 创建一个目的地列表 make a list Lists 添加列表项 “埃菲尔铁塔” Text 第一个目的地 “卢浮宫” Text 第二个目的地 “巴黎圣母院” Text 第三个目的地 如图6-2所示,变量destinations将调用make a list函数,其中插入了三个旅游目的地。 图 6-2 在App Inventor中创建列表非常容易图 6-2 在App Inventor中创建列表非常容易 App Inventor 编程实例及指南 - 103 -本文档使用 看云 构建 让用户选择一个目的地让用户选择一个目的地 ListPicker组件用来显示列表项供用户选择。通过将ListPicker的Elements属性设置为某个list,可以预先将 备选项目加载到ListPicker中。这里将ListPicker的Elements属性设置为刚刚创建的destinations列表。因 为想在应用启动时显示此列表,因此加载列表行为必须在事件Screen1.Initialize中定义。表6-4中列出了所 需的块。 表6-4 在应用启动时加载ListPicker备选项所需的块表6-4 在应用启动时加载ListPicker备选项所需的块 块的类型块的类型 所在抽屉所在抽屉 作用作用 Screen1.Initialize Screen1 应用启动时触发该事件 set ListPicker1.Elements to ListPicker1 将Elements属性设置为需要显示的列表 get global destinations Variables 读取目的地列表 块的作用块的作用 应用启动时触发Screen1.Initialize,如图6-3所示,事件处理程序通过设置ListPicker的Elements属性来显 示三个备选目的地。 图 6-3 在应用启动时需要执行的某些操作必须放在Screen1.Initialize事件处理程序中图 6-3 在应用启动时需要执行的某些操作必须放在Screen1.Initialize事件处理程序中  测试:首先点击”connect”重新连接测试设备;然后在香港老钱庄868525,(六合娃娃上点击 “选择巴黎的目的 地”按钮,出现有三个选项的列表。 使用搜索打开地图使用搜索打开地图 下面编写程序,当用户选中目的地时,ActivityStarter将打开谷歌地图并搜索选定的位置。 当用户选中ListPicker中的项目时,将触发ListPicker.AfterPicking事件;在AfterPicking事件的处理程序 中,需要设置ActivityStarter组件的DataUri属性,让它知道要打开哪里的地图,然后用 ActivityStarter.StartActivity打开谷歌地图。表6-5列出了此功能所需的块。 表6-5 用ActivityStarter打开谷歌地图所需要的块表6-5 用ActivityStarter打开谷歌地图所需要的块 块的类型块的类型 所在抽屉所在抽屉 作用作用 ListPicker1.AfterPicking ListPicker1 当用户选择ListPicker中的某项时触发该事 件 App Inventor 编程实例及指南 - 104 -本文档使用 看云 构建 set ActivityStarter1.DataUri to ActivityStarter1 DataUri告诉Maps在启动时打开哪里的地 图 join Text 将两段文本连接成DataUri “http://maps.google.com/? q=” Text Maps所需的DataUri信息中的第一部分 ListPicker1.Selection ListPicker1 用户选中的项(DataUri信息中的第二部 分) ActivityStarter1.StartActivity ActivityStarter1 打开地图 块的类型块的类型 所在抽屉所在抽屉 作用作用 块的作用块的作用 用户在ListPicker中选定的项存储在ListPicker.Selection中,选择触发了AfterPicking事件。如图6- 4,DataUri属性是“http://maps.google.com/?q=”与选中项组合。如果用户选中了第一项“艾菲尔铁 塔”,则DataUri将被设置为http://maps.google.com/?q=埃菲尔铁塔。 图 6-4 设置DataUri打开选中的地图图 6-4 设置DataUri打开选中的地图 为打开地图设定了ActivityStarter的其他属性,因此ActivityStarter1.StartActivity块将打开地图应用,并 根据DataUri所限定的条件进行搜索。  测试:重新连接测试设备,点击“选择巴黎的目的地”按钮。当选中了某个地点,是否出 现了该地点的地图?谷歌地图提供的“后退”按钮,可以返回到你的应用中并做再次选择,怎么样, 有用吗?(可能需要多次单击后退按钮) 设立虚拟旅游设立虚拟旅游 现在我们给应用增添一点趣味,让应用打开一些超级放大的地图,并欣赏巴黎的街景,以便在你旅游的同 时,你的朋友在家也能跟随你游览的脚步。要做到这一点,你首先要在谷歌地图中找到那些特定地图的 URL322555现场开奖,香港马会开奖结果直播。继续使用相同的巴黎地标性建筑为目的地,但是当用户选中目的地时,使用选中项的索引值 (在列表中的位置),来选择并打开一个指定放大倍数的地图或街景图。 App Inventor 编程实例及指南 - 105 -本文档使用 看云 构建 在进行下一步之前,您可能想把到目前为止创建的简单地图应用保存起来(Save Project As),以便接下 来所做的事情万一让应用出了问题,你还可以随时找回现有版本,并再试一次。 为特定地图寻找DataUri为特定地图寻找DataUri 首先在电脑上打开谷歌地图,搜索某个目的地的具体地图: 1. 在计算机上浏览http://maps.google.com; 2. 搜索地标(例如,艾菲尔铁塔); 3. 放大到你预期的级别; 4. 选择你想要的视图类型(如:322555现场开奖,香港马会开奖结果直播、卫星或街道视图); 5. 点击地图窗口左上部的Link(链接)按钮,(如图6-5)复制地图的URL322555现场开奖,香港马会开奖结果直播。这个网址(或部分网址)将 用于打开你应用中的地图。 图 6-5 从浏览器中获取特定地图的URL322555现场开奖,香港马会开奖结果直播图 6-5 从浏览器中获取特定地图的URL322555现场开奖,香港马会开奖结果直播 表6-6显示了即将使用的URL322555现场开奖,香港马会开奖结果直播。 表6-6 在谷歌地图上完成虚拟之旅的URL322555现场开奖,香港马会开奖结果直播表6-6 在谷歌地图上完成虚拟之旅的URL322555现场开奖,香港马会开奖结果直播 地标地标 地图URL地图URL 埃菲 尔铁 塔 https://maps.google.com/maps?q=%E5%9F%83%E8%8F%B2%E5%B0%94%E9%93%81%E5%A1%94&hl=zh- CN&ie=UTF8&ll=48.858369,2.294482ll=48.858369,2.294482&spn=0.001578,0.004128&sll=37.0625,-95.677068&sspn=61.153041,135.263672& 卢浮 宫 https://maps.google.com/maps?q=%E5%8D%A2%E6%B5%AE%E5%AE%AB&hl=zh- CN&ie=UTF8&ll=48.86061,2.337642ll=48.86061,2.337642&spn=0.003155,0.008256&sll=48.85826,2.294582&sspn=0.001578,0.004128& 巴黎 圣母 院 (街 景) https://maps.google.com/maps?q=%E5%B7%B4%E9%BB%8E%E5%9C%A3%E6%AF%8D%E9%99%A2&hl=zh- CN&ie=UTF8&ll=48.852545,2.348389ll=48.852545,2.348389&spn=0.006311,0.016512&sll=48.861595,2.33522&sspn=0.001585,0.004128& eLwNIAAAQJORRDXw&cbp=12,30.77,,0,-16.32 将表6-6中的网址粘贴到浏览器中,你会看到前两个是放大的卫星视图,而第三个是街景图。 可以用这些URL直接打开地图,也可以用http://mapki.com中列出的谷歌地图协议。例如,仅凭GPS坐标 App Inventor 编程实例及指南 - 106 -本文档使用 看云 构建 来显示埃菲尔铁塔地图。从表6-6的长322555现场开奖,香港马会开奖结果直播中找出这些坐标及地图geo:协议: geo: 48.858369,2.294482&t=h&z=19 使用这样的DataUri打开的地图,与包含这些GPS坐标的长322555现场开奖,香港马会开奖结果直播打开的地图基本相同。t = h特指既可以显 示卫星视图,又可以显示322555现场开奖,香港马会开奖结果直播视图的混合模式,而z = 19特指缩放级别。如果你有兴趣详细了解不同类型 地图的参数设置,请查看http://mapki.com中的文档。 两种类型的URL随你选择用,我们对前两个DataUri使用geo格式,第三个使用长322555现场开奖,香港马会开奖结果直播格式。 定义DataURIs列表定义DataURIs列表 创建列表变量DataURIs,其中包含了每个地图的DataURI,如图6-6所示,其中的选项分别与 destinations列表中的选项相对应(即第一个dataURI对应第一个目的地:埃菲尔铁塔)。 图 6-6 虚拟之旅的地图322555现场开奖,香港马会开奖结果直播列表图 6-6 虚拟之旅的地图322555现场开奖,香港马会开奖结果直播列表 前两项显示了埃菲尔铁塔和卢浮宫的DataURIs,使用geo:协议。第三个DataURI是在太长,无法完整显 示。从表6-6的最后一行复制此网址,并粘贴在text块中。 修改ListPicker.AfterPicking行为修改ListPicker.AfterPicking行为 在第一个版本的ListPicker.AfterPicking中,将ActivityStarter1的DataUri属性设置为字符 串“http://maps.google.com/?q=”与用户所选的目的地(如“埃菲尔铁塔”)的串联(或组合);在 第二个版本中,AfterPicking程序则更为复杂,用户从一个列表(目的地)中选择,而DataUri要从另一列 表(dataURIs)中读取。具体来说,当用户从ListPicker中选中一项,此时需要知道选中项的索引,以便 用这个索引从dataURIs列表中选择正确的DataUri。稍后我们会详细解释索引的含义,但为了更好地描述 这一概念,要先把这些块创建出来,便于我们理解。这项功能需要许多块,均在表6-7中列出。 表6-7 根据用户在第一个列表中的选择,来确定在第二个列表中的选择,需要如下的块表6-7 根据用户在第一个列表中的选择,来确定在第二个列表中的选择,需要如下的块 块的类型块的类型 所在抽屉所在抽屉 作用作用 Initialize global index to Variables 变量用来保存用户所选项的索引 数字1 Math 变量index的初始值设为1 ListPicker1.AfterPicking ListPicker1 当用户选中一项时,触发该事件 set global index to Variables 将变量值设为所选项在列表中的排列位置 App Inventor 编程实例及指南 - 107 -本文档使用 看云 构建 index in list Lists 获得所选项的位置(索引) ListPicker1.Selection ListPicker1 所选项如"埃菲尔铁塔",将其插入index in list的 thing插槽 get global destinations Variables 将其插入index in list的list插槽 set ActivityStarter1.DataUri ActivityStarter1 在启动Activity打开地图之前,设置该属性 select list item Lists 从dataURIs列表中选择一项 get global DataURIs Variables 读取DataURIs列表 块的类型块的类型 所在抽屉所在抽屉 作用作用 块的功能块的功能 当用户选中ListPicker1中的某项时触发AfterPicking事件,如图6-7所示。选中的项,如“埃菲尔铁塔”, 成为ListPicker.Selection。AfterPicking事件的处理程序借此找到所选项在列表destinations中的位置, 即索引值。索引值对应于所选目的地在列表中的位置。所以,如果“艾菲尔铁塔”被选中,则index为1; 如果“卢浮宫”被选中,index为2;同样,如果“巴黎圣母院”被选中,则index为3。 图 6-7 根据用户在一个列表中的选择,在另一个列表中做对应的选择图 6-7 根据用户在一个列表中的选择,在另一个列表中做对应的选择 使用索引在另一个列表(本例中DataURIs)中做选择,选中项被设置为组件ActivityStarter的DataUri属 性。一旦设置完成, ActivityStarter.StartActivity就可以打开地图了。  测试:在香港老钱庄868525,(六合娃娃上点击“选择巴黎的目的地”按钮,将出现有三个选项的列表。选择其中一 个,看看会出现哪张地图。 改进改进 下面是一些可以尝试的改进建议: 创建一个虚拟旅游,目的地富有异国情调,也可以是你的工作场所或学校; App Inventor 编程实例及指南 - 108 -本文档使用 看云 构建 创建一个可定制的虚拟旅游应用,让用户自己输入旅游地点,并地图URL连接,来生成一个旅游向导。 相关数据需要存储到TinyWebDB数据库中,并且让应用可以调用这些已经输入的数据。有关创建 TinyWebDB数据库的例子,请参见出题及测验应用。 小结小结 下面是本章涉及到的概念: 列表变量(List Variable):可用于保存像旅行目的地以及地图URL这样的多条目数据; ListPicker组件:允许用户从项目列表中选择。ListPicker的Elements属性用来保存列表内 容,Selection属性用来保存选中的项,而用户选中后将触发AfterPicking事件; ActivityStarter组件:用于打开另一个应用。本章展示了如何调用地图应用,你也可以打开浏览器或任 何其他的Android应用,甚至是你自己创建的应用。更多信息请参 见http://appinventor.googlelabs.com/learn/reference/other/activitystarter.html(无法访问); 想要打开谷歌地图中的详细地图,可以设置ActivityStarter的DataUri属性。如何确定DataUri的值 呢?可以在浏览器中打开详细地图,然后点击链接按钮,获得并使用URI: 1. 直接将URI设置为ActivityStarter的DataUri属性; 2. 或者使用在http://mapki.com中定义的协议,创建你自己的URI。 Index(索引):根据某一项在整个列表中的位置来定义索引。在ListPicker块中,可以根据用户选中 项在列表中的位置来确定索引。获取索引值是至关重要的,例如在本章中,需要用索引值在第二个相 关的列表中进行选择。关于List变量及ListPicker组件的更多信息,请参见第19章。 背景知识背景知识 ActivityActivity 中文译为“活动”(名词),是开发人员使用的术语。在Android应用中,一个“活动”通常代表一个单独的 屏幕,即App Inventor中的Screen,用来容纳各种用户界面组件、侦听用户事件并做出响应。它有四种基 本状态: 1. 活跃:用户可见并可与之交互; 2. 暂停:用户部分可见但不能与之交互; 3. 停止:用户完全不可见,但仍然停留在内存中; 4. 清除:从内存中将“活动”删除。 HTTPHTTP 超文本传输协议(HyperText Transfer Protocol)。 App Inventor 编程实例及指南 - 109 -本文档使用 看云 构建 hyper: 亢奋的 text: 正文,文本 transfer: 传输 protocol: 协议 Web浏览器、服务器以及相关的web应用程序都是通过HTTP相互通信的。HTTP是现代全球因特网中使用 的公共语言。--摘自《HTTP权威指南》 2012年9月出版的《HTTP权威指南》2012年9月出版的《HTTP权威指南》 HyperTextHyperText App Inventor 编程实例及指南 - 110 -本文档使用 看云 构建 有趣的是,hyper在正式的英汉词典中被译为“亢奋、精力旺盛的”,然而在一个流行的网络词典Urban dictionary(城市词典)中,对它的解释则更为生动形象,这里摘录两段:“当你无法集中注意力,感觉想要 跳起来撞墙...通常是一种自然状态下的兴奋...由糖引起的。” 另一则:“当你吃了大量的糖,而且跳来跳 去高声喊叫并勒令老师闭嘴。”这两种解释中共同的部分是“糖与跳跃”,用这个词来形容一段文字,首 先文字是引人入胜的(糖),其次文字让人跳跃(链接)。而这正是互联网信息给人们的阅读方式带来的改变。 HyperText通常被译为“超文本”,其本意为“有链接的文本”。我们所见到的网页背后就是一个超文本 文件,其中包含的链接体现为两个方面,一是可以链接到其他的网页(另一个超文本文件),二是指向某些网 络资源(也是文件),如图片、声音、视频等。 TransferTransfer 译为“传输”,指的是一个资源从一个位置被转移到另一个位置。比如我们打开一个网页,实际上是一个 文件从服务器端被转移到了个人电脑上,并在浏览器中呈现出来。 ProtocolProtocol 译为“协议”。中文“协议”这个词很容易被理解为“合同”,一种在双方或多方之间建立起来的某种书 面约定。英文“protocol”的含义之一是“礼仪、礼节”,还有一种解释是“外交官及国家元首必须遵从 的仪式和礼节”。想象一下我们的国家主席在天安门广场欢迎外国元首时的一系列安排:是先鸣礼炮呢, 还是先奏国歌呢?他们会说什么呢?先说什么,后说什么?这些可是一点都不能含糊的。俗话说“外交无 小事”。同样,当两台计算机之间进行交流时,也必须遵守某种约定。如果访问者发出的请求无法让被访 问者清楚明白,那么被访问者将不予回应,交流就无法达成;再或者被访问者听懂了访问者的请求,但是 给出的回应让访问者无法理解,这样的交流也是无效的。再举个例子,假如你去法国旅游,你用中文跟当 地人交流,那会是怎样的情景呢?此时,需要为交流设定一种语言(符号体系),假如你会说英文,你就可以 问人家是否也会说英文,如果对方会说英文,这样交流就可以建立起来了。这就是protocol的本质。计算 机之间的通信协议保证了全世界计算机之间的互联互通,它规定了通信的规则,包括了三个方面的要素, 一是建立通信的请求及应答方法,二是交流过程中要传递的数据的格式,三是规定了通信的顺序(时序)。 http协议是众多通信协议中的一种,它规定了互联网上浏览器与服务器之间的通信规则。 URLURL 统一资源定位符(Uniform Resource Locator),由一组字符串构成,用来说明如何访问web服务器上的某项 资源。如“http://www.17coding.net/index.html”,它代表了本网站的主页322555现场开奖,香港马会开奖结果直播(你可能在浏览器322555现场开奖,香港马会开奖结果直播 栏中输入的是www.17coding.net,浏览器替你把输入的内容解释为 http://www.17coding.net/index.html),它由三部分组成:①协议声明“http://”; ②服务器的322555现场开奖,香港马会开奖结果直播 (www.17coding.net);③资源在服务器上的具体位置(文件夹)及名称(文件index.html在服务器的根目录 下)。 URIURI 统一资源标识符(Uniform Resource Identifier),由一组字符串组成,是web服务器上某项资源在全世界 范围内的独一无二的标识。资源从本质上讲就是文件(文件的内容可能是网页、音频、视频、图片等)。现在 App Inventor 编程实例及指南 - 111 -本文档使用 看云 构建 几乎所有的URI都是URL。 DataURIDataURI DataURI可以将小型的资源文件(如小图片)通过编码后直接嵌入到网页内,随页面加载直接加以呈现。与普 通的图片加载相比,可以减少一次对服务器的http请求。不过DataURI的使用仅限于小型的资源文件,而 且可能会影响到页面的加载速度。本章中的ActivityStarter的DataUri属性采用了两种方式,一种是普通的 URI,另一种是谷歌地图的内部协议geo(未公开)。DataURI标准文档 英汉对照英汉对照 activity: 活动 starter: 启动装置 list: 列表 picker: 选择器 palette: 调控面板 metro: 地铁,大都市 destination: 目的地 make: 制造 element: 元素 selection: 选择[名词] 资源下载资源下载 meteo.jpg App Inventor 编程实例及指南 - 112 -本文档使用 看云 构建 第 7 章 安卓,我的车在哪? 你把车停得尽量靠近体育馆,但演唱会一结束,你却忘了车停在哪儿,你的同伴也很茫然。幸运的是,你 的Android香港老钱庄868525,(六合娃娃还在,它从来不忘事,你新装了一款热门应用“Android,我的车在哪儿?”有了这个应 用,在停车时点一下按钮,Android的位置传感器会“记住”车的GPS坐标和322555现场开奖,香港马会开奖结果直播。当稍后重新打开应用 时,它会指给你从现在位置到停车位置的方向,问题解决了! 学习要点学习要点 本章涵盖如下概念: LocationSensor组件:确定Android设备的位置; TinyDB组件:直接在设备数据库中记录数据; ActivityStarter组件:在应用中打开谷歌地图,并显示从一个位置到另一个位置的方向。 准备开始准备开始 登陆App Inventor网站,开始一个新项目“AndroidWhere”(项目名称不能有空格),将屏幕标题设置 为“Android,我的车在哪儿?”,连接测试香港老钱庄868525,(六合娃娃。 设计组件设计组件 应用包含下列可视组件: 多个Label组件:显示当前位置和“记住”的位置信息,有些Label显示静态文本,如GPSLabel显 示“GPS:”;其他Label,如CurrentLatLabel显示来自位置传感器的数据。给这些Label设定一个默 认值(0,0),当GPS取得位置信息时,这个值将随之改变; 两个Button组件:记录位置和指示该位置的方向; 以及三个非可视组件: App Inventor 编程实例及指南 - 113 -本文档使用 看云 构建 LocationSensor组件:获取当前位置信息; TinyDB组件:永久保存位置信息; ActivityStarter组件:用于打开谷歌地图,以获得当前位置和记住位置之间的路线。 按照图7-1所示的组件设计器截图来创建组件。 图 7-1 组件设计器中应用的用户界面图 7-1 组件设计器中应用的用户界面 跟随表7-1,逐个拖出组件,并做相应设置,创建如图7-1所示的用户界面。 表7-1 应用中的所有组件表7-1 应用中的所有组件 组件类型组件类型 面板中分组面板中分组 命名命名 作用作用 Label User Interface CurrentHeaderLabel 显示标题“当 前位置” HorizontalArrangement Screen Arrangement CurrentAddrArrangement 放置322555现场开奖,香港马会开奖结果直播信息 App Inventor 编程实例及指南 - 114 -本文档使用 看云 构建 Label User Interface CurrentAddressLabel 显示“地 址:” Label User Interface CurrentAddressDataLabel 显示动态数 据:当前322555现场开奖,香港马会开奖结果直播 HorizontalArrangement Screen Arrangement CurrentGPSArrangement 安置GPS信息 Label User Interface GPSLabel 显 示“GPS:” Label User Interface CurrentLatLabel 显示动态数 据:当前纬度 Label User Interface CommaLabel 显示“,” Label ser Interface CurrentLongLabel 显示动态数 据:当前经度 Button User Interface RememberButton 点击记录当前 位置 Label User Interface RememberedAddressTitleLabel 显示“已记录 的地点” HorizontalArrangement Screen Arrangement RememberAddrArrangement 安置已保存的 GPS信息 Label User Interface RememberedAddressLabel 显示“地 址:” Label User Interface RememberedAddressDataLabel 显示动态数 据:已记录的 322555现场开奖,香港马会开奖结果直播 HorizontalArrangement Screen Arrangement RememberGPSArrangement 安置已记录的 GPS信息 Label User Interface RememberedGPSlabel 显 示“GPS:” Label User Interface RememberedLatLabel 显示动态数 据:已记录的 纬度 Label User Interface Comma2Label 显示“,” 组件类型组件类型 面板中分组面板中分组 命名命名 作用作用 App Inventor 编程实例及指南 - 115 -本文档使用 看云 构建 Label User Interface RememberedLongLabel 显示动态数 据:已记录的 经度 Button User Interface DirectionsButton 点击来显示地 图 LocationSensor Sensors LocationSensor1 感知GPS信息 TinyDB Storage TinyDB1 永久保存已记 录的位置信息 ActivityStarter Connectivity ActivityStarter1 打开地图 组件类型组件类型 面板中分组面板中分组 命名命名 作用作用 用以下方式设置组件属性: 设置显示静态文本的Label的Text属性为固定文本,参照表7-1; 设置显示动态GPS数据的Label的Text属性为“0.0”; 设置显示动态322555现场开奖,香港马会开奖结果直播的Label的Text属性为“未知”; 取消勾选RememberButton和DirectionsButton的Enabled属性(设置为不可用); 设置ActivityStarter属性(表7-2),以便ActivityStarter.startActivity可以打开谷歌地图。(图7-1中 ActivityStarter的属性显示不完整。)表7-2中未列出的属性可以留空。 **表7-2 打开谷歌地图所要设定的ActivityStarter属性 属性属性 值值 Action android.intent.action.VIEW ActivityClass com.google.android.maps.MapsActivity ActivityPackage com.google.android.apps.maps  提示:ActivityStarter组件可在应用中打开安装在设备上的任何其他Android应用。要打 开地图,表7-2中的属性必须一字不差地输入;要打开其他应用,请参 阅http://appinventor.googlelabs.com/learn/reference/other/activitystarter.html 中的App Inventor文档。 为组件添加行为为组件添加行为 需要为应用设定如下行为: 当LocationSensor读取到位置信息时,将数据填写到相应的Label中,表示传感器已经读取到当前位置 App Inventor 编程实例及指南 - 116 -本文档使用 看云 构建 信息,用户这时可以选择保存此位置信息; 当用户点击RememberButton时,当前位置信息被复制到“已记录的地点”名下的Label中。这些信 息要保存到设备数据库中,以便用户关闭并再次打开应用时,数据不会消失; 当用户点击DirectionsButton时,打开谷歌地图,并显示“已记录”位置的方向; 当应用重新启动时,从数据库中加载“已记录”的位置信息。 显示当前位置显示当前位置 两种情况会触发LocationSensor.LocationChanged事件,(1)传感器首次读取位置信息时;(2)设备 的位置变化,传感器读数更新时。首次读数有时仅需几秒钟,但如果GPS卫星信号受到屏蔽,会一直没有 读数(也与设备的设置有关)。有关GPS和LocationSensor的更多信息,请参见第23章。 在读取到位置信息时,程序要将数据写到相应的Label中。表7-3列出了所有相关的块。 表7-3 读取到位置信息时,用户界面显示这些信息所需要的块表7-3 读取到位置信息时,用户界面显示这些信息所需要的块 块的类型块的类型 所在抽屉所在抽屉 作用作用 LocationSensor1.LocationChanged LocationSensor 当香港老钱庄868525,(六合娃娃收到新的GPS读数时, 触发该事件 set CurrentAddressDataLabel.Text to CurrentAddressDataLabel 将当前322555现场开奖,香港马会开奖结果直播的新数据写入 label LocationSensor1.CurrentAddress LocationSensor 该属性保存了街道322555现场开奖,香港马会开奖结果直播信息 set CurrentLatLabel.Text to CurrentLatLabel 将纬度信息写入相应的label get latitude Variables 插入set CurrentLatLabel.Text to块 的插槽 set CurrentLongLabel.Text to CurrentLongLabel 将经度信息写入相应的label get longitude Variables 插入set CurrentLongLabel.Text to 块的插槽 set RememberButton.Enabled to RememberButton 设置“记住我现在的位置”按 钮属性 true Logic 插入set RememberButton.Enabled to插槽 块的作用块的作用 App Inventor 编程实例及指南 - 117 -本文档使用 看云 构建 如图7-2所示,latitude(经度)和longitude(纬度)是LocationChanged事件的参数,因此可以从 Variables抽屉中抓取;但CurrentAddress则不是参数,而是LocationSensor的属性,因此要从 LocationSensor抽屉里抓取。LocationSensor除了获取GPS位置信息之外,还通过调用谷歌地图,获得了 与位置信息相对应的街道322555现场开奖,香港马会开奖结果直播信息。 图 7-2 使用LocationSensor读取当前位置信息图 7-2 使用LocationSensor读取当前位置信息 事件处理程序还启用了RememberButton,该按钮的初始设置为禁用(未选中),因为在传感器获得读数 之前,用户不需要“记住”什么,而现在我们可以为“记住”行为编写程序了。  测试:用香港老钱庄868525,(六合娃娃(wifi与电脑连接)实时测试位置感知应用是无效的。将程序打包并下载到手 机上:选择“buildApp(provide QR code for .apk)”,按照提示在香港老钱庄868525,(六合娃娃上打开应用。GPS及322555现场开奖,香港马会开奖结果直播 信息显示在屏幕上,同时RememberButton变为可用。 如果没有获得读数,检查一下Android设备的位置及安全性设置,并尝试走到户外。要了解更多信息,请 参见第23章。 记录当前位置记录当前位置 当用户点击RememberButton时,当前位置信息被写入“已记录的地点”下方的label中。表7-4显示了实 现这一功能所需要的块。 表7-4 记录并显示当前位置所需要的块表7-4 记录并显示当前位置所需要的块 块的类型块的类型 所在抽屉所在抽屉 作用作用 RememberButton.Click RememberButton 用户点击按钮时触发 该事件 App Inventor 编程实例及指南 - 118 -本文档使用 看云 构建 set RememberedAddressDataLabel.Text to RememberedAddressDataLabel 将传感器获得的322555现场开奖,香港马会开奖结果直播 信息写入“已记 录”label中 LocationSensor1.CurrentAddress LocationSensor 该属性保存了街道地 址信息 set RememberedLatLabel.Text to RememberedLatLabel 将纬度信息写入“已 记录”label中 LocationSensor1.Latitude LocationSensor 该属性保存了纬度信 息 set RememberedLongLabel.Text to RememberedLongLabel 将经度信息写入“已 记录”label中 LocationSensor1.Longitude LocationSensor 该属性保存了经度信 息 set DirectionsButton.Enabled to DirectionsButton 设置 DirectionsButton的 Enabled属性 true Logic 设置 DirectionsButton的 Enabled属性为真 块的类型块的类型 所在抽屉所在抽屉 作用作用 块的作用块的作用 当用户点击RememberButton时,当前位置信息将写入“已记录”label中,如图7-3所示。 图 7-3 将当前位置信息写入“已记录”label中图 7-3 将当前位置信息写入“已记录”label中 注意到DirectionsButton已可用,这会有点儿小麻烦,因为如果用户立即点击DirectionsButton,记住的 位置也是当前位置,因而地图中不会提供方向有关的信息。但是,人们似乎不会这么做,当用户移动位置 时(例如步行到演唱会),则当前位置将偏离已记录的位置。 App Inventor 编程实例及指南 - 119 -本文档使用 看云 构建  测试:将应用的新版本下载到香港老钱庄868525,(六合娃娃,并再次测试。当单击RememberButton时,当前位置 信息是否被写入到“已记录”的label中? 显示“已记录”位置的方向显示“已记录”位置的方向 当用户点击DirectionsButton 时,应用将打开谷歌地图,地图中显示从用户当前位置到“已记录”位置 (即停车的位置)的方向。 ActivityStarter组件可以打开任何Android应用,也包括谷歌地图,但必须做一些相应的设置。不过像打开 浏览器或地图这样的应用,设置起来相当简单。 打开地图的关键是设置ActivityStarter.DataUri属性,该属性无异于你在浏览器中直接输入的网址。要想搞 清楚这一点,只需在浏览器中打开http://maps.google.com,并询问,比如旧金山与奥克兰之间的方向。 当结果出来时,点击地图的左上部的链接按钮,并检查显示的URL。这正是你在应用中所需要的URL。 所不同的是,带有方向的地图涉及到两个位置,即起点和终点,它们分别用一组特定的GPS坐标来表示 (而非城市之间)。该URL必须采用以下形式: http://maps.google.com/maps?saddr=37.82557,-122.47898&daddr=37.81079,-122.47710 在浏览器中输入网址,说说看,它指引你跨越了那个著名的地标性建筑? 这里需要为URL设定动态参数:起点322555现场开奖,香港马会开奖结果直播(saddr)和终点322555现场开奖,香港马会开奖结果直播(daddr)。在前几章中,你已经学会用 join块将文本连接起来,这里也是如此。将当前位置和已记录位置的GPS数据插入到URL中,设置 ActivityStarter.DataUri属性为URL,然后调用ActivityStarter.StartActivity。表7-5列出了此项功能所需 要的块。 块的作用块的作用 用户点击DirectionsButton时,事件处理程序生成一个地图URL,然后调用ActivityStarter打开地图应用 并加载地图,如图7-4所示,用join创建的URL发送给地图应用。 App Inventor 编程实例及指南 - 120 -本文档使用 看云 构建 图 7-4 生成一个URL,用来打开地图并指示方向图 7-4 生成一个URL,用来打开地图并指示方向 最终的URL包含了地图域名(http://maps.google.com/maps)以及两个URL参数:saddr与daddr,用 来指定方向的起点位置及终点位置。在本应用中,saddr被设定为当前位置的纬度和经度,而daddr被设定 为已记录的停车位置的纬度和经度。 表7-5 打开一张带有方向指示的地图所需要的块表7-5 打开一张带有方向指示的地图所需要的块 块的类型块的类型 所在抽屉所在抽屉 作用作用 DirectionsButton.Click DirectionsButton 用户点击”指示方向”按钮触 发该事件 set ActivityStarter1.DataUri to ActivityStarter1 设置要打开地图的URL join Text 将URL的各组成部分连接起来 “http://maps.google.com/maps? saddr=” Text URL中固定的部分,后面接起 点经纬度 CurrentLatLabel.Text CurrentLatLabel 当前位置的纬度值 “,” Text 放在经纬度值之间的逗号 CurrentLongLabel.Text CurrentLongLabel 当前位置的经度值 “&daddr=” Text URL中的第二个参数,后面接 终点经纬度 RememberedLatLabel.Text RememberedLatLabel 已记录位置的纬度 “,” Text 放在经纬度值之间的逗号 RememberedLongLabel.Text RememberedLongLabel 已记录位置的经度 ActivityStarter1.StartActivity ActivityStarter1 打开地图  测试:用香港老钱庄868525,(六合娃娃下载新的版本并再次测试,一旦取得读数,单击RememberButton然后走 开。当单击DirectionsButton时,地图是否提示您如何追溯你的脚步?点击几次后退按钮。你是否有 回到了你的应用? 永久保存已记录的位置信息永久保存已记录的位置信息 现在已经具备了一个全功能的应用:记住起点位置,并从当前用户所在的位置绘制一张回到起点的地图。 虽然用户“记住”了位置,但假如应用被关闭,然后再重新打开,“记住”的信息也将消失。实际上你希 望用户能够记录下车的位置,关闭应用,走到别处,然后重新启动应用,并获取已记录的车辆所在位置的 方向。 如果你能想起“开车不发短信”应用(第4章),说明你的思路是正确的,我们需要使用TinyDB数据库来 永久保存这些数据,采取的方案也与之前的应用类似: App Inventor 编程实例及指南 - 121 -本文档使用 看云 构建 1. 当用户点击RememberButton时,位置信息存储到数据库中; 2. 当应用启动时,从数据库中加载位置信息并保存到一个变量或属性中。 从修改RememberButton.Click事件处理程序开始,来存储这些要被“记住”的信息。存储纬度、经度和 322555现场开奖,香港马会开奖结果直播三组信息,需要三次调用TinyDB.StoreValue。表7-6列出了所要补充的块。 表7-6 永久保存位置信息所需要的块表7-6 永久保存位置信息所需要的块 块的类型块的类型 所在抽屉所在抽屉 作用作用 TinyDB1.StoreValue(3) TinyDB1 将数据保存在设备数据库中 “address” Text 插入TinyDB1.StoreValue的tag插槽中 LocationSensor1.CurrentAddress LocationSensor1 插入TinyDB1.StoreValue的value插槽 中,永久保存322555现场开奖,香港马会开奖结果直播信息 “lat” Text 插入第二个TinyDB1.StoreValue的tag 插槽中 LocationSensor.CurrentLatitude LocationSensor 插入第二个TinyDB1.StoreValue的value 插槽中,永久保存纬度信息 “long” Text 插入第三个TinyDB1.StoreValue的tag 插槽中 LocationSensor.CurrentLongitude LocationSensor 插入第三个TinyDB1.StoreValue的value 插槽中,永久保存经度信息 块的作用块的作用 如图7-5所示,TinyDB1.StoreValue将LocationSensor属性中的位置信息保存到数据库中。你该记得 在“开车不发短信”中,StoreValue函数有两个参数,tag与value,tag充当已存储数据的标识,value是 你实际想保存的数据,即本例中的LocationSensor数据。 App Inventor 编程实例及指南 - 122 -本文档使用 看云 构建 图 7-5 在数据库中存储被“记住”的位置信息图 7-5 在数据库中存储被“记住”的位置信息 启动应用时读取“记住”的位置信息启动应用时读取“记住”的位置信息 将数据保存在数据库中,是为了以后可以调用它。在本应用中,如果用户在保存了位置信息之后退出应 用,那么当应用重新打开时,你希望从数据库中读出信息并显示给用户。 在前几章中讨论过,应用的启动会触发Screen.Initialize事件,而在启动时从数据库中读取数据是一种惯 例,我们也不例外。 使用TinyDB.GetValue函数来读取存储的GPS数据。要读取的存储数据包括322555现场开奖,香港马会开奖结果直播、纬度及经度,因此要调 用GetValue函数三次。像在“开车不发短信”中一样,要事先检查数据库中否保存了数据(如,第一次启 动应用时,TinyDB.GetValue将返回一个空文本)。 挑战一下自己,看看是否可以独立创建这些块,然后再与图7-6进行比较。 App Inventor 编程实例及指南 - 123 -本文档使用 看云 构建 图 7-6 在应用启动时,从数据库中读取数据,如果数据不为空则显示数据图 7-6 在应用启动时,从数据库中读取数据,如果数据不为空则显示数据 块的作用块的作用 理解这些块的方法是设想用户的使用过程:用户首次打开应用,先保存位置信息,稍后再次打开应用。首 次打开应用,数据库中没有信息可加载,也不必填写“已记录”label或启用DirectionsButton。在后续的 使用中,如果确有数据存储,就要从数据库中加载这些位置信息。 首先用“address”为tag(标签)调用TinyDB1.GetValue函数,之前在存储位置信息时使用过这个tag。 读取的值保存在变量tempAddress中,并检查其是否为空。 if块将检查从数据库中读出的数据。如果TinyDB对指定的tag没有返回值,则返回空文本。首次启动应用时 没有数据可读,直到用户点击了RememberButton。由于变量tempAddress中保存了数据库的返回值, 因此if块将检查tempAddress的长度,如果长度>0,则TinyDB有322555现场开奖,香港马会开奖结果直播信息返回,也表明经纬度.GetValue 读出经纬度信息。当设置完所有信息,最后启用DirectionsButton。  测试:将新版本应用下载到香港老钱庄868525,(六合娃娃,并再次测试。点击RememberButton,并确保“记 住”读数。关闭应用并再次打开。那些数据是否还在? 完整的应用:Android,我的车在哪儿?完整的应用:Android,我的车在哪儿? 图7-7显示了完整的“Android,我的车在哪儿?”应用中所用到的块。 App Inventor 编程实例及指南 - 124 -本文档使用 看云 构建 图 7-7 “Android,我的车在哪儿”应用中所有的块图 7-7 “Android,我的车在哪儿”应用中所有的块 App Inventor 编程实例及指南 - 125 -本文档使用 看云 构建 改进改进 可以尝试如下改进: 创建一个“Android,他们在哪儿?”的应用,让一群人可以了解彼此行踪。无论你在徒步旅行还是在 公园散步,这个应用都有助于你节省时间,甚至可能挽救生命。应用中的数据是共享的,因此要使用 web数据库,用TinyWebDB组件来替代TinyDB。更多信息请参见第22章。 创建一个“行踪”应用,用列表来记录自己的位置改变,即行踪。当记录数达到一定数量时,或超过 一定时间时,开始一个新的“行踪”,因为即使是轻微的位移也会产生一个新的位置读数。这类应用 需要使用列表来存储位置记录,需要帮助时请参见第19章。 小结小结 下面是本章涉及到的概念: LocationSensor组件:可以报告用户的纬度、经度及当前的街区322555现场开奖,香港马会开奖结果直播。当传感器首次获得数据或数据 发生变化(设备移动)时,将触发LocationChanged事件。有关LocationSensor的更多信息,请参见 第23章; ActivityStarter组件:可以在一个应用中打开其他应用,包括谷歌地图。对于地图,需要将 ActivityStarter的DataUri属性设置为想要打开的地图的URL322555现场开奖,香港马会开奖结果直播。如果你想显示两个GPS坐标之间的 方向,URL应该写成下面的格式,你可以用实际位置的GPS坐标来替换下面的示例数 据:http://maps.google.com/maps/?saddr=0.1,0.1&daddr=0.2,0.2; join用来将文本片段拼凑(连击)成单一的文本对象,也可以让静态文本与动态数据相连接。对于地图 URL来说,GPS坐标就是动态数据; TinyDB让数据永久地保存在在香港老钱庄868525,(六合娃娃的数据库中。保存在变量或属性中数据,会随着应用的关闭而丢 失,但存储在数据库中的数据,可以在每次启动应用时被载入。有关TinyDB和数据库的详细信息,请 参见第22章。 资源下载资源下载 AndroidWhere.aia AndroidWhere.apk App Inventor 编程实例及指南 - 126 -本文档使用 看云 构建 第 8 章 总统测验 “总统测验”是一个关于美国前总统的问答游戏。虽然测验的内容与总统有关,但你可以把它当作模板, 来实现对任何题目的测验。 在前几章中,你已经了解了一些编程的基本概念。现在,准备好面对更大的挑战吧。你会发现,无论是编 程技巧,还是抽象思维,这一章都要求你有一个概念性的飞跃。特别需要强调的是,本章将使用两个列表 变量来存储数据——应用中的问题和答案,使用索引变量来跟踪用户正在回答的题目。在本章结束时,对 于创建测验类应用和其他需要使用列表的应用,你已经掌握了必要的知识。 本章假设你已经熟悉了App Inventor的基础知识:使用组件设计器构建用户界面,用块编辑器来定义事件 处理程序并为组件添加行为。如果你还不熟悉,在继续学习之前,请复习前面几章。 在测验中,用户通过单击“下一题”按钮,连续地回答问题,并收到回答是否正确的反馈。 学习要点学习要点 如图8-1所示,本章覆盖以下内容: 定义列表变量:用来存储问题和答案; 使用索引遍历列表,用户每次点击“下一题”按钮时,显示下一个问题; 使用条件语句(if)控制行为:只有在特定条件下才能执行某些操作。在用户测验到最后一题时,将使 用if块来处理程序; 每一道题对应一张不同的图片,要实现图片的切换。 App Inventor 编程实例及指南 - 127 -本文档使用 看云 构建 图 8-1 “总统测验”在香港老钱庄868525,(六合娃娃中图 8-1 “总统测验”在香港老钱庄868525,(六合娃娃中 准备开始准备开始 登陆App Inventor网站,创建新项目“PresidentsQuiz”,并设置屏幕的标题为“总统测验”,连接测试 设备。从appinventor网站下载测验中用到的图片:roosChurch.gif,nixon.gif,carterChina.gif和 atomic.gif。在下一节中将这些图片加载到项目中。 设计组件设计组件 “总统测验”应用的界面很简单:显示问题并允许用户来回答。图8-2显示了应用在组件设计器中的截图, 按图来创建组件。 App Inventor 编程实例及指南 - 128 -本文档使用 看云 构建 图 8-2 组件设计器中的“总统测验”图 8-2 组件设计器中的“总统测验” 首先将下载的图片加载到项目中:单击Media区域的Upload File按钮,选择一个文件(如 roosChurch.gif),其他图片也是如此。然后添加表8-1中列出的组件。 表8-1 “总统测验”应用所需组件表8-1 “总统测验”应用所需组件 组件类型组件类型 面板中分组面板中分组 命名命名 作用作用 Image User Interface Image1 与问题一同显示的图片 Label User Interface QuestionLabel 显示正在回答的问 HorizontalArrangement Layout HorizontalArrangement1 放置答案输入框及“提 交”按钮 TextBox User Interface AnswerText 用户在此输入答案 App Inventor 编程实例及指南 - 129 -本文档使用 看云 构建 Button User Interface AnswerButton 用户点击之后提交答案 Label User Interface RightWrongLabel 显示“正确”或“不正 确”的反馈 Button User Interface NextButton 用户点击进入下一题 组件类型组件类型 面板中分组面板中分组 命名命名 作用作用 按照下面提示设置组件属性: Image1:Picture为roosChurch.gif(最先出现);Width为“Fill parent”,Height为200; QuestionLabel:Text为“问题…”(在块编辑器中输入第一个问题); AnswerText:Hint为“输入回答”,Text为空,放置到HorizontalArrangement1中; AnswerButton:Text为“提交”,放置到HorizontalArrangement1中; NextButton:Text为“下一步”; RightWrongLabel:Text为空。 为组件添加行为为组件添加行为 编程来实现以下行为: 应用启动时,显示第一个问题以及相应的图片; 点击“下一题”按钮时,显示第二题,再次点击,显示第三题,以此类推; 当显示最后一题时,点击“下一题”按钮将回到第一题; 在用户回答问题之后,反馈回答是否正确; 首先按照表8-2的提示,定义两个列表变量:QuestionList用来保存问题,AnswerList用来保存答案。图 8-3显示在块编辑器中创建的两个列表。 表8-2 用于保存问题和答案的列表变量表8-2 用于保存问题和答案的列表变量 块的类型块的类型 所在抽屉所在抽屉 作用作用 Initialize global QuestionList to Variables 保存问题的列表(更名为QuestionList) Initialize global AnswerList to Variables 保存答案的列表(更名为AnswerList) make a list Lists 为QuestionList插入列表项 问题内容(三个) Text 问题 App Inventor 编程实例及指南 - 130 -本文档使用 看云 构建 make a list Lists 为AnswerList插入列表项 答案内容(三个) Text 答案 块的类型块的类型 所在抽屉所在抽屉 作用作用 图 8-3 问题及答案列表图 8-3 问题及答案列表 定义索引变量定义索引变量 在整个测试过程中,每次用户点击“下一题”按钮,都要跟踪用户正在回答的问题。定义变量 currentQuestionIndex作为QuestionList和AnswerList的索引值。表8-3列出了所需的块,图8-4显示了 变量的定义。 表8-3 创建索引表8-3 创建索引 块的类型块的类型 在抽屉在抽屉 作用作用 Initialize global currentQuestionIndex to Variables 保存当前问题(与答案)的索引(位置) 数字1 Math 将currentQuestionIndex的初始值设为1(第 一题) 图 8-4 索引变量的初始值为1图 8-4 索引变量的初始值为1 显示第一个问题显示第一个问题 有了这些变量,就可以为应用设定交互行为。无论是何种应用,渐进式的开发是非常重要的,而且每一步 只定义一个行为。我们首先考虑与问题相关的行为,具体而言,在应用启动时显示列表中的第一道题,稍 后再来处理图片的事情。 代码块的设定应该与列表中的具体问题无关,这样,如果需要更换问题或创建新的测验类应用时,只需改 变列表中的具体问题,而不必修改事件处理程序。 鉴于上述考虑,对于第一道题,不要直接引用“哪位总统在大萧条时期实施了‘新政’?”这样的题目内 容,而是引用“QuestionList的第一个插槽”这样抽象的形式(与具体问题无关)。这样,即使第一个插 App Inventor 编程实例及指南 - 131 -本文档使用 看云 构建 槽中的问题改变了,这些程序块仍然有效。 select list item块用来选择列表中的项,使用中要求指定list(列表)及index(索引)(列表中的位 置)。如果列表中有三个项,可以输入1、2或3作为索引。 第一个行为是,在应用启动时选择QuestionList中的第一道题,将其写入QuestionLabel;还记 得“Android,我的车在哪儿?”的应用吧,如果想让某件事发生在应用启动时,可以将有关指令放在 Screen1.Initialize事件处理程序中,表8-4中列出所需的块。 表8-4 应用启动时加载第一个问题所需的块表8-4 应用启动时加载第一个问题所需的块 块的类型块的类型 所在抽屉所在抽屉 作用作用 Screen1.Initialize Screen1 应用启动时触发该事件 set QuestionLabel.Text to QuestionLabel 将第一道题内容写入QuestionLabel select list Item Lists 从QuestionList中选择第一道题 get Global QuestionList Variables 从其中选择问题的列表 数字1 Math 用索引值1来选择第一道题 块的作用块的作用 应用启动时触发Screen1.Initialize事件。如图8-5所示,变量QuestionList中的第一项被选中,并被写入 QuestionLabel.Text。因此,应用启动时,用户会看到第一道题。 图 8-5 应用启动时选择并显示第一道题图 8-5 应用启动时选择并显示第一道题  测试:连接装有AI伴侣的设备,或点击“connectEmulator”打开Android模拟器。当应 用启动后,你是否看到QuestionList中的第一道题:“哪位总统在大萧条时期实施了'新政'?” 遍历所有问题遍历所有问题 现在为“下一题”按钮的行为编程。之前定义的currentQuestionIndex用来记住用户正在回答的问题,现 在设定当用户单击“下一题”时,为currentQuestionIndex加1(即,从1变为2,或从2变为3,依此类 推),并根据currentQuestionIndex的值来选择并显示新的问题。挑战一下你自己,看看是否可以自己搭 建这些块。完成之后,与图8-6进行对照。 App Inventor 编程实例及指南 - 132 -本文档使用 看云 构建 图 8-6 显示下一题图 8-6 显示下一题 块的作用块的作用 第一行的块让变量currentQuestionIndex递增。如果当前值为1则加到2;如果是2则加到3,以此类推。 一旦currentQuestionIndex值改变,应用将以此来选择新的问题并显示。首次单击“下一 题”时,currentQuestionIndex从1变为2,应用将选择并显示QuestionList中的第二道题:“哪位总统在 1979年实现中美建交?”;第二次单击“下一题”时,currentQuestionIndex从2变为3,应用将选择并 显示QuestionList中的第三道题:“哪位总统因水门事件而辞职?”  提示:花一分钟的时间来比较一下NextButton.Click与Screen.Initialize两个事件处理程序 的差别。在Screen.Initialize中,用具体数字1来选择列表项;而在NextButton.Click中,用索引变量 currentQuestionindex来选择列表项,即选择第currentQuestionindex项,而非第一或第二第三 项,因而点击“下一题”将选中不同的项。这是索引最常见的用法——增加索引值来找到并显示列表 项。 问题是,索引的每次递增,都会转到下一题,那么当测验到最后一题时,怎么办呢?即:当 currentQuestionIndex=3时点击“下一题”,currentQuestionIndex将从3变为4,应用将从问题列表中 选择第currentQuestionIndex项,即第4项,而列表QuestionList中只有3项,此时Android设备将不知所 措并强行退出应用。那么应用如何知道已经测验到最后一题了呢?  测试:测试“下一题”按钮,看看应用运行是否正常。在香港老钱庄868525,(六合娃娃上按“下一题”按钮,是否 显示第二题“哪位总统在1979年实现中美建交?”?应该是的;再按“下一题”,应该出现第三题。 但如果再次点击,就会看到错误提示:“Attempting to get item 4 of a list of length 3.(试图从只 有3个项的列表中获取第4项。)”这就是程序的bug!知道原因吗?在继续阅读之前试试看自己解决 它。 当点击“下一题”按钮时,应用要问一个问题,并根据问题的答案执行不同的操作。既然已知 QuestionList中包含三个问题,问题可以这样来问:“currentQuestionIndex是否>3?”如果是,将 currentQuestionIndex设回1,这样就回到了第一道题。表8-5中列出了所需的块。 表8-5 检查索引值是否到了列表的结尾所需的块表8-5 检查索引值是否到了列表的结尾所需的块 App Inventor 编程实例及指南 - 133 -本文档使用 看云 构建 块的类型块的类型 所在抽屉所在抽屉 作用作用 if Control 判断用户是否正在做最后一题 = Math 检查currentQuestionIndex的值是否为3 get global currentQuestionIndex Variables 放入“=”左边的插槽 数字3 Math 放入“=”右边的插槽 set global currentQuestionIndex to Variables 设为1来转回到第一道题 数字1 Math 设置索引值为1  测试:单击香港老钱庄868525,(六合娃娃上的“下一题”按钮,会照常出现第二题“哪位总统在1979年实现中美建 交?”,继续点击“下一题”,将显示第三题。下面是你真正想测的:如果再次点击,将出现第一题 (“哪位总统在大萧条时期实施了‘新政’?”)。 图 8-7 检查索引值递增图 8-7 检查索引值递增 单击“下一题”时,索引照旧会递增。但程序会检查是否currentQuestionIndex>3(问题的数量)。如 果大于3,则将currentQuestionIndex重新设置为1,并显示第一题;如果≤3,则不执行if块内的程序,并 照常显示当前问题。 图 8-8 检查测验是否到了最后一题(第三题)图 8-8 检查测验是否到了最后一题(第三题) 让测验易于修改让测验易于修改 App Inventor 编程实例及指南 - 134 -本文档使用 看云 构建 如果NextButton.Click中的块能够正常运行,恭喜你,你正在成为一名合格的程序员!但是,如果想在测 验中添加新题目(及答案),该怎么办?这些块还能正常运行吗?为了验证这一点,先在QuestionList中 添加第四道题,并在AnswerList中添加第四个答案,如图8-9。 图 8-9 向两个列表中分别添加一项图 8-9 向两个列表中分别添加一项  测试:多次单击“下一题”按钮,你发现无论点击多少次,第四题始终不出现。知道问题 所在吗?在继续阅读之前,尝试做些修改,以便让第四题出现。 问题出在“最后一题”的判断条件太具体:currentQuestionIndex>3。如果把3改为4,程序正常了,但 问题是,每次增减问题和答案时,都要记着修改判断条件。计算机程序中的这种强相关性最容易导致错 误,特别是当程序变得复杂时。好的对策是让程序的设计与列表中的问题数量无关。这种通用性,对于程 序员来说,当你想创建其他专题的定制测验时,可以让程序的移植更加容易。尤其是在处理动态列表时, 这样做是必须的,例如,测验中允许用户添加新问题(见第10章)。一个通用性好的程序不该与3这样的 具体数字相关联,因为这只对那些有三个问题的测验有效。对currentQuestionIndex的判断条件应该是 QuestionList列表的长度(项数),而不是具体数字。当条件更具通用性时,即使是添加或删除 QuestionList中的项,程序也能正常运行。现在修改NextButton.Click事件处理程序,替换掉具体数字3。 表8-6中列出了所需要的块。 表8-6 检查列表长度所需的块表8-6 检查列表长度所需的块 块的类型块的类型 所在抽屉所在抽屉 作用作用 length of list Lists 询问列表QuestionList中有多少个列表项 get global QuestionList Variables 插入length of list块的list插槽中 块的作用块的作用 If块中将currentQuestionIndex值与QuestionList的列表长度进行比较,如图8-10所示。如果 currentQuestionIndex为5,而QuestionList的长度为4,则currentQuestionIndex将被重新设置为1。值 得注意的是:由于程序块不再与3或任何具体数字相关联,因此无论列表中有多少项,程序都将正常运行。 App Inventor 编程实例及指南 - 135 -本文档使用 看云 构建 图 8-10 采取更加通用的方式检查列表的结尾图 8-10 采取更加通用的方式检查列表的结尾  测试:当单击“下一题”按钮时,程序是否在四个问题间循环?在第四题后是否又回到第 一题? 为每道题切换图片为每道题切换图片 现在程序已经可以遍历所有的问题(而且代码更加聪明灵活,也更抽象),下面来设置图片。眼下无论显 示什么问题,图片都是同一个,我们希望当用户单击“下一题”时,图片与问题相匹配。此前在Media中 载入了四张图片,现在用图片的文件名来创建第三个列表PictureList。然后修改NextButton.Click事件处 理程序,同时切换问题与图片。(想到currentQuestionIndex就说明你已经开窍了!)首先创建列表 PictureList,用图片文件名初始化列表,要保证列表中的文件名与先前加载的图片文件名完全相同。图8- 11显示了PictureList块的样子。 图 8-11 PictureList中用图片文件名来充当列表项图 8-11 PictureList中用图片文件名来充当列表项 下面来修改NextButton.Click事件处理程序,以便图片可以随问题索引的改变而改变。Image组件的 Picture属性用于指定要显示的图片。表8-7中列出了修改NextButton.Click所需的块。 表8-7 显示与问题相匹配的图片所需的块表8-7 显示与问题相匹配的图片所需的块 块的类型块的类型 所在抽屉所在抽屉 作用作用 set Image1.Picture to Image1 改变图片 select list Item Lists 选择一个与当前问题相匹配的图片 global PictureList Variables 从列表中选择一个文件名 get global currentQuestionIndex Variables 选择第currentQuestionIndex项 App Inventor 编程实例及指南 - 136 -本文档使用 看云 构建 块的作用块的作用 rrentQuestionIndex同时充当QuestionList和PictureList两个列表的索引,这要求正确设置各个列表, 如,第一题对应第一个答案及第一张图,第二题对应第二个答案及第二张图,依此类推,这样一个索引值 可用于三个列表,如图8-12所示。举例说明:第一张图roosChurch.gif是罗斯福总统的图(与英国首相丘 吉尔在一起),而“罗斯福”是第一个问题的答案。 图 8-12 每次选择与问题匹配的第currentQuestionIndex张图片图 8-12 每次选择与问题匹配的第currentQuestionIndex张图片  测试:多次点击“下一题”,每次点击是否出现不同的图片? 检查用户答案检查用户答案 现在应用已经可以遍历所有的试题及答案(及匹配答案的图片),这是列表应用的极好案例。但真实的测 验要对用户的回答判断正误。下面添加一些块来告诉用户他的回答是否正确。用户在AnswerText中输入答 案,并点击AnswerButton提交答案;程序用Ifelse块将用户输入与标准答案作比较,并用 RightWrongLabel显示比较结果。表8-8列出了程序中用到的块。 表8-8 用于显示答案是否正确的块表8-8 用于显示答案是否正确的块 块的类型块的类型 所在抽屉所在抽屉 作用作用 AnswerButton.Click AnswerButton 点击AnswerButton按钮时触发该事件 ifelse Control 如果回答正确,做一件事,否则做另一件 事 = Math 判断回答是否正确 AnswerText.Text AnswerText 包含了用户的回答 select list Item Lists 从AnswerList列表中选择当前问题的答案 get global AnswerList Variables 答案的列表 get global currantQuestionIndex Variables 当前用户正在回答的问题的索引值 App Inventor 编程实例及指南 - 137 -本文档使用 看云 构建 set RightWrongLabel.Text to RightWrongLabel 显示回答是否正确 “正确” Text 回答正确时显示 set RightWrongLabel.Text to RightWrongLabel 显示回答是否正确 “不正确” Text 回答错误时显示 块的类型块的类型 所在抽屉所在抽屉 作用作用 块的作用块的作用 在图8-13中,Ifelse块用来检验用户的输入(AnswerText.Text)是否等于AnswerList中的第 currentQuestionIndex项。如果currentQuestionIndex=1,程序将用户的回答与AnswerList中的第一 项“罗斯福”作对比,同样,如果currentQuestionIndex=2,则与AnswerList中的第二项“卡特”作对 比,等等。如果对比结果相同,则执行then块,即RightWrongLabel显示“正确!”;如果对比结果不 同,执行else块,即RightWrongLabel显示“不正确!”。 图 8-13 检查用户的回答,并告诉用户答案是否正确图 8-13 检查用户的回答,并告诉用户答案是否正确  测试:尝试回答一道题,程序会显示你的回答是否正确。分别试验正确和错误的回答。你 会注意到,回答正确,意味着你的输入必须与AnswerList中的答案完全匹配(包括大小写、标点或空 格)。继续测试后面的问题,并确认运行正常。 应用运行正常,但你会看到,当单击“下一题”时,虽然图片和问题都切换到下一题,但“正 确!”或“不正确!”的文本以及前一题中输入的回答仍然显示在屏幕上,如图8-14所示。尽管这一点无 伤大雅,但用户肯定会发现这类的界面问题。将RightWrongLabel及AnswerText清空,需要在 NextButton.Click事件处理程序中添加几个块,表8-9列出了所需的块。 App Inventor 编程实例及指南 - 138 -本文档使用 看云 构建 图 8-14 用户界面上的小问题图 8-14 用户界面上的小问题 表8-9 清除RightWrongLabel及AnswerText的块表8-9 清除RightWrongLabel及AnswerText的块 块的类型块的类型 所在抽屉所在抽屉 作用作用 set RightWrongLabel.Text to RightWrongLabel 需要清空内容的label “” Text 当用户点击“下一题”时,删除对上一题回答 的反馈 set AnswerText.Text to AnswerText 用户对上一题的回答 “” Text 当用户点击“下一题”时,删除对上一题的回 答 块的作用块的作用 用户单击“下一题”时,图8-15中的前两行用于清空RightWrongLabel和AnswerText。 图 8-15 当转入下一题时,清空上一题的答案及对答案的反馈图 8-15 当转入下一题时,清空上一题的答案及对答案的反馈 App Inventor 编程实例及指南 - 139 -本文档使用 看云 构建  测试:回答一个问题,然后点击“提交”,再单击Next按钮,上一题的答案及反馈是否消 失了? 完整的应用:总统知识测验完整的应用:总统知识测验 图8-16与8-17显示了“总统测验”应用中块的最终配置。 图 8-16 “总统测验”应用中块的最终配置(之一)图 8-16 “总统测验”应用中块的最终配置(之一) App Inventor 编程实例及指南 - 140 -本文档使用 看云 构建 图 8-17 “总统测验”应用中块的最终配置(之二)图 8-17 “总统测验”应用中块的最终配置(之二) 改进改进 一旦测验应用开始正常运行,你也许会乐于做一些改进,例如: 现在应用中只显示与问题有关的图片,也可以尝试播放录音或视频片段。在使用声音上,你甚至可以 发展出一款“辩声识曲(Name That Tune)”的应用; 本测验对正确答案的要求过于严格,有几种改进方法:一是使用text.contains块,来检查是用户的输 入中是否包含了真正的答案;另一种方法是给每道题提供多个答案,通过遍历(foreach)来检查是否 与标准答案相匹配;你还可以想办法处理掉那些用户输入的多余空格,或者不做大小写区分,等等; 将测验改为多选题,这需要用另一个列表来保存每个问题的可选答案。答案也可能是一个二级列表, 第二级列表中保存着特定问题的可选答案。使用ListPicker组件,让用户来选择答案。更多关于lists的 内容请参见第19章。 小结小结 下面是本教程中所涉及到的概念: 将应用程序划分为数据(通常保存在列表中)及事件处理程序两个部分;使用Ifelse块来做条件判断, 有关条件语句的更多信息,请参见第18章; 在事件处理程序中,程序块只能引用抽象的名称来指代列表项及列表长度,以便当列表数据发生变化 时,程序还可以正常运行; 索引变量可以跟踪当前项在列表中的位置,当索引递增时,要小心列表的末尾,使用if块来处理应用中 的行为。 App Inventor 编程实例及指南 - 141 -本文档使用 看云 构建 第 9 章 木琴 很难相信,利用技术来记录和播放音乐只能追溯到1878年,也就是爱迪生获得留声机专利时。时至今日, 我们已经有了长足的进步,音乐合成器、光盘、采样和混音、播放音乐的香港老钱庄868525,(六合娃娃,甚至是拥塞的远程互联 网。在本章中,通过创建一个可以录制和播放音乐的木琴应用,你也将成为这种进步的推动者。 作品描述作品描述 如图9-1所示,这个应用(最初由App Inventor团队的Liz Looney创建)可以做到: 图 9-1 木琴应用的用户界面图 9-1 木琴应用的用户界面 通过触摸屏幕上的彩色按钮播放八个不同的音符; 按“播放”按钮,回放之前弹奏的音符; App Inventor 编程实例及指南 - 142 -本文档使用 看云 构建 按“重置”按钮清除之前弹过的音符,以便输入新曲。 学习要点学习要点 本章程涵盖了以下概念: 使用单一的声音组件来播放不同的音频文件; 使用Clock组件来计算并实现两个音符之间的延迟; 在创建一个过程时做判断; 创建能够自我调用的过程; 列表的高级应用,包括添加、删除及读取项。 准备开始准备开始 登陆App Inventor网站,创建新项目“Xylophone”,屏幕标题设置为“木琴”,并连接到测试香港老钱庄868525,(六合娃娃或模 拟器。 设计组件设计组件 应用中有13个不同的组件(其中8个Button组成了乐器键盘),见表9-1。要在编程之前一次性创建这么多 组件,显得有些乏味,因此我们按功能将应用划分为若干部分,分步来创建组件,这需要在组件设计器与 块编辑器之间反复切换,就像第五章“瓢虫快跑”应用中一样。 表9-1木琴应用的所有组件表9-1木琴应用的所有组件 组件类型组件类型 面板中分组面板中分组 命名命名 作用作用 Button User Interface Button1 播放低音C Button User Interface Button2 播放D Button User Interface Button3 播放E Button User Interface Button4 播放F Button User Interface Button5 播放G Button User Interface Button6 播放A App Inventor 编程实例及指南 - 143 -本文档使用 看云 构建 Button User Interface Button7 播放B Button User Interface Button8 播放高音C Sound Media Sound1 播放音符 Button User Interface PlayButton 回放曲子 Button User Interface ResetButton 清除保存的曲子,开始新 曲 HorizontalArrangement Layout HorizontalArrangement1 放置“播放”及“重 置”按钮 Clock User Interface Clock1 记录音符之间的延迟 组件类型组件类型 面板中分组面板中分组 命名命名 作用作用 创建键盘创建键盘 用户界面中包含了从低音C到高音C的大调五声(七音符)音阶的八个音符键盘,本节将创建这样的音乐键 盘。 创建第一个音符按钮创建第一个音符按钮 首先创建前两个木琴键,用按钮来实现: 1. 从面板(palette)的user interface组中拖出一个按钮,保留Button1的名称,我们希望它像木琴的键 一样,是一个洋红色(Magenta)的长条,因此做如下设置: BackgroundColor属性:为洋红色(Magenta); Text属性:为“C”; Width属性:为“Fill parent”,使其占满屏幕; Height属性:为40像素。 2. 重复上述步骤创建第二个按钮,名为Button2,放在Button1下面。Width及Height属性值同 Button1,但BackgroundColor属性设为红色,Text属性设置为“D”。 (稍后将重复步骤2来创建其余六个音符按钮。)在组件设计器中看起来如图9-2所示。 App Inventor 编程实例及指南 - 144 -本文档使用 看云 构建 图 9-2 用按钮来充当音符按键图 9-2 用按钮来充当音符按键 在香港老钱庄868525,(六合娃娃上的显示看起来与此相似,只是两个彩色按钮之间没有空白。 添加Sound组件添加Sound组件 我们不能让木琴没有声音,创建一个Sound组件,名字为Sound1。MinimumInterval(最小间隔)属性 设置为0(默认值为500毫秒)。这可以让我们的演奏要多快有多快,而不必等半秒钟(500毫秒)。不必 设置Source属性,稍后我们会在块编辑器中设置。 下载1.wav和2.wav,并加载到项目中。与前几章不同,这里的声音文件必须保持原有文件名,不能修改, 理由稍后就会明晰。后面还有六个声音文件需要加载。 声音与按钮的连接声音与按钮的连接 当某个按钮被点击时,用程序来实现播放声音的行为,即:如果Button1被点击,则播放1.wav,如果 Button2被点击,播放2.wav,等等。切换到块编辑器,如图9-3所示,进行以下设置: 1. 从Screen1项下的Button1抽屉里拖出Button1.Click块; 2. 从Sound1抽屉里拖set Sound1.Source块,放置在Button1.Click块中; App Inventor 编程实例及指南 - 145 -本文档使用 看云 构建 3. 输入“text”来创建一个文本块(而不是从Built-in项下的Text抽屉里拖出,这样更便捷。)设置文本值 为“1.wav”,并与Sound1.Source块连接; 4. 添加Sound1.Play块。 图 9-3 点击按钮时播放声音图 9-3 点击按钮时播放声音 对Button2进行同样设置,如图9-4(只改了文件名),代码几乎完全重复。 图 9-4 添加更多的声音图 9-4 添加更多的声音 重复的代码提示我们最好是创建一个过程,像在第3章“打地鼠”和第5章“瓢虫快跑”中那样。具体来 说,我们将创建一个带数字参数的过程,将Sound1的Source属性设置为相应的声音文件,并播放该声音 文件。这是对程序进行重构改进而又不改变程序行为的又一个例子,这一概念在“打地鼠”一章中首次引 入。用join块将数字(如1)与文本“.wav”连接起来,创造出正规的文件名(如“1.wav”)。下面是创 建这个过程的步骤: 1. 在块编辑器中打开Procedures抽屉,拖出“to procedure”块; 2. 单击procedure将过程名改为playNote; 3. 点击procedure块左上角的蓝色方块呼出内部组件,将一个input x块插入“inputs”块; 4. 将input x块中的x改为number; 5. 将set Sound1.Source to块从Button1.Click事件处理程序中拖出,放在PlayNote过程内“do”的右 边,Sound1.Play块也将随之移动; App Inventor 编程实例及指南 - 146 -本文档使用 看云 构建 6. 将1.wav块拖入垃圾桶; 7. 从Text抽屉中拖出join块放到set Sound1.Source to的插槽内; 8. 将鼠标悬停在playNote的number参数上,呼出并拖动get number块,并将其放入join块的第一个插槽 中; 9. 从Text抽屉中拖出空文本块,放在join块的第二个插槽中; 将文本值设置为“.wav”。(切记不要输入引号); 从Procedures抽屉中拖出call PlayNote块,放到空的 Button1.Click内; 在number插槽中插入文本“1”。 现在,当Button1被点击时,过程PlayNote将以数字1为参数被调用。该过程将Sound1.Source属性设 为“1.wav”,并播放该声音。 创建一个Button2.Click块,调用参数为2的PlayNote过程。(可以复制现有的PlayNote块,将其移动到 Button2.Click块内,并将参数更改为2;也可以复制整个Button1.Click块,然后将Button1改为 Button2,再将参数1改为2。)程序如图9-5所示。 图 9-5 创建一个过程来演奏音符图 9-5 创建一个过程来演奏音符 告诉Android加载声音 此时在香港老钱庄868525,(六合娃娃上测试程序会让你失望:第一次按键时,不但没听到预想的声音,香港老钱庄868525,(六合娃娃还弹出错误提 示:“Error 703:Unable to play 1.wav”(不能播放1.wav);第二次再按同一个键时,才听到声音。 这是因为Android系统是在程序运行时才加载声音文件(只需加载一次),加载过程需要一点时间。第一 App Inventor 编程实例及指南 - 147 -本文档使用 看云 构建 次按键,当call Sound1 play块开始执行时,set Sound1.Source to块的加载任务尚未完成,因此系统给 出错误提示;等到第二次按键时,声音文件已经加载完成,因此可以正常播放。为什么前几章没有出现过 这个问题?因为我们在组件设计器中预先设置了Sound组件的Source属性为某个声音文件,当程序启动 时,声音文件会自动加载。而这里,直到程序启动之后,我们也没有对Sound1.Source进行设置,因此没 有对声音做初始化。我们必须在程序启动时直接加载声音文件,如图9-6所示。 图 9-6 在应用启动时加载声音文件图 9-6 在应用启动时加载声音文件  测试:在香港老钱庄868525,(六合娃娃中重新启动应用,按键之后立刻播放声音。(如果你没有听到声音,请确保 香港老钱庄868525,(六合娃娃上的媒体音量没有被设置为静音。) 实现其余的音符实现其余的音符 两个按钮已经实现了演奏音符的功能,现在需要回到组件设计器,加载其余六个声音文件3.wav、4.wav、 5.wav、6.wav、7.wav和8.wav,并添加其余六个音符。首先创建六个新Button组件,重复此前的步骤, 但Text及backgroundColor属性的设置有所不同,具体设置如下: Button3(“E” Pink / 粉红色) Button4(“F”,Orange / 橙色) Button5(“G”,Yellow / 黄色) Button6(“A”,Green / 绿) Button7(“B”,Cyan / 青色) Button8(“C”,Blue / 蓝) Button8的TextColor属性需要改为白色,这样更加醒目,如图9-7所示。 App Inventor 编程实例及指南 - 148 -本文档使用 看云 构建 图 9-7 在组件设计器中放置其余的声音按钮图 9-7 在组件设计器中放置其余的声音按钮 回到块编辑器中,为每个新按钮创建Click块并以相应的参数调用PlayNote过程。同样,在 Screen.Initialize中加载新的声音文件,如图9-8所示。 图 9-8 对按钮单击事件编程,使得键盘与音调相对应图 9-8 对按钮单击事件编程,使得键盘与音调相对应 App Inventor 编程实例及指南 - 149 -本文档使用 看云 构建  测试:现在所有按钮都已经就绪,点击不同按钮会演奏不同的音符。 记录并回放音符记录并回放音符 用按键来弹奏音符的确有趣,但如果能录制并播放歌曲岂不更好。为了实现回放功能,需要记录弹奏的音 符并加以保存。除了要记录弹奏的音高(声音文件),还要记录两个音符之间的时间长度,否则将无法表 现两个连续快弹音符与两个间隔10秒的音符之间的差别。 我们需要维护两个列表,每弹奏一个音符,两个列表中都会各自添加一条记录: notes:包含与演奏的音符相对应的声音文件名,按照演奏顺序排列; times:记录音符演奏时的时间点。  提示:在继续之前,不妨复习一下在“总统测验”中所学到的关于列表的知识。 我们可以从Clock组件中得到计时信息,因此也可以用来正确地设定音符的回放速度。 添加组件添加组件 在设计器中添加一个Clock组件及“播放”和“重置”按钮,按钮放在HorizontalArrangement中: 1. 拖入一个Clock组件,它将出现在“不可见组件”区域,取消勾选TimerEnabled属性,因为我们希望在 回放期间,计时器听从我们的调遣,适时地启动并完成计时; 2. 从layout组中拖出一个HorizontalArrangement组件放在按钮下面,Width属性设为“Fill parent”; 3. 从User Interface组中拖动一个按钮,改名为PlayButton,Text属性设为“播放”; 4. 拖出另一个按钮并放在PlayButton右侧,改名为ResetButton,Text属性设为“重置”。 图9-9中显示了应用在设计视图中的外观。 App Inventor 编程实例及指南 - 150 -本文档使用 看云 构建 图 9-9 记录并回放声音的组件被添加到设计器中图 9-9 记录并回放声音的组件被添加到设计器中 记录音符及时间记录音符及时间 回到块编辑器中,为组件添加正确的行为。我们需要维护两个列表:notes与times,每次用户按下一个按 钮,就向列表中添加一项: 1. 从Variables抽屉中拖出一个initialize global name to块来定义一个新的变量; 2. 单击“name”将变量命名为“notes”; 3. 打开Lists抽屉,拖动一个make a list块,将其放置在变量notes的插槽中; 这样就定义了一个名为“notes”的空列表。重复上述步骤定义另一个变量,命名为“times”。块的样子 如图9-10所示。 App Inventor 编程实例及指南 - 151 -本文档使用 看云 构建 图 9-10 设置变量来记录音符图 9-10 设置变量来记录音符 块的功能块的功能 每演奏一个音符,需要保存两项数据:声音文件名(保存到notes列表),以及演奏瞬间的时刻(保存到 times列表)。用Clock1.Now块来记录时刻,它返回当前时刻的时间值(例如,2011年3月12日上午8时 33分14秒),精确到毫秒。这些数据可以通过Sound1.Source和Clock1.Now块获得,将分别被添加到 notes及times列表中,如图9-11所示。 图 9-11 将演奏的声音添加到列表中图 9-11 将演奏的声音添加到列表中 例如,如果你演奏“哆来咪哆咪哆咪”[CDECECE],你的列表中最终会有七条记录,可能是: notes:1.wav,2.wav,3.wav,1.wav,3.wav ,1.wav,3.wav times[日期省略]:12:00:01,12:00:03,12:00:04,12:00:05,12:00:06,12:00:07,12:00:08 当用户按下“重置”按钮时,我们希望清空这两个列表。由于用户看不到清空带来的任何变化,因此添加 一个Sound1.Vibrate块,通过振动来告知用户按键生效了,这种设置对用户来说是非常友好的。图9-12显 示了这一功能用到的块。 App Inventor 编程实例及指南 - 152 -本文档使用 看云 构建 图 9-12 为用户的“重置”操作提供反馈图 9-12 为用户的“重置”操作提供反馈 音符的回放音符的回放 作为一个思想实验,先来考虑如何实现音符的回放,而暂时忽略回放速度。我们可以(但不会)通过创建 图9-13中的那块来实现这个暂时的目标: 变量count用来跟踪notes列表中当前正在播放的音符的索引(位置); 新过程 PlayBackNote,用来播放当前音符,并移动到下一个音符; 编写PlayButton.Click事件处理程序,设置count为1,只要列表中有保存的音符,就调用 PlayBackNote。 App Inventor 编程实例及指南 - 153 -本文档使用 看云 构建 图 9-13 回放被记录下来的音符图 9-13 回放被记录下来的音符 块的功能块的功能 这可能是你第一次看到能自我调用的过程。这件事乍一看好像不可能,但实际上这是计算机科学中一个非 常重要的概念:强大的递归。 为了更好地了解递归的工作原理,我们来一步一步地探究,当用户演奏了三个音符( 1.wav、 3.wav和 6.wav),然后按下“播放”按钮时,都发生了什么。PlayButton.Click首先判断列表中是否保存了音符: 由于notes列表长度3>0,列表不空,因此设定count等于1,并调用PlayBackNote: 1. 在第一次调用PlayBackNote时,count= 1: Sound1.Source被设置为在notes中的第1项,即1.wav; 调用Sound1.Play,播放1.wav; 由于count值(1)小于notes的长度(3),因此count递增为2,并再次调用PlayBackNote; 2. 第二次调用PlayBackNote时,count=2: Sound1.Source被设置为notes中的第2项,即3.wav; 调用Sound1.Play,播放3.wav; 由于count(2)小于notes的长度(3),因此count递增为3,并再次调用PlayBackNote; 3. 第三次调用PlayBackNote时,count=3: App Inventor 编程实例及指南 - 154 -本文档使用 看云 构建 Sound1.Source被设置为notes中的第3项,即6.wav; 调用Sound1.Play,播放6.wav; 由于count(3)不小于notes的长度(3),因此跳出if块,回放结束。  提示:虽然递归功能强大,但运用起来存在危险。来做一个思想实验:问问自己,如果程 序员忘了在PlayBackNote块中插入count递增的块,会发生什么事情。 这里的递归是正确的,但这个例子中还有另一个问题:在两次调用Sound1.Play之间几乎没有时间间隔 (程序运行的速度非常快),因此每个音符都被下一个音符截断,除了最后一个。所有音符(除了最后一 个)都等不到播放完,Sound1的source属性就已经被改写为下一个音符,并由Sound1.Play播放出来。为 了获得正确的行为,需要在两次调用PlayBackNote之间添加延迟功能。 播放适当延迟的音符播放适当延迟的音符 延迟的设定与两个音符之间的时间差有关,我们用clock来为这个时间差计时。例如,如果时间差为3,000 毫秒(3秒),则将Clock1.TimerInterval设置为3000,并启动计时器;在计时结束时再调用 PlayBackNote。对PlayBackNote的if块做出修改,如图9-14所示。创建Clock1.Timer事件并编写事件处 理程序,来说明计时结束时将发生的事情。 图 9-14 在音符之间加入延迟图 9-14 在音符之间加入延迟 块的功能块的功能 现在假设两个列表中记录了以下内容: notes:1.wav,3.wav,6.wav times:12:00:00,12:00:01,12:00:04 如图9-14所示,在PlayButton.Click中设置count为1,并调用PlayBackNote。 App Inventor 编程实例及指南 - 155 -本文档使用 看云 构建 1. 第一次调用PlayBackNote时,count= 1: Sound1.Source被设置为notes中的第1项,即“1.wav”; 调用Sound1.Play播放1.wav; 因为count(1)小于notes的长度(3),于是Clock1.TimerInterval被设置为times列表中的第1项 (12:00:00)与第2项(12:00:01)之间的时间差:1秒。Count递增到2,启用Clock1.Timer并开始计 时; Clock1.Timer开始计时,间隔1秒之后,计时结束,定时器暂时禁用,并调用PlayBackNote。 2. 第二次调用PlayBackNote时,count= 2 : Sound1.Source被设置为notes中的第2项,即“3.wav”; 调用Sound1.Play播放3.wav; 因为count(2)小于notes的长度(3),于是Clock1.TimerInterval被设置为times列表中的第2项 (12:00:01)与第3项(12:00:04)之间的时间差:3秒。Count递增到3,启用Clock1.Timer并开始计 时; Clock1.Timer计时开始,间隔3秒之后,定时器暂时禁用,并调用PlayBackNote。 3. 第三次调用PlayBackNote时,count= 3 : Sound1.Source被设置为notes中的第3项,即“6.wav”; 调用Sound1.Play来播放6.wav; 由于count(3)不小于notes的长度(3),跳出if块,回放完成。 改进改进 下面是一些可供探讨的备选方案: 目前,在回放过程中,没有对用户点击ResetButton做任何限制,这将导致程序的崩溃(错误提示: select list item: Attempt to get item number 4 of a list of lengh 0。)(你知道原因吗?)修改 PlayButton.Click,让ResetButton在回放期间禁用,回放完成后再重新启用。将PlayBackNote中的if 块改为ifelse块,并在“else”中重新启用ResetButton。 类似问题也发生在PlayButton上,用户可以在回放过程中再次点击该按钮。(想象一下会发生什么。 ) 在PlayButton.Click中禁用PlayButton,并将其Text属性改为“播放中...... ”,并像ResetButton一 样,在PlayBackNote的ifelse块中重新启用该按钮,并重置Text属性。 添加一个按钮来显示一首歌曲的名字,如“致爱丽丝”。当用户单击时,向notes及times列表中填写 相应的值,将count设定为1,并调用PlayBackNote。有一个非常有用的块 App Inventor 编程实例及指南 - 156 -本文档使用 看云 构建 Clock1.MakeInstantFromMillis(用毫秒设置时间间隔),可以用来设定音符之间的延迟。 如果用户按下一个音符,然后去做别的事情了,几小时后回来,又按下另一个音符,尽管音符可能属 于同一首歌,但这绝不是用户的意图。有两种方法可以改进程序:(1)在一个合理的时间间隔后,停 止记录音符,如1分钟;(2)通过对Clock1.TimerInterval使用Math抽屉中的max块,来限制音符播 放的时长。 通过改变按钮的外观,如Text、BackgroundColor或ForegroundColor属性,来形象地提示当前正在 播放的音符。 小结小结 以下是本章涵盖的概念: 通过修改Sound组件的Source属性,可以用一个而非八个Sound组件来播放不同音频文件。记住要在 应用初始化时加载声音文件,以免运行时加载所引起的问题。(见图9-6); 列表(Lists)可以为程序提供存储功能,可以在列表中保存用户的操作记录,并在以后对存储内容进 行提取和再处理。我们使用这个功能来录制及播放歌曲; Clock组件可以用来确定当前时间,两个时间值只差为我们提供了两个事件之间的时间间隔; Clock组件的TimerInterval属性可以在程序中设置,就像我们设置两个音符之间的时间间隔一样; 编写一个能自我调用的过程不仅是可能的,有时也是必要的。这种强大的技术称为递归。在编写递归 过程时,一定要确保为程序的退出设定一个基本条件,它的重要性远大于为自我调用设定条件,否则 程序将陷入无限循环。 资源下载资源下载 1.wav 2.wav 3.wav 4.wav 5.wav 6.wav 7.wav 8.wav App Inventor 编程实例及指南 - 157 -本文档使用 看云 构建 第 10 章 出题及答题 第8章的“总统测验”可以被定制成各种测验,但这种定制只对App Inventor程序员有用。只有程序员可 以修改问题和答案,而对于父母、老师或其他用户来说,他们无法创建一个测验或变换问题(除非他们也 学App Inventor!)。 本章将构建一个“出题”应用,“老师”可以在输入表单中创建试题。试题和答案将被存储在Web数据库 中,以便“学生”可以单独访问“答题”应用并参加考试。通过创建这两个应用,你会在概念上产生更大 的飞跃,并学习如何创建一个应用,让用户自行生成数据,并实现用户之间跨应用的数据共享。 “出题”与“答题”这两个应用协同工作,让“老师”可以为“学生”出题。父母可以在长途旅行中做一 些旅行花絮类的应用,以增加孩子们的乐趣;小学教师可以创建“数学突击”一类的小测验;而大学生们 可以创建一系列的测验,帮助他们的学习小组来准备期末考试。本章建立在第8章“总统测验”的基础上, 如果你还没学过,在继续本章之前,请先学习第8章。 本章将设计两个应用:针对“老师”的“出题”应用(见图10-1)以及针对“学生”的“答题”应用。 在“出题”应用中: 用户在输入表单中输入问题及答案; 显示输入的一对问答; 将问题及答案存储在数据库中。 App Inventor 编程实例及指南 - 158 -本文档使用 看云 构建 图 10-1 出题应用图 10-1 出题应用 “答题”应用的功能与之前的“总统测验”类似。事实上,是以“总统测验”为起点创建“答题”应用, 不同的是,这里的问题是使用“出题”应用输入并保存在数据库中的。 学习要点学习要点 “总统测验”是一个使用静态数据的应用范例:不管用户做多少次测验,问题都是一样的,因为问题被写 在程序中(称为“硬编码”)。新闻应用、博客以及像Facebook和Twitter这类的社交网络应用采用的是 动态数据,这意味着数据随时在改变。通常这种动态信息由用户生成,这类应用允许用户输入、修改并共 享信息。在“出题”与“答题”应用中,将学习创建一个应用,来处理用户生成的数据。 在第9章“木琴”应用中,我们首次引入动态列表概念:用户输入的音符被记录在列表中。由用户生成数据 的应用更为复杂,而且使用的块也更抽象,因为没有预设的静态数据可供参照。尽管可以定义列表变量, 但不能设置具体的项。在编写程序的同时,需要设想最终用户输入的数据被添加到列表中。 本章涵盖了App Inventor中的如下内容: 输入表单:允许用户输入信息; 显示来自多个列表的数据项; 永久保存数据:“出题”应用将问题和答案保存到网络数据库中,“答题”应用将从同一个数据库中 加载它们; 数据共享:使用TinyWebDB组件(而不是之前的TinyDB)将数据存储在Web数据库中。 准备开始准备开始 登陆App Inventor网站,创建新项目“MakeQuiz”,屏幕标题设为“出题”,并连接到测试香港老钱庄868525,(六合娃娃或模拟 App Inventor 编程实例及指南 - 159 -本文档使用 看云 构建 器。 设计组件设计组件 使用组件设计器来创建用户界面,如图10-2所示(图的后面有更详细的说明),组件清单列于表10-1中。 从Palette中拖出组件,将名称改为表中的命名。注意,标题Label的名称(Label1 – Label3)不必改,就 用它们的默认值(因为在编辑器中不会使用这些名称)。 表10-1 “出题”应用中的所有组件表10-1 “出题”应用中的所有组件 组件类型组件类型 面板中分组面板中分组 命名命名 作用作用 TableArrangement Layout TableArrangement1 格式化表单,包括问题及答案 Label User Interface Label1 提示“问题:” TextBox User Interface QuestionText 用户在此输入问题 Label User Interface Label2 提示“答案:” TextBox User Interface AnswerText 用户在此输入答案 Button User Interface SubmitButton 用户点击提交问题-答案对儿 Label User Interface Label3 显示“测验的问题及答案。” Label User Interface QuestionAnswersLabel 显示之前输入的成对的问题答 案 TinyWebDB Storage TinyWebDB1 用数据库保存并提取数据 App Inventor 编程实例及指南 - 160 -本文档使用 看云 构建 图 10-2 组件设计器中的“出题”应用图 10-2 组件设计器中的“出题”应用 按以下方式设置组件属性: 1. 设置Text属性:Label1为“问题:”,Label2为“答案:”,Label3为“试题及答案”; 2. 设置Label3的字号为18,并勾选FontBold属性; 3. 设置QuestionText的Hint属性为“输入问题”,AnswerText的Hint属性为“输入回答”; 4. 设置SubmitButton的Text属性为“提交”; 5. 设置QuestionsAnswersLabel的Text属性为“试题及答案”; 6. 将QuestionText、AnswerText以及与它们相关的Label移入TableArrangement1。 为组件添加行为为组件添加行为 在“总统测验”中,首先定义了两个全局列表变量QuestionList和AnswerList,本章中无需为这两个变量 提供预设的问题和答案,如图10-3所示。 App Inventor 编程实例及指南 - 161 -本文档使用 看云 构建 图 10-3 列表变量初始化图 10-3 列表变量初始化 需要注意,与“总统测验“不同的是,这两个列表没有定义列表项,因为“出题”及“答题”应用中,所 有数据都将由用户创建(即动态的、用户生成的数据)。 记录用户的输入记录用户的输入 首先来处理用户的输入行为。具体来说,当用户输入问题和答案并点击提交时,程序要向列表中添加数据 项来更新QuestionList和AnswerList,如下图所示: 图 10-4 向列表中添加新项图 10-4 向列表中添加新项 块的作用块的作用 向列表中添加项,意味着向列表的末尾追加新项。如图10-4,程序从QuestionText和AnswerText文本框 中获取用户输入的内容,并分别被追加到相应的列表中。 向列表中添加的项更新了列表变量QuestionList和AnswerList,但用户看不到任何变化。第三行的块用来 显示这个变化:用冒号将两个列表的内容连接起来。默认情况下,App Inventor用小括号来包围列表内 容,列表项之间用空格间隔,像这样:(item1 item2 item3)。当然,这不是显示列表的理想方式,只 是暂时用来测试程序的行为。稍后我们将用更高级的方式来显示列表,即,每对问题答案各占一行。 清空问题及答案清空问题及答案 回忆一下在“总统测验”中,当移动到下一题时,要清空上一题的回答结果。在本应用中,当用户提交了 一对问题-答案后,同样要清空QuestionText及AnswerText文本框,以便准备下一题的输入,如下图所 示: App Inventor 编程实例及指南 - 162 -本文档使用 看云 构建 图 10-5 提交问题-答案之后清空文本框图 10-5 提交问题-答案之后清空文本框 块的作用块的作用 用户提交的问题-答案,将分别被添加到各自的列表中,并显示出来,这时QuestionText和AnswerText中 的文本被清空,如图10-5所示。请注意,可以复制一个有内容的文本块(如上图中的“:”块),通过删 除块中的文本,来获得一个空的文本块。 用多行文本显示问题-回答用多行文本显示问题-回答 现在是以App Inventor的默认格式来显示问题及答案。假如有一个有关州首府的测验,已经输入了两对问 题-答案,则显示成: (加州首府在哪? 纽约州首府在哪?):(萨克拉门托 奥尔巴尼)。 可以想像,如果测验中的问题很多,结果会显得非常混乱。理想的显示方式,应该是每行只显示一对问题- 答案: 加州首府在哪? 萨克拉门托 纽约州首府在哪? 奥尔巴尼 第20章讲述了单个列表中项的逐行显示技术,在继续学习之前,可以去阅读一下。 这里的任务稍显复杂,因为涉及到两个列表。为了应对这种复杂性,需要创建过程displayQAs,并从 SubmitButton.Click事件处理程序中调用该过程。 逐行显示问题-答案,需要做到以下几点: 使用foreach块遍历QuestionList中的每个问题; 使用变量answerIndex,在遍历问题的同时,获取与问题对应的答案; 使用join块连接每对问题-答案,并用换行符(\n)来分开每对问题-答案,如下图所示: 块的作用块的作用 过程displayQAs封装了所有用于显示数据的块,如图10-6所示,在需要显示列表时,可直接调用 App Inventor 编程实例及指南 - 163 -本文档使用 看云 构建 displayQAs,而不必再重复使用过程内部的块。 图 10-6 创建displayQAs过程图 10-6 创建displayQAs过程 由于foreach块只能遍历一个列表,而本应用中有两个列表,因此要求在遍历问题列表的同时,为每个问题 选择对应的答案。这需要定义一个索引变量,就像第8章“总统测试”中的currentQuestionIndex一样, 这里定义了answerIndex,当foreach遍历QuestionList时,用来跟踪对应的答案在列表AnswerList中的 位置。 在foreach开始遍历之前,设answerIndex的值为1;在foreach遍历过程中,answerIndex用来从 AnswerList中选择当前问题的答案,然后递增1。在foreach的每次迭代中,当前的问题-答案被添加到 QuestionsAnswersLabel的最后一行,问题与答案之间以冒号分隔。 调用新建的过程调用新建的过程 已经创建了显示问题-答案的过程displayQAs,但在调用它之前,它起不到任何作用。修改 SubmitButton.Click事件处理程序,用displayQAs替代对QuestionsAnswersLabel.Text的简单设置,来 显示所有的问题-答案。更新后的块如图10-7所示。 App Inventor 编程实例及指南 - 164 -本文档使用 看云 构建 图 10-7 在SubmitButton.Click中调用displayQAs过程图 10-7 在SubmitButton.Click中调用displayQAs过程 将数据永久保存到Web数据库将数据永久保存到Web数据库 到目前为止,用户输入的问题-答案只是保存在列表中,如果此时用户退出应用,会怎么样呢?正如“开车 不发短信”(第4章)或“Android,我的车在哪儿?”(第7章)中所学到的,如果数据不能存储到数据 库中,那么当用户退出并重新打开应用时,数据将丢失。只有永久存储数据,才能让出题者在每次打开应 用时,都能看到最新版本的数据,并对数据内容进行编辑。同时,永久保存数据也是必要的,因为在“答 题”应用中也需要访问这些数据。 之前我们学习过用TinyDB组件在数据库中存储并检索数据,本章将使用TinyWebDB组件。两者的区别 是:TinyDB将数据存储在香港老钱庄868525,(六合娃娃上,而TinyWebDB将数据存储在Web数据库中。 本应用在设计上之所以选择在线数据库,而非香港老钱庄868525,(六合娃娃数据库,关键在于存这些数据要供两个应用访问,如果 出题者把问题和答案都存储在个人的香港老钱庄868525,(六合娃娃上,那么答题者将无法获取数据并参加考试!而TinyWebDB将数 据保存在互联网上,答题者可以使用不同于出题者的设备来访问试题及答案。(在线数据存储通常被称作 云。) 下面是永久保存列表数据(如问题及答案)的通用方案: 每当向列表中添加新项时,将数据保存到数据库; 应用启动时,从数据库中加载数据,并保存在列表变量中。 首先考虑数据的保存:每次用户输入新的问题-答案时,将QuestionList和AnswerList保存到数据库中。 块的功能块的功能 TinyWebDB1.StoreValue块将数据存储在Web数据库中。StoreValue有两个参数:tag用做数据的标 识,value是要保存的实际数据。如图10-8所示,QuestionList在存储时以“questions”为tag(标 签),而AnswerList则用“answers”为tag(标签)。 App Inventor 编程实例及指南 - 165 -本文档使用 看云 构建 图 10-8 将问题和答案保存到数据库中图 10-8 将问题和答案保存到数据库中 建议在个人应用中使用更有特点的tag(如DavesQuestions和DavesAnswers)来代替questions和 answers,这非常重要,因为你正在使用App Inventor的默认Web数据库,所以你的数据(列表 questions和answers)可能会被别人的数据覆盖,也包括那些正在学习本教程的人。 这里要提醒各位,App Inventor默认的Web服务会在各个程序员和各种应用之间共享,因此它仅适用于测 试。当你打算正式发布一款应用时,需要建立自己私有的数据库服务。幸运的是,做到这一点很简单,而 且不需要编程(见第22章)。 从数据库加载数据从数据库加载数据 本应用需要永久保存数据,一方面因为出题者可以随时关闭应用,并随时启动应用,以便对之前输入问题 和答案进行补充、修改或删除。这就需要在每次启动应用时,从数据库中加载那些已存储的数据。(另一 方面,答题者可以访问数据库总的问题及答案,稍后会涉及到。) 正如我们之前所学,在应用启动时需要进行的操作,要通过Screen.Initialize事件处理程序来实现。在本应 用中,需要用TinyWebDB组件向Web数据库请求questions及answers这两个列表,因此 Screen1.Initialize将两次调用TinyWebDB.GetValue。块的设置如下图: App Inventor 编程实例及指南 - 166 -本文档使用 看云 构建 图 10-9 在应用启动时从数据库中请求列表数据图 10-9 在应用启动时从数据库中请求列表数据 块的功能块的功能 图10-9中使用的TinyWebDB.GetValue块与之前用过的TinyDB.GetValue的运行机制不同,后者会立即返 回一个值,而前者只负责向Web数据库发送请求,不会立即收到返回值。当应用收到Web数据库返回的数 据时,会触发TinyWebDB.GotValue事件,因此需要另外编写一个GotValue事件的处理程序来接收返回的 数据。 当TinyWebDB.GotValue事件发生时,所请求的数据封装在参数valueFromWebDB中,所请求的数据标 签(tag)则封装在参数tagFromWebDB中。 如图10-9所示,在Screen1.Initialize事件处理程序中发出了两次GetValue请求,分别请求questions和 answers,因此GotValue也将被触发两次。为了避免把questions的数据写入AnswerList中(或反过 来),需要对tag进行检查,来判断收到的是哪个请求的返回值,然后再把返回值写到相应的列表中 (QuestionList或AnswerList)。现在,你该意识到这些tag的真正用途了吧! 块的功能块的功能 应用中两次调用TinyWebDB1.GetValue来请求存储过的数据:分别是为QuestionList及AnswerList。当 收到Web数据库返回的数据时,触发TinyWebDB1.GotValue事件,如图10-10。 图 10-10 当收到来自web的数据时触发GotValue事件图 10-10 当收到来自web的数据时触发GotValue事件 从数据库中返回的数据封装在GotValue事件的valueFromWebDB参数中。在GotValue的事件处理程序 中,外层的if块用来判断数据库的返回值valueFromWebDB是否为空。设想用户首次启动应用,数据库中 没有任何数据。通过询问参数valueFromWebDB是否“is a list?”,可以得知是否真的有数据返回。如 果没有数据返回,则会跳过对GotValue事件的处理。 如果有数据返回(is a list?为真),再继续判断收到的是哪个请求。识别数据的标记tag封装在 tagFromWebDB中:即可能是“questions”,也可能是“answers”,如果tag是“questions”,则将 valueFromWebDB保存到变量QuestionList,否则(else块),保存到AnswerList。(如果你使用的tag 不是“questions”和“answers”,请替换成你自己的tag再做判断。) 我们希望当两个列表都已收到时(GotValue被触发两次)再来显示这些数据。想想看,如何判断从数据库 收到了两个列表的数据?是的,用if块来检测两个列表的长度是否相同,因为只有两个列表都收到了,检测 App Inventor 编程实例及指南 - 167 -本文档使用 看云 构建 结果才能为真。如果为真,你可以轻松调用之前编写的displayQAs过程来显示加载的数据。 图 10-11 “出题”应用中的块图 10-11 “出题”应用中的块 答题:从数据库中读取试题的应用答题:从数据库中读取试题的应用 “出题”应用已经就绪,下面来创建“答题”应用,一个可以动态加载测验的应用,相当简单。只是 在“总统测验”的基础上稍加修改(如果你还没学过,现在就去学,然后再继续)。 打开“总统测验”,选择“save project as”将应用另存为“TakeQuiz”,这保证在不修改“总统测 验”的情况下,以此为基础来构建“答题”应用 。 在组件设计器中调整组件在组件设计器中调整组件 在组件设计器中做如下改变: 1. 这个版本的“出题/答题”应用不需要为问题搭配图片,因此首先删除所有与图片相关的部分:在组件设 计中,从Media区域中选择并删除所有图片,然后再删除Image1组件,这将删除块编辑器中对它的所有引 用(块编辑器中的global PictureList需手工删除); 2. 由于“答题”应用中会用到数据库中的数据,因此添加一个TinyWebDB组件; 3. 在试题被加载完成之前,不希望用户来回答问题或点击“下一题”按钮,因此取消勾选“提交”和“下 一题”按钮的Enabled属性。 在快编辑器中编程:从数据库加载测验在快编辑器中编程:从数据库加载测验 首先,修改列表变量的初始化设置:这里不需要预置问题及答案,因此用create empty list块替代 QuestionList和AnswerList初始化时用到的make a list块,结果如图10-12所示。 App Inventor 编程实例及指南 - 168 -本文档使用 看云 构建 图 10-12 应用开始时,问题及答案列表为空图 10-12 应用开始时,问题及答案列表为空 其次,删除PictureList,这里不需要图片;修改Screen1.Initialize,两次调用TinyWebDB.GetValue来加 载列表,与“出题”应用中相同,如图10-13所示。 图 10-13 从Web数据库中请求问题及答案列表图 10-13 从Web数据库中请求问题及答案列表 最后,拖出一个TinyWebDB.GotValue事件处理程序。此事件处理程序与“出题”应用中的程序类似,但 这里只显示第一个问题,而且没有答案。先尝试自己做些修改,然后对照图10-14,看看你的方案是否与 图中的相符。 图 10-14 用GotValue处理来自Web的数据图 10-14 用GotValue处理来自Web的数据 块的作用块的作用 应用启动时触发Screen1.Initialize事件,应用从Web数据库请求数据(questions及answers)。每次 (共两次)收到数据都会触发TinyWebDB.GotValue事件。首先使用“is a list?”来判断 valueFromWebDB中是否真的含有数据:如果有,则使用tagFromWebDB来判断是哪个请求返回的,并 将valueFromWebDB值写入相应的列表中。如果QuestionList已经加载,则从QuestionList选择第一题并 显示;如果AnswerList已经加载,则启用“提交”及“下一题”按钮,以便用户可以开始答题。 完整的答题应用完整的答题应用 App Inventor 编程实例及指南 - 169 -本文档使用 看云 构建 图 10-15 “答题”应用中块的最终设置图 10-15 “答题”应用中块的最终设置 改进改进 在“出题”与“答题”应用开始运行之后,你也会会尝试做一些改进。例如: 允许出题者为每个问题指定一个图片。当然,你(作为开发者)不能预加载这些图像,并且目前应用 的用户也无法做到这一点。因此,图片必须是一些Web URL,出题者需要在“出题”应用的表单中输 入这些URL,这些URL构成了应用的第三个列表。请注意,你可以将Image组件的Picture属性设置为 一个URL。 允许出题者从问题和答案列表中删除项。用户可以使用ListPicker组件选择一个问题,并用remove list item块来删除列表项(记住要同时从两个列表中删除,并更新数据库)。想获得ListPicker和列表删除 的相关帮助,请参见第19章。 让出题者为他的测验设定名称。测验名称也要以一个不同的tag保存到数据库中,并在“答题”应用 中,与整个测验一同加载。名称加载完成后,可以将其设置为Screen1的Title属性,这样当用户答题 时,测验名称也将显示出来。 允许创建多个不同名称的测验。需要建一个测验的列表,并且用测验的名称作为保存问题和答案时的 tag(一部分)。 小结小结 以下是本章涵盖的内容: 动态数据是指由用户输入的、或从数据库中加载的信息。用动态数据编程会更加抽象。更多信息请参 见第19章; App Inventor 编程实例及指南 - 170 -本文档使用 看云 构建 可以使用TinyWebDB组件在Web数据库中永久保存数据; 要从Web数据库中检索数据,需要用TinyWebDB组件的GetValue方法。当Web数据库返回数据时, 会触发TinyWebDB.GotValue事件。用TinyWebDB.GotValue事件处理程序,可以把数据存储在列表 中,或以其他方式进行处理; TinyWebDB数据可以在多部香港老钱庄868525,(六合娃娃和应用之间共享。关于(Web)数据库的更多信息,请参见第22 章。 App Inventor 编程实例及指南 - 171 -本文档使用 看云 构建 第 11 章 广播中心 FrontlineSMS 是一款工具软件,用于联络那些无法访问互联网但可以用香港老钱庄868525,(六合娃娃通信的人,通常用于互联网 尚未普及地区的选举监督、天气预报广播等。软件作者Ken Banks借助于移动通信技术为人们提供帮助, 他的贡献大概无人能及。 FrontlineSMS运行在连接了香港老钱庄868525,(六合娃娃的电脑上。电脑和香港老钱庄868525,(六合娃娃共同构成一个短信中转站,为群内人员提供文本通 信服务。无法上网的人可以发送一个特殊代码来加入群,随后他们会收到来自中转站的各种广播消息。这 个中转站我们称之为“广播中心”,对于那些没有网络的地方,广播中心成为与外界联系的重要手段。 使用App Inventor可以创建自己的短信处理应用。有趣的是,应用需要运行在一部android设备上,但应 用的用户却不必使用Android香港老钱庄868525,(六合娃娃,他们可以用任何香港老钱庄868525,(六合娃娃,智能的或非智能的,与应用之间进行短信的交 流。应用虽然具有图形化的用户界面(GUI),但GUI仅供应用的管理者使用,用来监控应用中的各种活 动。 本章将创建一个与FrontlineSMS功能类似的广播中心,不过是运行在Android香港老钱庄868525,(六合娃娃上。一台具有中转枢纽 作用的移动设备,意味着管理者可以在移动中保持交流,这一点在某些场合下尤其重要,如选举监督和医 疗争议谈判。 假想有一个“快闪舞蹈团”(FlashMob Dance Team,缩写为FMDT),他们可以召之即来,随时随地 表演舞蹈,然后瞬间解散,消失得无影无踪,他们用你创建的广播中心来组织表演活动。人们只要向中心 发送短信“joinFMDT”(参加快闪舞蹈团),即可完成入团注册,每个注册成功的人都可以向舞蹈团中的 其他人广播消息。 广播中心用下面的方式处理收到的短信: 1. 如果发信人不在广播中心的成员名单中,则回复短信邀请他加入,并告知他申请代码; 2. 如果收到“joinFMDT”,则接收发信人为广播中心成员;【如果组员发送“joinFMDT”呢?】 App Inventor 编程实例及指南 - 172 -本文档使用 看云 构建 3. 如果发信人已经是广播中心的成员,则转发该消息给全体广播中心成员。 我们来分步实现这些功能模块。首先,用自动回复来邀请人们加入广播中心。整个应用完成之后,对于创 建这类“以短信为用户界面的应用”,你将有透彻的了解。 学习要点学习要点 本章包括下列App Inventor概念,其中有些你可能已经熟悉了: Texting组件:发送短信及处理收到的短信; 列表变量:在本例中用来记录今晚六彩现场开奖结果号码清单; foreach块:对列表中的数据进行逐项重复操作。在本例子中,使用foreach块向今晚六彩现场开奖结果号码列表中的所 有香港老钱庄868525,(六合娃娃广播消息; TinyDB组件:实现数据的永久存储,以保证当应用关闭并再次打开时,今晚六彩现场开奖结果号码列表不丢失。 准备开始准备开始 你需要一部可以接收和发送短信的香港老钱庄868525,(六合娃娃来测试程序,因为App Inventor自带的模拟器没有这个功能。您还 需要招呼一些朋友给你发送短信,来充分地测试应用。 连接到App Inventor网站,创建新项目“BroadcastHub”,设置Screen1.Title属性为“广播中心”,并 连接测试香港老钱庄868525,(六合娃娃。 设计组件设计组件 广播中心有利于香港老钱庄868525,(六合娃娃之间的通信:这些香港老钱庄868525,(六合娃娃不需要安装应用,甚至不必是智能香港老钱庄868525,(六合娃娃。因此在本例中不必为 用户提供操作界面,只需为群管理员提供操作界面。 管理员的操作界面包括两个简单的部分,一是显示当前的“广播列表”,即已注册成员的今晚六彩现场开奖结果号码清单, 二是记录所有收到并被广播出去的短信。 为了创建这个界面,要添加表11-1中列出的组件。 表11-1 广播中心操作界面中的组件表11-1 广播中心操作界面中的组件 组件类型组件类型 面板中分组面板中分组 命名命名 作用作用 Label User Interface Label1 今晚六彩现场开奖结果号码清单的标题 Label User Interface BroadcaseListLabel 显示所有已注册的今晚六彩现场开奖结果号码 Label User Interface Label2 日志信息的标题 Label User Interface LogLabel 显示收到及广播短信的记录 App Inventor 编程实例及指南 - 173 -本文档使用 看云 构建 Texting Social Texting1 处理短信 TinyDB Storage TinyDB1 保存已注册的香港老钱庄868525,(六合娃娃号码清单 组件类型组件类型 面板中分组面板中分组 命名命名 作用作用 添加组件之后,还要设置以下属性: 1. 设置每个Label的Width属性为“Fill parent”,让组件在水平方向上充满香港老钱庄868525,(六合娃娃; 2. 设置标题Label的FontSize属性(Label1和Label2)为18,并勾选FontBold框; 3. BroadcastListLabel和LogLabel的Height设置为200像素,用于显示多行; 4. 设置BroadcastListLabel的Text属性为“广播列表...”; 5. LogLabel的Text属性设置为空。 图11-1显示了应用在组件设计器中的布局。 图 11-1 广播中心组件设计图 11-1 广播中心组件设计 App Inventor 编程实例及指南 - 174 -本文档使用 看云 构建 为组件添加行为为组件添加行为 在这个应用中,促使程序运行的事件是其他香港老钱庄868525,(六合娃娃发来的短信,而不是用户在界面上的输入或点击,因此应 用的任务是处理这些短信,并将发信人香港老钱庄868525,(六合娃娃号码保存到列表中,具体操作如下: 如果短信发送者不在广播列表中,则回复一个邀请参加的短信; 如果收到短信“joinFMDT”,则将发送者注册为广播列表的一员; 如果短信发送者已经在广播列表中,则将该短信广播到列表中的所有香港老钱庄868525,(六合娃娃。 现在开始创建第一个行为:收到短信时,回复发送者,邀请他注册,方法是向你发送短信“joinFMDT”。 表11-2中列出了需要的块。 表11-2 邀请人们通过发短信来加入群组,需要下面的块表11-2 邀请人们通过发短信来加入群组,需要下面的块 块的类型块的类型 所在抽屉所在抽屉 作用作用 Texting.MessageReceived Texting1 当香港老钱庄868525,(六合娃娃收到短信 时,触发该事件 set Texting1.PhoneNumber to Texting1 设置短信接收者的 今晚六彩现场开奖结果号码 参数number Variables MessageReceived 事件的参数:发送 者香港老钱庄868525,(六合娃娃号 Set Texting1.Message Texting1 设置要发送的邀请 短信 “想加入快闪舞蹈团,请发 送‘joinFMDT’到此号 码。” Text 邀请短信的内容 Texting1.SendMessage Texting1 块的作用块的作用 根据在第4章“开车不发短信”中的经验,你应该很熟悉这些块。当香港老钱庄868525,(六合娃娃收到短信时会触发 Texting1.MessageReceived事件。如图11-2,在事件处理程序中设置Texting1组件的PhoneNumber及 Message属性,然后发送短信。 App Inventor 编程实例及指南 - 175 -本文档使用 看云 构建 图 11-2 收到短信后回复邀请短信图 11-2 收到短信后回复邀请短信  测试:需要用第二部香港老钱庄868525,(六合娃娃来测试这一功能;你不能给自己发短信,否则会永远循环下去! 如果没有其他香港老钱庄868525,(六合娃娃,可以注册Google Voice或类似的服务,从这些服务中给自己的香港老钱庄868525,(六合娃娃发短信。用第 二部香港老钱庄868525,(六合娃娃发送“你好”到测试香港老钱庄868525,(六合娃娃,则第二部香港老钱庄868525,(六合娃娃会收到一个邀请加入“舞蹈团”的短信。 将某人加入广播列表将某人加入广播列表 现在创建第二个行为:收到短信“joinFMDT”后,将发信人添加到广播列表中。首先定义列表变量 BroadcastList来保存注册的今晚六彩现场开奖结果号码。从Variables中拖出一个“initialize global name to”块,将 name改为“BroadcastList”,并用make a list块初始化列表,此时列表为空。如图11-3(稍后将实现向 列表中添加数据项的功能)。 图 11-3 变量BroadcastList用于存储注册的今晚六彩现场开奖结果号码【也可用create empty list块】图 11-3 变量BroadcastList用于存储注册的今晚六彩现场开奖结果号码【也可用create empty list块】 下面修改Texting1.MessageReceived事件处理程序,如果收到短信“joinFMDT”,则将发信人香港老钱庄868525,(六合娃娃号码 添加到BroadcastList中。判断短信内容需要使用Ifelse块(在第十章“出题”应用中使用过),将新号码 添加到列表中需要使用add item to list块。整个设置所需的块见表11-3。在今晚六彩现场开奖结果号码添加完之后,用 BroadcastListLabel来显示新列表。 表11-3 检查来信内容,并将发信人添加到广播列表中,需要如下块表11-3 检查来信内容,并将发信人添加到广播列表中,需要如下块 块的类型块的类型 所在抽屉所在抽屉 作用作用 initialize global BroadcastList to Variables 定义广播列表变量 ifelse Control 根据收到短信的内容决定做什么事 = Math 判断短信内容是否等于“joinFMDT” App Inventor 编程实例及指南 - 176 -本文档使用 看云 构建 get messageText Variables 将来信内容插入“=”块(左边 “joinFMDT” Text 将固定文本插入“=”块(右边) add items to list Lists 向广播列表中添加发信人今晚六彩现场开奖结果号 get number Variables 将发信人香港老钱庄868525,(六合娃娃号码插入“add items to list” set BroadcaseListLabel.Text to BroadcaseListLabel 显示新列表 get global BroadcastList Variables 将其插入set BroadcaseListLabel.Text to 块 set Texting1.Message to Texting1 设置短信内容,准备用Texting1回复发信 人 “恭喜你成功加入…” Text 祝贺发信人加入群组成功。 块的类型块的类型 所在抽屉所在抽屉 作用作用 块的作用块的作用 如图11-4所示,对刚收到的短信进行回复,第一行的块将发信人香港老钱庄868525,(六合娃娃号码设置为接收人香港老钱庄868525,(六合娃娃号码,即设置 Texting1.PhoneNumber为number。然后判断messageText是否为特殊代码“joinFMDT”:如果是, 则将发送者香港老钱庄868525,(六合娃娃号添加到BroadcastList并发短信祝贺;如果不是,则回复邀请短信。在Ifelse块之后,回 复短信被发出(最后一行)。 图 11-4 如果收到短信“joinFMDT”,则将发信人香港老钱庄868525,(六合娃娃号添加到BroadcastList图 11-4 如果收到短信“joinFMDT”,则将发信人香港老钱庄868525,(六合娃娃号添加到BroadcastList  测试:用第二部香港老钱庄868525,(六合娃娃发送短信“joinFMDT”到测试香港老钱庄868525,(六合娃娃,在测试香港老钱庄868525,(六合娃娃收到短信的同时,第 App Inventor 编程实例及指南 - 177 -本文档使用 看云 构建 二部香港老钱庄868525,(六合娃娃的号码出现在“已注册的今晚六彩现场开奖结果号码”下面,第二部香港老钱庄868525,(六合娃娃会收到祝贺短信。尝试发一个其他内 容的短信,检查邀请短信是否能正常发送。 广播消息广播消息 下面来添加广播行为:当广播列表BroadcastList中的成员向广播中心发来短信时,将此信息转发给列表中 的所有香港老钱庄868525,(六合娃娃。这一功能稍显复杂,需要更多的控制块:增加一个Ifelse块和一个foreach块。新增的Ifelse块 用于检查发送短信的香港老钱庄868525,(六合娃娃号是否在广播列表中,而foreach块用于向列表中的所有香港老钱庄868525,(六合娃娃广播这条短信。另外 还要将之前的Ifelse块移动到新Ifelse块的“else”部分。表11-4列出了需要新增的块。 表11-4 向列表中的成员广播某个成员发来的短信需要新增的块表11-4 向列表中的成员广播某个成员发来的短信需要新增的块 块的类型块的类型 所在抽屉所在抽屉 作用作用 ifelse Control 根据发信人是否已在广播列表中来决定做不同的事 is in list? Lists 检查某数据是否在列表中 get global BroadcastList Variables 将其插入is in list?的list插槽中 get number Variables 将其插入is in list?的thing插槽 set Texting1.Message to Texting1 设置将被广播出去的短信内容(列表成员的来信) get messageText Variables 即将被广播出去的列表成员来 foreach Control 向列表中的所有成员发送同一条短信 get global BroadcastList Variables 将其插入foreach的list插槽 set Texting1.PhoneNumber to Texting1 设置接收短信的香港老钱庄868525,(六合娃娃号码 get item Variables BroadcaseList中当前正在操作的项/变量:保存的是手 机号 块的作用块的作用 这里使用了嵌套的ifelse块,使得程序更加复杂,如图11-5所示。嵌套的ifelse块指的是在一个ifelse块 的“then”或“else”插槽中嵌入了另一个ifelse块。在本例中,外层的ifelse负责检查发信人的香港老钱庄868525,(六合娃娃号是 否已在广播列表中。如果在,则将该短信转发给列表中的所有人;如果不在,则执行内层ifelse判断:短信 内容messageText是否为“joinFMDT”,并依据判断结果,执行不同的分支操作。 App Inventor 编程实例及指南 - 178 -本文档使用 看云 构建 图 11-5 检查发信人是否已在广播列表中,如果是,则广播此短信图 11-5 检查发信人是否已在广播列表中,如果是,则广播此短信 从理论上,if块和ifelse块可以做任意层级的嵌套,来实现更加复杂的行为(更多关于条件语句块的内容请 参见第18章)。 在外层ifelse块的then分支中,使用foreach块来广播短信。foreach遍历BroadcastList列表中的每一项, 并把短信发送给列表中的每个今晚六彩现场开奖结果号码。在foreach执行循环时,BroadcastList中的每个今晚六彩现场开奖结果号码依次被 保存在item中(item是一个变量,代表了foreach当前正在处理的项)。在foreach块内,设置 Texting.PhoneNumber的值为当前项item,并向其发送短信。有关foreach的更多信息,请参见第20章。  测试:首先要有两部不同的香港老钱庄868525,(六合娃娃通过发送“joinFMDT”到测试香港老钱庄868525,(六合娃娃,实现成功注册。然 后,从一部香港老钱庄868525,(六合娃娃向广播中心发一条短信,这时两部香港老钱庄868525,(六合娃娃都应该收到这条短信(包括发送短信的那一 个)。 整理列表的显示整理列表的显示 广播短信的功能已经实现,但管理员的界面尚需改进。首先,今晚六彩现场开奖结果号码列表的显得很乱:用Label显示列表 时,列表项之间用空格分隔,并且尽可能占满一行,像下面这样: (+861303318989 +861581235590 +8618902018909 +8613301103355 +8613801237890) 为了改善这种局面,使用表11-5列出的块创建一个过程displayBroadcastList,来实现每行只显示一个号 码。请务必在add items to list块的下面调用该过程,以便显示更新后的列表。 表11-5 改进今晚六彩现场开奖结果号码列表显示所需的块表11-5 改进今晚六彩现场开奖结果号码列表显示所需的块 App Inventor 编程实例及指南 - 179 -本文档使用 看云 构建 块的类型块的类型 所在抽屉所在抽屉 作用作用 to procedure(“displayBroadcastList”) Procedures 创建过程displayBroadcastList set BroadcaseListLabel.Text to BroadcaseListLabel 用来显示列表 “” Text 空文本 foreach Control 对今晚六彩现场开奖结果号码列表进行遍历 pnumber foreach内置 变量pnumber为遍历过程中正在访 问的 get global BroadcaseList Variables 插入foreach块的in list插槽 set BroadcaseListLabel.Text to BroadcaseListLabel 显示今晚六彩现场开奖结果号码列表 join Text 将多个文本片段连接为一个文本对 象 BroadcaseListLabel.Text BroadcaseListLabel 每次循环都以既有label内容为基础 追加新项 “\n” Text 换行,以便下一个号码显示在下一 行 get pnumber foreach内置 遍历时列表中正在访问的项(香港老钱庄868525,(六合娃娃号 码) 块的作用块的作用 过程displayBroadcastList中的foreach块逐行地将每个香港老钱庄868525,(六合娃娃号码添加到label的末尾,如图11-6所示,用换 行符(\ n)来分隔每个号码,使得每个号码各占一行。 图 11-6 逐行显示香港老钱庄868525,(六合娃娃号码图 11-6 逐行显示香港老钱庄868525,(六合娃娃号码 不过displayBroadcastList过程不会主动做任何事,除非调用它。在Texting1.MessageReceived事件处理 程序中,紧接着add item to list块调用它。过程的调用取代了列表BroadcastList在 BroadcastListLabel.Text中的默认显示。块call displayBroadcastList归属在Procedures抽屉中。 App Inventor 编程实例及指南 - 180 -本文档使用 看云 构建 图11-7显示了Texting1.MessageReceived事件处理程序中相关的块。 图 11-7 调用displayBroadcastList过程图 11-7 调用displayBroadcastList过程 关于用foreach来显示列表的详细信息请参见第20章,关于创建和调用过程的详细信息请参见第21章。  测试:重新启动应用来清除列表,然后用至少两个不同的香港老钱庄868525,(六合娃娃进行注册(再次)。香港老钱庄868525,(六合娃娃号 码是否逐行显示了? 录广播过的短信录广播过的短信 在收到短信并向其他香港老钱庄868525,(六合娃娃发出广播之后,程序应该记录此类事件,以便管理员可以对活动进行监督。在组 件设计器中,已经添加的LogLabel组件就是用于这一目的。下面编写程序,每当收到新的短信时,改变 LogLabel的显示。 要创建像这样的一段文本:“来自+8613901231234的短信已经广播。”字符“+8613901231234”不是 固定数据,而是MessageReceived事件自带的参数值。因此,要创建的文本包括三个部分:①“来自”; ②香港老钱庄868525,(六合娃娃号码,为参数number;③“的短信已经广播”。正如在前几章中所做的一样,用join将三个部分连 接起来,表11-6列出了需要的块。 表11-6 构建广播日志所需要的块表11-6 构建广播日志所需要的块 块的类型块的类型 所在抽屉所在抽屉 作用作用 set LogLabel.Text to LogLabel 在此显示日志 join Text 由多个文本片段创建成一个文本对象 “来自” Text 每条日志信息的第①部 App Inventor 编程实例及指南 - 181 -本文档使用 看云 构建 get number Texting1.MessageReceived事件内 置参数 日志信息的第②部分:短信发送者的 香港老钱庄868525,(六合娃娃号码 “的短信已经广播。 \n” Text 日志信息的第③部分 LogLabel.Text LogLabel 在原有日志前插入一条新的日志 块的类型块的类型 所在抽屉所在抽屉 作用作用 块的作用块的作用 在收到短信后,向BroadcastList列表中的所有号码广播此短信,再修改LogLabel,记录刚才的广播操作, 如图11-8所示。需要注意的是,我们将消息添加到列表的开始,而不是结尾,因此最后发出的消息将显示 在最顶端。 图 11-8 向广播日志中添加一条新消息图 11-8 向广播日志中添加一条新消息 join块创建了一条新记录:来自+8613901231234的短信已经广播。 每次短信广播之后,这条记录将被添加到LogLabel.Text的第一行,使最新的记录一直出现在顶部。join块 中各个文本片段的顺序决定了日志中记录的顺序。在本例子中,新消息被编排在前三个插槽中,而 LogLabel.Text,已经保存的现有记录,将插入最后一个插槽。 App Inventor 编程实例及指南 - 182 -本文档使用 看云 构建 “的短信已经广播。\n”中的“\n”称为换行符,它让每条记录单独占一行,像这样: 来自+8613030123668的短信已经广播。 来自+8613901231234的短信已经广播。 关于使用foreach来显示列表的详细信息,请参见第20章。 将BroadcastList保存在数据库中将BroadcastList保存在数据库中 现在应用算是大功告成了,但通过前几章的学习,你可能猜到了一个问题:如果管理员将应用关闭再重新 启动时,广播列表中的数据将会丢失,每个人都得重新注册。为了解决这个问题,要使用TinyDB组件实现 BroadcastList列表在数据库中的存储和检索。 这里将使用与“出题”应用(第10章)中相类似的方案: 每次添加新项时,将列表保存到数据库中; 应用启动时,从数据库中加载列表,并保存到一个变量中。 用表11-7中所列的块,将列表存储到数据库中。TinyDB组件中的tag作为数据的标识,将保存在数据库中 的不同数据区分开来。在本例中,你可以将数据标记为“broadcastList”。在 Texting1.MessageReceived中,将这些块添加到add items to list块之下。 表11-7 用TinyDB来存储列表所需的块表11-7 用TinyDB来存储列表所需的块 块的类型块的类型 所在抽屉所在抽屉 作用作用 TinyDB1.StoreValue TinyDB1 将数据保存到数据库中 “broadcastList” Text 将其插入StoreValue的tag插槽中 get global BroadcastList Variables 将其插入StoreValue的value插槽中 块的功能块的功能 当应用收到短信“joinFMDT”,并将新成员的香港老钱庄868525,(六合娃娃号码添加到列表时,调用TinyDB1.StoreValue将 BroadcastList保存到数据库中。tag(“broadcastList”)的使用是为了便于之后对数据的检索。如图 11-9,被StoreValue调用的值(valueToStore)是变量BroadcastList。 App Inventor 编程实例及指南 - 183 -本文档使用 看云 构建 图 11-9 调用TinyDB来存储BroadcastList列表图 11-9 调用TinyDB来存储BroadcastList列表 从数据库加载广播列表(BroadcastList)从数据库加载广播列表(BroadcastList) 每次应用启动时都要加载广播列表,按照表11-8中列出的块来实现这一功能。应用的启动将触发 Screen1.Initialize事件,因此将在该事件的处理程序中实现加载。使用存储时的 tag(“broadcastList”)来调用TinyDB.GetValue。就像前几章一样,我们需要检查是否的确有数据返 回,这里将检查返回值是否为列表,因为如果列表中没有数据,那么它也就不是列表。 块的作用块的作用 应用启动将触发Screen1.Initialize事件。如图11-10所示,使用TinyDB1.GetValue块向数据库请求数据, 返回的数据临时保存在已定义的变量valueFromDB中。 表11-8 应用启动时加载广播列表所需要的块表11-8 应用启动时加载广播列表所需要的块 块的类型块的类型 所在抽屉所在抽屉 作用作用 initialize global valueFromDB to Variables 用于保存并检查数据库返回值的临时变量 “” Text 设valueFromDB初始值为空 App Inventor 编程实例及指南 - 184 -本文档使用 看云 构建 Screen1.Initialize Screen1 应用启动时触发该事件 set global valueFromDB to Variables 将数据库返回值暂时存放在其中 TinyDB1.GetValue TinyDB1 向数据库请求数据 “broadcastList” Text 将其插入GetValue的tag插槽 if Control 判断数据库中是否有数据 is a list Lists 如果数据库返回值是一个列表,则返回值不为空 get global valueFromDB Variables 将其插入is a list? set global BroadcaseList to Variables 将变量值设置为数据库的返回值 get global valueFromDB Variables 数据库返回值不为空时,将返回值写入广播列表 call displayBroadcastList Procedures 加载数据成功后,显示数据 块的类型块的类型 所在抽屉所在抽屉 作用作用 图 11-10 从数据库中加载广播列表BroadcastList图 11-10 从数据库中加载广播列表BroadcastList 事件处理程序中的if块是必需的,因为在首次启动应用时,数据库将返回空文本(“”),这时还没有生成 广播列表。通过判断valueFromDB是否为列表,可以确定是否真的有数据返回。如果没有,则跳过那些保 存返回数据以及显示数据的块。  测试:对于涉及数据库操作的应用,不适合做实时测试,因为每次连接测试设备时,都会 清空数据库。为了测试数据库以及Screen.Initialize事件处理程序,需要将应用打包并下载到香港老钱庄868525,(六合娃娃【点 击“buildApp(provide QR code for .apk)”,下载并安装应用】。在香港老钱庄868525,(六合娃娃上启动应用,用另外两 部香港老钱庄868525,(六合娃娃发送“joinFMDT”加入群组,再退出应用。当重启应用时,如果那些今晚六彩现场开奖结果号码还在,说明数 据库部分工作正常。 完整的广播中心应用完整的广播中心应用 App Inventor 编程实例及指南 - 185 -本文档使用 看云 构建 图 11-11 完整的广播中心应用中的块图 11-11 完整的广播中心应用中的块 改进改进 在庆祝一个如此复杂的应用完工的时候,你也许想要做进一步的改进。例如: 在广播短信环节,广播中心向所有人发出短信,也包括发送这条短信的列表成员。修改此功能,将短 信群发给除了发送者之外的所有成员; 允许列表成员退出群组,用香港老钱庄868525,(六合娃娃发送短信“quitabc”给广播中心,请求从列表中删除自己。需要使用 remove from list块; 管理员可以在操作界面上添加或删除广播列表中的成员(香港老钱庄868525,(六合娃娃号); 管理员可以指定某些不允许加入列表的香港老钱庄868525,(六合娃娃号; 细化应用的功能,让任何人都可以加入到列表并接收广播,但只有管理员可以广播消息; 进一步细化应用,让任何人都可以加入到接收广播,但只有一个固定列表中的今晚六彩现场开奖结果号码可以向全体成 员广播消息(这正式赫尔辛基事件 的成功之处); 应用中的广播列表可以永久保存,但日志却不能。每次关闭该应用再重新打开时,日志也从头开始。 改进一下,让日志也能永久保存。 小结小结 以下是本章涵盖的内容: 应用不仅可以响应用户发起的事件,也可以响应非用户发起的事件,像收到短信这样的事件。这意味 着应用的用户也可以是其他香港老钱庄868525,(六合娃娃; App Inventor 编程实例及指南 - 186 -本文档使用 看云 构建 ifelse与foreach块的嵌套使用可以构造出复杂的行为。有关条件语句if和循环语句foreach的详细信 息,请参见第18章及第20章; join块可以用来创建一个多重内容组成的文本对象; TinyDB可以用于数据库操作:存储及检索数据。通用方案是,在数据发生变化时,调用StoreValue更 新数据库;在应用启动时,调用GetValue从数据库中读取数据。 App Inventor 编程实例及指南 - 187 -本文档使用 看云 构建 第 12 章 遥控机器人 作者介绍作者介绍 Liz LooneyLiz Looney Liz Looney是本书的合著者之一。她是谷歌的软件工程师,也是谷歌“机器人工作小组”(Robotics Task Force)的成员,作为App Inventor团队的初创成员,她领导了乐高头脑风暴机器人(LEGO MINDSTORMS)组件的开发工作。Liz Looney是一位卓越的软件工程师,有着超过25年的从业经历, 曾先后在Borland、Oracle及Google公司工作。 本章将创建一个应用,将Android香港老钱庄868525,(六合娃娃变成LEGO MINDSTORMS NXT 机器人的遥控器。应用中用按钮来 控制机器人前后移动、左右转动和停止,如果机器人遇到障碍物,它还会自动停止。应用中使用具有蓝牙 功能的香港老钱庄868525,(六合娃娃与机器人通信。 LEGO MINDSTORMS机器人不只是玩具,更是教具。After-school program 使用机器人来教小学和初中 的孩子们掌握解决问题的能力,并引导他们了解工程和计算机编程。NXT机器人也用于FIRST LEGO League 机器人竞赛,这项比赛允许9-14岁的孩子参加。 App Inventor 编程实例及指南 - 188 -本文档使用 看云 构建 NXT可编程机器人套件中有一个“NXT智能积木”主单元,它可以控制三个电机及四个输入传感器。你可 以用乐高的构造元件、齿轮、车轮、电机和传感器来组装机器人。该套件自带的软件可以对机器人进行编 程,但现在我们将用App Inventor来创建Android应用,通过蓝牙连接来控制NXT机器人。 应用中参与协作的机器人具有超声波传感器以及用于移动的车轮,如Shooterbot 机器人。图中所示,这 款机器人通常是人们利用LEGO MINDSTORMS NXT 2.0套件建造的第一个机器人。它的左车轮与输出端 口C相连,右车轮与输出端口B相连,颜色传感器与输入端口3相连,超声波传感器与输入端口4相连。 学习要点学习要点 本章用到了以下组件和概念: BluetoothClient组件:用于建立Android设备与NXT机器人之间的蓝牙连接; ListPicker组件:为用户提供机器人选择列表,选中后开始建立机器人到Android的连接; NxtDrive组件:用于驱动机器人的轮子; NxtUltrasonicSensor组件:利用机器人的超声波传感器探测障碍物; Notifier组件:显示错误消息。 准备开始准备开始 本章的应用需要Android 2.0或以上版本。此外,出于安全原因,蓝牙设备必须首先配对才能彼此连通。在 开始构建应用之前,需要按以下步骤使Android设备与NXT机器人配对: 1. 在NXT上单击向右箭头,直到显示“Bluetooth”,然后按下橙色方块; 2. 点击向右的箭头,直到显示“Visibility”,然后按下橙色方块; 3. 如果“Visibility”值已设定为可见,继续步骤4;如果不可见,请单击向左或向右箭头设置其值为可 见; 4. 在Android设备上,进入设置→无线与网络; 5. 确保打开蓝牙功能; 6. 点击“蓝牙”; 7. 在“可用设备”中查找名为“NXT”的设备; 8. 如果机器人名字下显示“已配对但未连接”字样,则配对成功!否则,继续执行步骤9; 9. 如果机器人名字下显示“与此设备配对”,则点击它; 10. 在NXT上,要求输入密码,按下橙色方块接受1234为密码; App Inventor 编程实例及指南 - 189 -本文档使用 看云 构建 11. 在Android上,也会要求输入PIN码,输入1234,然后按确定; 12. 现在应该看到“已配对但未连接。”,说明配对成功!  注意:如果你曾经修改过机器人的名字,则寻找机器人现在的名字,而非“NXT”。 连接到App Inventor网站,创建新项目“NXTRemoteControl”,将设置屏幕的标题为“遥控机器人”, 并连接测试香港老钱庄868525,(六合娃娃。 设计组件设计组件 在这个应用中,我们需要分别创建可见组件及不可见组件,并分别定义它们的行为。 不可见组件不可见组件 在创建用户界面之前,先来创建表12-1中的不可见组件,如图12-1所示,用来控制NXT。 表12-1 NXT“机器人遥控”应用中的不可见组件表12-1 NXT“机器人遥控”应用中的不可见组件 组件类型组件类型 面板中分组面板中分组 命名命名 作用作用 BluetoothClient Connectivity BluetoothClient1 建立Android与NXT 的连接 NxtDrive LEGO® MINDSTORMS® NxtDrive1 驱动机器人的轮 NxtUltrasonicSensor LEGO® MINDSTORMS® NxtUltrasonicSensor1 检测障碍物 Notifier User Interface Notifier1 显示错误信息 图 12-1 在组件设计器底部显示的不可见组件图 12-1 在组件设计器底部显示的不可见组件 按以下方式设置组件的属性: 1. 设置NxtDrive1及NxtUltrasonicSensor1的BluetoothClient属性为 BluetoothClient1;(说明轮子的 驱动与障碍物的侦测都需要依赖蓝牙通信——译者注) 2. 勾选NxtUltrasonicSensor1的BelowRangeEventEnabled属性(近距离侦测障碍物功能可用); 3. 设置NxtDrive1的DriveMotors属性 如果机器人的左轮电机与输出端口C连接,右轮电机与输出端口B连接,则保持默认设置“CB”; App Inventor 编程实例及指南 - 190 -本文档使用 看云 构建 如果机器人的配置与上述不同,则将DriveMotors属性设置为两个字母的文本,其中第一个字母是连 接左轮电机的输出端口,第二个字母是连接右轮电机的输出端口。 4. 设置NxtUltrasonicSensor1的SensorPort属性 如果机器人的超声波传感器与输入端口4连接,则保持默认值“4”; 如果机器人的配置与上述不同,则将SensorPort设置为与超声波传感器连接的输入端口。 可视组件可视组件 现在创建用户界面组件,如图12-2所示。 图 12-2 组件设计器中的应用图 12-2 组件设计器中的应用 建立蓝牙连接时,Android设备需要访问NXT机器人具有唯一性的蓝牙322555现场开奖,香港马会开奖结果直播,但蓝牙322555现场开奖,香港马会开奖结果直播由8个用冒号分隔 的2位数的十六进制数(二进制数的另一种表示方式)组成,输入起来异常麻烦,而且每次运行应用都要在 香港老钱庄868525,(六合娃娃上输入该322555现场开奖,香港马会开奖结果直播。为了减少麻烦,使用ListPicker来显示已经与香港老钱庄868525,(六合娃娃配对的机器人列表(列表项的值为机 器人的名称及蓝牙322555现场开奖,香港马会开奖结果直播),并从中选择一个。 使用按键来驱动机器人的前进、后退、左右转动、停止和断开连接,使用VerticalArrangement来放置除 ListPicker以外的所有组件,用HorizontalArrangement来放置左右转向及停车按钮。 按照表12-2中列出的组件来创建图12-2所示的用户界面。 表12-2 NXT机器人控制器应用中的可见组件表12-2 NXT机器人控制器应用中的可见组件 App Inventor 编程实例及指南 - 191 -本文档使用 看云 构建 组件类型组件类型 面板中分组面板中分组 命名命名 作用作用 ListPicker User Interface ConnectListPicker 选择要连接的机器人 VerticalArrangement layout VerticalArrangement1 布局容器,容纳除ListPicker 之外的组件 Button User Interface ForwardButton 前进 HorizonalArrangement layout HorizonalArrangement1 布局容器,容纳左转、右 转、停止按钮 Button User Interface LeftButton 左转 Button User Interface StopButton 停止 Button User Interface RightButton 右转 Button User Interface BackwardButton 后退 Button User Interface DisconnectButton 与NXT断开连 按照图12-2所示来设置可视组件布局:将LeftButton、StopButton和RightButton放在 HorizontalArrangement1中,将ForwardButton、HorizontalArrangement1、BackwardButton和 DisconnectButton放在VerticalArrangement1中。 按下列方式设置组件属性: 1. 取消勾选Screen1的Scrollable属性(滚屏功能); 2. 设置ConnectListPicker和DisconnectButton的宽度为“Fill parent”; 3. 设置VerticalArrangement1、ForwardButton、HorizontalArrangement1、LeftButton、 StopButton、RightButton及BackwardButton的Width与Height为“Fill parent”; 4. 设置ConnectListPicker的Text属性为“连接”; 5. 设置ForwardButton的Text属性为“∧”; 6. 设置LeftButton的Text属性为“<”; 7. 设置StopButton的Text属性为“—”; 8. 设置RightButton的Text属性为“>”; App Inventor 编程实例及指南 - 192 -本文档使用 看云 构建 9. 设置BackwardButton的Text属性为“∨”; 10. 设置DisconnectButton的Text属性为“断开连接”; 11. 设置ConnectListPicker和DisconnectButton的FontSize属性为30; 12. 设置ForwardButton、LeftButton、StopButton、RightButton及BackwardButton的FontSize属性 为40。 在这类应用中,当香港老钱庄868525,(六合娃娃与NXT建立蓝牙连接之前,应该隐藏用户的操作界面,为此取消勾选 VerticalArrangement1的Visible属性。不要担心,当NXT连通后,将重新显示用户界面。 为组件添加行为为组件添加行为 本节将编程来设置应用的行为,包括: 用户从列表中选择机器人,并与之建立连接; 断开机器人与应用的连接; 使用控制按钮来操控机器人; 在机器人侦测到障碍物时,让它停下来。 连接到NXT机器人连接到NXT机器人 添加第一个行为:连接到NXT。点击 ConnectListPicker将显示已配对的机器人列表,选中一个,将在应 用与机器人之间建立蓝牙连接。 显示机器人列表显示机器人列表 使用ConnectListPicker组件来显示机器人列表。ListPicker的外表像按钮,被点击后则显示列表项,并允 许进行单选。 使用BluetoothClient1.AddressesAndNames块来提供列表,列表项是已经与Android设备配对的蓝牙设 备的名称及322555现场开奖,香港马会开奖结果直播。由于NXT已经将轮驱动及超声波组件的BluetoothClient属性设定为BluetoothClient1, 因此AddressesAndNames属性列表中的设备会自动限定为这类机器人,其他类型的蓝牙设备(如耳机) 将不会出现在列表中。表12-3列出了所需要的块。 表12-3 在应用中添加ListPicker列表所需要的块表12-3 在应用中添加ListPicker列表所需要的块 块的类型块的类型 所在抽屉所在抽屉 作用作用 ConnectListPicker.BeforePicking ConnectListPicker 当ConnectListPicker被点击时,触发该 事件 set ConnectListPicker.Elements to ConnectListPicker 为ConnectListPicker设置可供选择的列 表项 App Inventor 编程实例及指南 - 193 -本文档使用 看云 构建 块的类型块的类型 所在抽屉所在抽屉 作用作用 块的作用块的作用 点击ConnectListPicker将触发ConnectListPicker.BeforePicking事件,并显示可选项列表。将 ConnectListPicker.Elements属性设置为 BluetoothClient1.AddressesAndNames块,来设定可选项; ConnectListPicker将显示已经与Android设备配对的机器人列表。 图 12-3 显示机器人列表图 12-3 显示机器人列表  测试:在香港老钱庄868525,(六合娃娃上点击“连接”,看看会发生什么,你会看到所有已经与香港老钱庄868525,(六合娃娃配对的机器人 列表。 如果只见黑屏,说明香港老钱庄868525,(六合娃娃尚未与任何机器人配对;如果见到其他蓝牙设备,如蓝牙耳机,说明 NxtDrive1 与 NxtUltrasonicSensor1的BluetoothClient属性设置有误。 建立蓝牙连接建立蓝牙连接 从列表中选择一个机器人,应用将通过蓝牙与机器人连接。如果连接成功,用户界面将发生变化:隐藏 ConnectListPicker,并显示用户界面的其余部分。如果机器人开关没有打开,则连接失败,会弹出错误信 息。 使用call BluetoothClient1.Connect块与机器人进行连接。ConnectListPicker.Selection属性提供了选中 机器人的322555现场开奖,香港马会开奖结果直播和名称信息。 使用ifelse块来测试连接是否成功。ifelse块需要连接三个不同的块:“if”、“then”及“else”。 “if”与BluetoothClient1.Connect块连接,“then”区域放置连接成功时要执行的块;“else”区域放 置连接失败时要执行的块。 如果连接成功,使用Visible属性来隐藏 ConnectListPicker并显示VerticalArrangement1(其中放置了除 ConnectListPicker之外的所有组件)。如果连接失败,则使用Notifier1.ShowAlert块来显示错误信息。 表12-4列出了设置上述行为所需的块。 表12-4与机器人建立蓝牙连接所需的块表12-4与机器人建立蓝牙连接所需的块 App Inventor 编程实例及指南 - 194 -本文档使用 看云 构建 块的类型块的类型 所在抽屉所在抽屉 作用作用 ConnectListPicker.AfterPicking ConnectListPicker 当从ConnectListPicker选中一个机器 人时触发 ifelse Control 检验蓝牙连接是否成功 call BluetoothClient1.Connect BluetoothClient1 连接到机器人 ConnectListPicker.Selection ConnectListPicker 选中的机器人的322555现场开奖,香港马会开奖结果直播及名称 set ConnectListPicker.Visible to ConnectListPicker 隐藏ConnectListPicker按钮 false Logic 插入set ConnectListPicker.Visible to 块 set VerticalArrangement1.Visible to VerticalArrangement 显示“连接”按钮之外的所有组件 true Logic 插入set VerticalArrangement1.Visible to块 Notifier1.ShowAlert Notifier1 用来弹出错误信息 “无法建立蓝牙连接。” Text 错误信息。 块的作用块的作用 选中机器人后将触发ConnectListPicker.AfterPicking事件,见图12-4,BluetoothClient1.Connect块用 于建立与机器人之间的蓝牙连接。如果连接成功,执行“then”块:隐藏ConnectListPicker按钮并显示 VerticalArrangement1内的所有组件,即,设置ConnectListPicker.Visible属性为false,设置 VerticalArrangement1.Visible属性为true。如果连接失败,执行“else”块:用Notifier1.ShowAlert块 弹出错误信息。 App Inventor 编程实例及指南 - 195 -本文档使用 看云 构建 图 12-4 建立蓝牙连接图 12-4 建立蓝牙连接 与NXT断开连接与NXT断开连接 让Android设备与NXT机器人连接着实让人兴奋,不过“断开连接”是我们下面要添加的行为,这样便于 对连接与断开进行连续测试。 当点击DisconnectButton时,应用将关闭蓝牙连接,用户界面将发生变化:ConnectListPicker按钮将重 新出现,而用户界面上的其余组件将被隐藏。 表12-5列出了构建BluetoothClient1.Disconnect(断开蓝牙连接)所需的块。设置Visible属性来显示 ConnectListPicker按钮并隐藏VerticalArrangement1中包含的所有组件。 表12-5 与机器人断开连接所需的块表12-5 与机器人断开连接所需的块 块的类型块的类型 所在抽屉所在抽屉 作用作用 DisconnectButton.Click DisconnectButton 当点击DisconnectButton时触发该 事件 BluetoothClient1.Disconnect BluetoothClient1 断开与机器人的蓝牙连接 set ConnectListPicker.Visible to ConnectListPicker 显示ConnectListPicker(“连 接”按钮) true Logic 插入set ConnectListPicker.Visible to块 set VerticalArrangement1.Visible to VerticalArrangement 隐藏用户界面上的其余组件 false Logic 插入set VerticalArrangement1.Visible to 块的作用块的作用 点击DisconnectButton将触发DisconnectButton.Clicked事件,如图12-5所示,断开蓝牙连接要用 BluetoothClient1.Disconnect块,之后设置ConnectListPicker.Visible属性为true来显示 ConnectListPicker,设置VerticalArrangement1.Visible属性为false来隐藏VerticalArrangement1。 App Inventor 编程实例及指南 - 196 -本文档使用 看云 构建 图 12-5 与机器人断开连接图 12-5 与机器人断开连接  测试:请确保机器人已经打开,点击香港老钱庄868525,(六合娃娃上的“连接”按钮,并选择要连接的机器人。建 立蓝牙连接需要一点时间。一旦连接成功,用户界面将显示机器人的控制按钮,以及“断开连接”按 钮。 单击“断开连接”按钮:控制机器人的按钮会消失,“连接”按钮则重新出现。 操控机器人操控机器人 下面是真正有趣的部分:添加前进、后退、左右转动及停止行为。不要忘记“停止”,否则你手中的机器 人会失去控制! NxtDrive组件提供了五个块,用来驱动机器人的电机: MoveForwardIndefinitely块:驱动两个电机前进; MoveBackwardIndefinitely块:驱动两个电机后退; TurnCounterClockwiseIndefinitely块:驱动机器人左转:让右侧电机向前而左侧电机后退; TurnClockwiseIndefinitely块:驱动机器人右转:让左侧电机向前而右侧电机后退; Stop将停止电机。 每个移动及转向块都有一个Power参数,需要与数字块配合使用,来设定机器人电机的输出功率,取值范 围可以从 0到100。但如果设置的功率太小,电机会发出吱吱声而不运转。在本例中建议使用90(百分 比)。表12-6中列出了所需的块。 表12-6 用于控制机器人的块表12-6 用于控制机器人的块 块的类型块的类型 所在抽屉所在抽屉 作用作用 ForwardButton.Clic ForwardButton 点击ForwardButton时触 NxtDrive1.MoveForwardIndefinitely NxtDrive1 驱动机器人前进 数字90 Math 功率值 BackwardButton.Click BackwardButton 点击BackwardButton时触发 NxtDrive1.MoveBackwardIndefinitely NxtDrive1 驱动机器人后退 数字90 Math 功率值 LeftButton.Click LeftButton 点击LeftButton时触发 NxtDrive1.TurnCounterClockwiseIndefinitely NxtDrive1 驱动机器人逆时针转动 数字90 Math 功率值 App Inventor 编程实例及指南 - 197 -本文档使用 看云 构建 RightButton.Click RightButton 点击RightButton时触发 NxtDrive1.TurnClockwiseIndefinitely NxtDrive1 驱动机器人顺时针转动 数字90 Math 功率值 StopButton.Click StopButton 点击StopButton时触发 NxtDrive1.Stop NxtDrive1 让机器人停止 块的类型块的类型 所在抽屉所在抽屉 作用作用 块的作用块的作用 如图12-6所示,点击ForwardButton按钮时触发ForwardButton.Clicked事件,此时调用 NxtDrive1.MoveForwardIndefinitely块,让机器人以90%的功率前进,其余按钮的事件处理程序与此类 似,并以相同的功率驱动机器人后退及左右转动。点击StopButton时触发StopButton.Clicked事件,调用 NxtDrive1.Stop块让机器人停止运动。 图 12-6 操控机器人图 12-6 操控机器人  测试:按照此前的“测试”说明,先连接NXT机器人。不要将机器人放在桌子上,以免跌 落,然后测试以下行为: 1. 点击前进按钮,机器人应该向前移动; App Inventor 编程实例及指南 - 198 -本文档使用 看云 构建 2. 点击后退按钮,机器人应该向后移动; 3. 点击左转按钮,机器人应逆时针转动; 4. 点击右转按钮,机器人应顺时针转动; 5. 点击停止按钮,机器人应停止。 如果机器人不动并发出吱吱声,可能需要加大电机的功率,可以用最大功率100。 用超声波传感器探测障碍物用超声波传感器探测障碍物 使用超声波传感器的机器人可以侦测到30厘米范围内的障碍物,遇到障碍物时机器人会像罪犯一样停下 来,如图12-7所示。 图 12-7 为NXT机器人设置障碍图 12-7 为NXT机器人设置障碍 NxtUltrasonicSensor组件用于侦测障碍物,两个属性BottomOfRange和TopOfRange用来定义侦测范围 (以厘米为单位)。默认设定BottomOfRange为30厘米,TopOfRange为90厘米。 NxtUltrasonicSensor组件具有三个事件BelowRange、WithinRange及boveRange,当侦测到障碍物在 BottomOfRange(下限)距离以内时,会触发BelowRange事件;当障碍物的距离在BottomOfRange与 TopOfRange (上下限)之间时,会触发WithinRange事件;当障碍物的距离超过TopOfRange(上限) 时,将触发AboveRange事件。 这里使用NxtUltrasonicSensor1.BelowRange事件块,用来侦测30厘米以内的障碍物,如果你想尝试侦测 不同距离的障碍物,可以调整BottomOfRange属性。当BelowRange时间发生时,使用NxtDrive1.Stop 块让机器人停下来。表12-7中列出了所需的块 表12-7 使用NxtUltrasonicSensor需要的块表12-7 使用NxtUltrasonicSensor需要的块 App Inventor 编程实例及指南 - 199 -本文档使用 看云 构建 块的类型块的类型 所在抽屉所在抽屉 作用作用 NxtUltrasonicSensor1.BelowRange NxtUltrasonicSensor1 超声波传感器在30厘米内遇到障 碍物时触发 NxtDrive1.Stop NxtDrive1 让机器人停下来 块的功能块的功能 当机器人的超声波传感器侦测到30厘米以内的障碍物时,NxtUltrasonicSensor1.BelowRange事件被触 发,如图12-8所示,此时NxtDrive1.Stop块让机器人停下来。 图 12-8 侦测障碍物图 12-8 侦测障碍物  测试:按照此前的“测试”说明,先连接NXT机器人。引导机器人朝着障碍物(如猫)的 方向前进,机器人将在距离猫30厘米时停下来。如果机器人没停下来,可能是猫已经远离了机器人, 它们之间的距离一直大于30厘米。可以换一个静止的障碍物来进行测试。 改进改进 应用运行起来,想必你已经花了大量时间来操控这个机器人,不过还是想继续其他的尝试: 调节驱动电机的输出功率: 可以修改插入到前进(MoveForwardIndefinitely)、后退(MoveBackwardIndefinitely)、左转 (TurnCounterclockwiseIndefinitely)及右转(TurnClockwiseIndefinitely) 块中的数字块的值。 当侦测到障碍物时,使用NxtColorSensor让红灯闪烁: 可以使用NxtColorSensor组件及其GenerateColor属性; 需要将DetectColor属性设置为false(或在组件设计器取消勾选该属性),因为颜色传感器无法同 时检测和产生颜色。 使用Android的方向传感器OrientationSensor来控制机器人。 使用乐高的构造元件建立香港老钱庄868525,(六合娃娃与机器人之间的物理连接,创建应用实现机器人的自主性。 小结小结 以下是本章涵盖的内容: App Inventor 编程实例及指南 - 200 -本文档使用 看云 构建 ListPicker组件:让用户可以从已配对的机器人列表中进行选择; BluetoothClient组件:使Android设备与机器人建立连接; Notifier组件:用来显示错误消息; Visible属性:用于隐藏或显示用户界面中的组件; NxtDrive组件:可以控制机器人的移动、转向及停止; NxtUltrasonicSensor组件:用于侦测障碍物。 外部链接外部链接 LEGO MINDSTORMS EV3控制程序 Android应用控制LEGO EV3(Video) App Inventor 编程实例及指南 - 201 -本文档使用 看云 构建 第 13 章 亚马逊掌上书店 假设你正在一家你很喜欢的书店里翻书,你想了解某一本书在Amazon.com(网上书店)的售价,那么这 款“亚马逊掌上书店”应用就可以帮你实现这一愿望。通过扫描书上的条码,或输入书上的ISBN,应用将 告诉你这本书当前在Amazon.com的最低售价。你也可以按照主题进行书籍的搜索。 “亚马逊掌上书店”演示了如何使用App Inventor来创建与web service进行交互的应用(web service又 称作API 或应用程序接口)。本应用将从一个web services上获取数据,该web services是由本书的作者 之一创建的。由本章结束时,你将为自己创建一款定制的应用,来访问亚马逊网上书店。 该应用界面非常简单,用户可以输入关键字或书的ISBN 码,应用将列出书名、ISBN以及新书在亚马逊上 的最低售价。也可以使用条码扫描组件,让户可不必输入文本,而是通过扫描来进行搜索(从技术上讲, 是扫描仪替你输入了书的ISBN!)。 学习要点学习要点 本章将学习以下内容: 在应用中使用条形码扫描仪功能; 用TinyWebDB组件访问Web信息源(Amazon API); 学会处理从web信息源返回的复杂数据。所谓复杂数据,具体来说,就是图书列表中的每本书都是一 个列表,其中包含三个列表项:书名、价格及ISBN。 此外我们将介绍用Python语言及谷歌的App引擎编写的源代码,并用它来创建自己的web service API。 什么是API?什么是API? 在开始设计组件和编写应用之前,我们来解释一下什么是API(应用程序接口),以及它如何工作。可以把 API想象为一个网站,只是它不与人类交互,而是与其他计算机程序交互。 App Inventor 编程实例及指南 - 202 -本文档使用 看云 构建 图 13-1 模拟器中的“亚马逊掌上书店”图 13-1 模拟器中的“亚马逊掌上书店” API通常被称作“服务器端”程序,因为它们的特点就是为“客户端”程序提供信息。“客户端”程序负责 实现与人类的接口——如App Inventor应用。如果你曾经在香港老钱庄868525,(六合娃娃上使用Facebook 应用,你实际上是通过 Facebook的客户端程序与Facebook的服务器端程序(API)进行通信。 本章将创建一个Android客户端应用,与Amazon API进行通信。应用将向Amazon API请求书的信息(书 名、书号及价格等),而API将向应用返回最新的信息列表,用户将在应用中浏览到书的相关信息。 我们即将使用的Amazon API是App Inventor专用的API。这里我们不想过多地解释细节,但有关配置的 知识是非常有用的,正是由于有了这些配置,我们才能用TinyWebDB组件与Amazon进行沟通。好在你已 经学会了使用TinyWebDB!调用TinyWebDB.GetValue来请求信息,然后在TinyWebDB.GotValue事件 处理程序中处理返回的信息,就像在用Web数据库一样。(如果忘记了,去复习一下第10章的“出题”应 用。) 在创建应用之前,需要先了解一下Amazon API协议,协议规定了请求数据的方式以及返回数据的格式。 就像不同的族群有不同的礼仪(两人相遇时,是握手、鞠躬还是点头?),计算机之间的互相则需要有协 议 。 Amazon API为调用者提供了一个Web页面,来说明API的使用方法。虽然设计API的目的是为了与其它计 算机交互,但在这个页面上,你可以看到这种交互的过程。 按照下列步骤,你可以尝试调用一个指定tag 参数的GetValue,并在页面上看到返回的数据,这与你在App Inventor中使用TinyWebDB组件请求数据 的结果完全一致: App Inventor 编程实例及指南 - 203 -本文档使用 看云 构建 1. 在浏览器中访问网站http://aiamazonapi.appspot.com/,你会看到如图13-2所示的页面(页面中的中 文为译者添加)。 图 13-2 App Inventor专用的Amazon API的说明及测试页面图 13-2 App Inventor专用的Amazon API的说明及测试页面 2. 本页面允许你对与此API的GetValue功能进行测试:在tag输入框中输入搜索词(如“natural computing”),然后单击“Get value”按钮。页面将显示从Amazon API返回的排在前五位的书籍列 表,如图13-3所示。 图 13-3 调用Amazon API来搜索与tag(或关键字)“natural computing”有关的书籍图 13-3 调用Amazon API来搜索与tag(或关键字)“natural computing”有关的书籍 返回值是一个书的列表,每本书的信息由一对方括号包围[像这样],提供了书名、售价及ISBN。如果仔细 观察,你会发现每本书其实是另一主列表的子列表。主列表(natural computing)由外层的方括号包 围,每个子列表(或书)被封闭在单独的一对方括号内。所以此API的返回值实际上是一个列表的列表,每 个子列表提供一本书的信息。我们来细致地观察一下。 数据中的每个左括号([)标志列表的开头。第一左括号标志外层列表(书籍列表)的开始,紧挨着它的左 括号是第一个子列表,即第一本书的开头: [['"Natural Computing: DNA, Quantum Bits, and the Future of Smart Machines"', '$5.77', App Inventor 编程实例及指南 - 204 -本文档使用 看云 构建 '0393336832'], 子列表包含三个部分:书名、该书在亚马逊书店的最低售价及这本书的ISBN。当你的App Inventor应用取 得这些信息时,就可以使用select list item块来访问其中的每个部分,用索引值1访问书名,索引值2访问 价格,索引值3访问ISBN。(如果淡忘了有关列表及索引的使用方法,请复习第十章的“出题”应用。) 3. 除了搜索关键字,你还可以通过ISBN来精确地搜索一本书,只要在tag后面直接输入书号即可,试试输 入“1449397484”,如图13-4所示。返回结果如下: [['"App Inventor: Create Your Own Android Apps"', '$21.64', '1449397484']] 返回结果中的双括号([[)表示返回的仍然是一个列表的列表,虽然列表中只有一本书。这似乎有点奇怪, 但这一点对需要访问这类信息的应用来说非常重要。 图 13-4 用ISBN替代关键字在AmazonAPI中查询书籍图 13-4 用ISBN替代关键字在AmazonAPI中查询书籍 设计组件设计组件 “掌上书店”应用的用户界面比较简单:一个用于输入搜索词或ISBN的文本框,一个用于启动搜索(关键 字或ISBN)的按钮,另一个启动扫描书的条码的按钮(稍后会用到),一个搜索结果标题的label,另一个 显示搜索结果的label,还有两个非可视组件:TinyWebDB和条码扫描仪。表13-1列出了图13-5中所示的 所有组件,对照检查你的结果。 App Inventor 编程实例及指南 - 205 -本文档使用 看云 构建 图 13-5 组件设计器中“亚马逊掌上书店”的用户界面图 13-5 组件设计器中“亚马逊掌上书店”的用户界面 表13-1 “亚马逊掌上书店”应用的组件列表表13-1 “亚马逊掌上书店”应用的组件列表 组件类型组件类型 面板中分组面板中分组 命名命名 作用作用 Textbox User Interface SearchTextBox 用户在此输入关键字或 ISBN HorizontalArrangement Layout HorizontalArrangement1 将按钮放置在同一行内 Button User Interface SearchButton 点击启动关键字或ISBN 搜索 Button User Interface ScanButton 点击开始扫描书的ISBN 条码 Label User Interface Label1 搜索结果的标题 Label User Interface ResultLabel 显示搜索结果 TinyWebDB Storage TinyWebDB1 与Amazon.com交互 BarcodeScanner Sensors BarcodeScanner1 扫描条码 App Inventor 编程实例及指南 - 206 -本文档使用 看云 构建 组件的属性设置如下: 1. 设置SearchTextBox的Hint属性为“输入关键字或ISBN”; 2. 设置Button及Label的Text属性:如图13-5所示; 3. 设置TinyWebDB组件的serviceURL属性为http://aiamazonapi.appspot.com/ 。 设计行为设计行为 在块编辑器中设定下列行为: 按关键字搜索:用户输入关键字并点击SearchButton来调用Amazon搜索。通过调用 TinyWebDB.GetValue来实现这一点; 按ISBN搜索:用户输入一个ISBN并点击SearchButton; 条码扫描:用户点击ScanButton启动扫描仪,扫描ISBN完成后将启动Amazon搜索; 处理图书列表:首先采用默认方式显示Amazon返回的数据。稍后再加以修改,采用有组织的方式来显 示每本书的书名、售价及ISBN。 按关键字或ISBN搜索按关键字或ISBN搜索 用户点击SearchButton时,从SearchTextbox中获取搜索内容,并以此为tag,使用 TinyWebDB.GetValue块向Amazon API请求搜索数据。 当Amazon返回结果时,将触发TinyWebDB.GotValue事件。现在用ResultsLabel直接显示返回结果,如 图13-6所示,当见到这些真实的数据后,可以采取更为复杂的方式来显示数据。 图 13-6 向API发送搜索请求,并将结果显示在ResultsLabel中图 13-6 向API发送搜索请求,并将结果显示在ResultsLabel中 块的作用块的作用 当点击SearchButton时,TinyWebDB1.GetValue开始向API发出请求。随请求一同发送的tag正是用户在 SearchTextBox中输入的内容。 从第十章“出题”应用中得知,TinyWebDB.GetValue发出的请求并不能立即获得结果,而是在收到从 App Inventor 编程实例及指南 - 207 -本文档使用 看云 构建 API返回的数据时,触发TinyWebDB1.GotValue事件。在GotValue块中,首先检查返回值是否为列表, 如果是,则数据被写入ResultsLabel。(如果Amazon API离线或搜索的关键字不存在,将没有数据返 回。)再输入ISBN“1118717376”试试看。 图 13-7 搜索“Android”返回的结果图 13-7 搜索“Android”返回的结果  测试:在输入框中输入搜索词,如“Android”,然后点击“搜索”按钮。你将得到类似 于图13-7的结果。注意:香港老钱庄868525,(六合娃娃上返回的结果与网页上的结果略有不同:方括号变成了圆括号。(这个 界面看起来很糟糕,稍后我们会处理。) 消除用户的困惑消除用户的困惑 在前几章中了解到,使用TinyWebDB请求数据的过程需要一点时间,在收到返回的数据之前,用户界面上 悄无声息,这时用户会感到困惑,因此从用户友好的角度考虑,添加一点提示是非常必要的。这里我们用 ResultLabel来显示提示信息。如图13-8所示。但如果网络速度很快,数据很快返回并触发GotValue事 件,则几乎看不到提示,数据就显示出来了。 图 13-8 添加提示信息消除用户的困惑图 13-8 添加提示信息消除用户的困惑 扫描一本书扫描一本书 现实情况是:在香港老钱庄868525,(六合娃娃上输入字符通常不那么容易,而且总会出一些小错。如果能够在应用中使用条码扫 描,那么问题会变得简单(并且几乎不会出错)。这是Android香港老钱庄868525,(六合娃娃内置的另一项强大的功能,你可以用 App Inventor 编程实例及指南 - 208 -本文档使用 看云 构建 App Inventor轻而易举地实现它。 函数BarcodeScanner.DoScan用于启动扫描仪,可以在ScanButton被点击时调用它。一旦扫描操作完 成,将触发BarcodeScanner.AfterScan事件。该事件带有一个参数result,其中保存了扫描所获得的信 息。在本例中,result即是用于搜索的ISBN,如图13-9所示。 图 13-9 用户扫描书上的条码获得ISBN并启动搜索图 13-9 用户扫描书上的条码获得ISBN并启动搜索 块的作用块的作用 点击ScanButton将启动扫描仪,即执行BarcodeScanner1.DoScan。扫描完成时触发AfterScan事件。该 事件带有result参数,在本例中为书的ISBN。用ResultLabel告诉用户正在进行搜索,并在SearchTextBox 中显示result(扫描获得的ISBN),最后调用TinyWebDB.GetValue来启动搜索。仍然使用之前定义的 TinyWebDB.GotValue事件处理程序来处理返回的书籍信息。  测试:点击ScanButton并扫描一本书的条码。应用中是否显示了该图书的信息? 改进信息的显示改进信息的显示 我们所创建的这类客户端应用,可以按需要来处理收到的数据,可以与其他网上商店进行价格比较,也可 以用书名信息来搜索其他图书馆中的同类书籍。 通常是将加载自API的信息保存到变量中再做处理,而目前只是在TinyWebDB.GotValue事件处理程序 中,将Amazon API返回的所有信息直接写入到ResultsLabel中。 下面我们来处理(或这说安排)这些数据,方法是(1)将返回数据中的每本书的书名、售价以及ISBN分 别保存到单独的变量中;(2)以一种有序的方式显示这些项目。 到目前为止,你已熟知了全局变量(global variable),与之对应的是局部变量(local variable),这是 本章引入的一个新的概念。一个变量有它的有效范围:全局变量直接在块编辑器中定义,单独占一行,不 属于任何一组块,而所有的块都可以调用全局变量;局部变量则定义在某个程序块内部,它将包含其它 块,而且只对其包含的块有效,其余的所有块都无法访问到它。 App Inventor 编程实例及指南 - 209 -本文档使用 看云 构建 下面将使用这些变量,并将它们显示出来,试试看按需要创建这些变量,并组织一些块来分行显示每一项 搜索结果,完成之后与图13-10进行比较。 块的作用块的作用 这里定义了四个变量:resultList、title、cost及ISBN,用来保存Amazon API返回数据中的每一条数据。 从API返回的数据保存在参数valueFromWebDB中,这里将它另存为resultList。其实程序可以直接使用 valueFromWebDB,但通常会将它另存为一个变量,以便在该事件处理程序之外也可以使用这一数据。 (像valueFromWebDB这样的参数仅在事件处理程序内有效,事件处理程序之外无法访问该参数值。) foreach循环用来遍历返回结果中的每个数据项。回想一下,从Amazon返回的数据是一个列表的列表,每 个子列表代表一本书的信息,因此foreach块中的项变量被命名为bookItem,它保存了当前正在被遍历的 书的信息。 现在我们要清醒地面对项变量bookitem,它是一个列表,其中第一项是书名,第二项是售价,第三项是 ISBN,因此,select list item块利用索引值(index)将这些数据逐项提取出来,并保存在事先定义的变 量中(title、cost及ISBN)。 App Inventor 编程实例及指南 - 210 -本文档使用 看云 构建 图 13-10 在遍历过程中提取每本书的书名、售价及ISBN,并逐行显示它们图 13-10 在遍历过程中提取每本书的书名、售价及ISBN,并逐行显示它们 一旦数据被拆解成这种方式,就可以随意地摆布它们。程序中的局部变量只是作为join块的组成部分,来 逐行显示书名、售价及ISBN。  测试:尝试搜索其他书,看看返回的信息是如何显示的。应该类似于图13-11。 App Inventor 编程实例及指南 - 211 -本文档使用 看云 构建 图 13-11 用更有条理的方式显示搜索结果图 13-11 用更有条理的方式显示搜索结果 定制化API定制化API 我们所连接的API(http://aiamazonapi.appspot.com)是由Python(编程语言)和谷歌应用引擎(App Engine)创建的。开发者可以将应用引擎上创建并发布网站或服务(API)。只有当你的网站或服务非常受欢 迎时(这意味着你使用了大量的谷歌服务),才需要向App Engine付费。 这里使用的API只能访问全部Amazon API中的有限的部分:最多只能查询到五本书。如果想提供更多灵活 的访问,例如,不仅是找书。你可以从http://appinventorapi.com/amazon/下载源代码,并按照自己的 需要来修改它。 这种修改确实需要有Python编程的知识,所以要小心!但是加入你已经通过本书完成了App Inventor的 学习,那么是该考虑迎接新的挑战了。想要学习Python,可以查看网上的文章《如何像计算机科学一样思 考:学会使用Python》(http://openbookproject.net//thinkCSpy/),并查看本书第24章的“创建App Inventor API”部分。 改进改进 一旦应用运行起来,你可能会探索做一些改进。如: 如果用户的搜索没有任何返回值(比如当用户输入了一个无效的ISBN),程序没有任何反馈,修改或 添加块,当没有返回值时通知用户; App Inventor 编程实例及指南 - 212 -本文档使用 看云 构建 修改程序,只查找低于10美元的书籍; 修改程序,当扫描一本书后,用声音来报告Amazon的最低售价(使用第七章“Android,我的车在哪 儿”中用过的TextToSpeech组件); 从http://examples.oreilly.com/0636920016632/下载http://aiamazonapi.appspot.comAPI,修改 它,使之能返回更详细的信息。例如,返回每本书在Amazon上的网址(URL),并随每本书的信息一 同显示;用户点击URL可以打开相应的页面。正如前面提到的,修改API需要Python编程和谷歌App Engine的一些知识。更多信息请参见第24章。 小结小结 以下是本章涵盖的内容: 使用TinyWebDB组件以及指定的API,在应用中访问互联网API。将TinyWebDB组件的serviceURL属 性设置为API的322555现场开奖,香港马会开奖结果直播(URL),并调用TinyWebDB.GetValue来发出请求。请求的数据不会立即返回, 可以在TinyWebDB.GotValue事件处理程序中访问到该数据; BarcodeScanner.DoScan可以启动扫描仪。当用户完成条码扫描时,将触发 BarcodeScanner.AfterScan事件,并将扫描获得的数据保存在参数result中; 在App Inventor中,复杂数据可以表示为列表以及列表的列表。如果知道从API返回的数据的格式,就 可以使用foreach及select list item来分别提取没条信息,并将其保存到变量,这样就能以你需要的方 式,对变量进行处理或显示。 App Inventor 编程实例及指南 - 213 -本文档使用 看云 构建 第 14 章 理解应用的结构 本章将从程序员的视角来探讨应用的结构问题。先从一个经典的比喻开始,将应用理解为一份菜谱,然后 再从组件的角度,理解其对事件的响应,从而对应用产生新的认识。本章还将探讨应用如何提问、重复、 记忆以及与Web交互,所有这些在后面章节均有更详细的叙述。 大多数人是从用户的角度来描述一个应用,但是在程序员看来,应用要复杂得多。我们必须充分理解应用 的内部结构,才能更加有效地创建应用。 通常从两个方面来描述应用的内部结构:组件及行为,这大致与App Inventor的两个主要窗口相对应:组 件设计器及块编辑器。前者用来设定应用中的对象(组件),而后者用来编写程序,实现对用户及外部事 件的响应(应用程序的行为)。 在图14-1中,对应用的架构给出了总体描述,本章将对这种架构进行深入细致的探讨。 图 14-1 App Inventor应用的内部结构图 14-1 App Inventor应用的内部结构 组件组件 App Inventor 编程实例及指南 - 214 -本文档使用 看云 构建 应用中的组件分为两大类:可视组件及非可视组件。可视组件是在应用启动后能够看到的组件,如 Button、Textbox及Label等,这些通常被视为应用的用户界面。 非可视组件是不可见的,因此它们不是用户界面的组成部分,通常用于访问设备的内置功能,如,Texting 组件用于收发短信,LocationSensor组件用于确定设备的位置,而TextToSpeech组件用于朗读文字。非 可视组件是设备的技术核心,是服务于应用程序的小精灵。 两类组件都是由一组属性来定义,属性相当于组件信息的存储空间。如可视组件的Width、Height及 Alignment属性,它们共同定义了组件的外观。因此,最终用户所看到的如图14-2所示的“Submit”按 钮,实际上是在组件设计器中由一组属性所定义的,如表14-1所示。 表14-1 按钮属性表14-1 按钮属性 WidthWidth HeightHeight AlignmentAlignment TextText 50 30 center Submit 图 14-2 提交按钮图 14-2 提交按钮 可以将属性理解为上面表格中的内容,在组件设计器中,用它们来定义组件的初始外观。如果将Width属 性从50改为70,那么无论是在设计器中,还是在实际应用中,按钮看起来都会变宽。注意,最终用户不会 看到70这个数字,他们只能看到按钮变宽。 行为行为 一般来说,人们很容易了解组件的用途:文本框(Textbox)用于输入信息,按钮(Button)用来点击等 等。但是对于应用中的行为,则往往是抽象的和复杂的。行为定义了应用对事件的响应,无论是用户发起 的事件(如点击按钮),还是外部事件(如香港老钱庄868525,(六合娃娃收到短信)。定义这些交互行为的难度恰恰是编程的挑战 性所在。 幸运的是,App Inventor提供了一种非常适合于定义行为的可视化“块”语言,本节为理解块语言提供了 一种模型。 应用如菜谱应用如菜谱 人们习惯于把软件与菜谱相对比。像菜谱一样,传统的应用由一系列的顺序排列的指令构成,如图14-3所 示,而计算机(厨师)则按顺序执行这些指令。 App Inventor 编程实例及指南 - 215 -本文档使用 看云 构建 图 14-3 传统的软件由一系列顺序执行的指令构成图 14-3 传统的软件由一系列顺序执行的指令构成 一项典型的银行业务可能按如下顺序执行: 1. 启动某项业务; 2. 执行某些计算并修改客户账目; 3. 在屏幕上显示新的余额信息。 应用就是一系列的事件处理程序应用就是一系列的事件处理程序 然而,如今的绝大多数应用,无论是香港老钱庄868525,(六合娃娃的,还是Web或桌面电脑的,都不再适合采用这种菜谱模式了。 应用不再是顺序地执行一系列的指令,相反,更为普遍的是对事件的响应,事件的触发者是最终用户。例 如,当用户点击按钮时,程序会做出响应,执行某些操作(如发送短信)。对于使用触屏的香港老钱庄868525,(六合娃娃或设备, 当你的手指在屏幕上拖动时,将触发另一类事件,在应用中可以利用这类事件,在手指最初的接触点与最 终的抬起点之间画一条线,作为对该事件的响应。 这种类型的应用更适合于概括为“对事件做出响应的组件的集合”。这类应用中依然包含了“菜谱”—— 一些顺序执行的指令,但每个菜谱只限于对某些特定事件做出响应,如图14-4所示的“菜谱”。 图 14-4 拥有多个“事件菜谱”的应用图 14-4 拥有多个“事件菜谱”的应用 因此,当事件发生时,应用通过调用一系列的函数进行回应。这里的函数是指①利用某些组件来完成某些 操作,如发送短信;②对某些组件属性进行操作,如在用户界面上修改label的text属性。调用函数意味着 让函数运行,让它产生作用。我们把事件,连同对事件进行响应的一系列函数统称为事件处理程序(Event App Inventor 编程实例及指南 - 216 -本文档使用 看云 构建 Handler)。 许多事件由最终用户触发,但还有些不是。应用可以对香港老钱庄868525,(六合娃娃内部的事件进行响应,如方向传感器的变化以 及时钟的行走(即时间的流逝),也可以对香港老钱庄868525,(六合娃娃以外的事件做出响应,如来自于其他香港老钱庄868525,(六合娃娃的事件,或收到 来自web的数据,等等。如图14-5所示。 图 14-5 应用可以兼顾对内部及外部事件的响应图 14-5 应用可以兼顾对内部及外部事件的响应 之所以称App Inventor编程为“直观”编程,是因为这种编程完全基于一种事件响应模式,而“事件处理 程序”则是该语言中最重要的词汇(在其他语言中情况未必如此)。想要定义某个行为,首先要拖出一个 事件块,事件块在形式上是这样的:“When do”。假设有这样一个“朗读”应用,当用户点击按钮时, 应用大声读出用户输入的文字,这个应用只需要一个事件处理程序,如图14-6所示。 图 14-6 “朗读”应用中的事件处理程序图 14-6 “朗读”应用中的事件处理程序 这些块的作用是,当用户点击“SpeakItButton”按钮时,TextToSpeech组件将朗读用户在输入框 TextBox1中输入的文字。在这里,事件是SpeakItButton.Click,对事件的响应是调用 TextToSpeech1.Speak函数,事件处理程序中包括了图14-6中的所有块。 在App Inventor中,所有活动都发生在对事件的响应之中,应用中不可能存在事件块“when-do”之外的 App Inventor 编程实例及指南 - 217 -本文档使用 看云 构建 块,如图14-7这样的单摆浮搁的块是毫无意义的。 图 14-7 事件处理程序之外的散在的块毫无用处图 14-7 事件处理程序之外的散在的块毫无用处 事件类型事件类型 可以引发活动的事件被分类列在表14-2中。 表14-2 能够引发活动的事件表14-2 能够引发活动的事件 事件类型事件类型 举例举例 用户发起的事件 当用户点击Button1时,执行... 初始化事件 当应用启动时,执行... 计时器事件 当20毫秒过去时,执行... 动画事件 当两个物体碰撞时,执行... 外部事件 当今晚六彩现场开奖结果收到短信时,执行... 用户引发的事件用户引发的事件 用户引发的事件是一种最常见的事件类型,在输入表单中,通常点击按钮事件会引发应用的响应。图形化 的应用更多的是对触摸及拖拽事件做出响应。 初始化事件初始化事件 有时需要在应用启动时实现某些功能,这既不同于响应最终用户引发的事件,也不是对其他类型事件的响 应,那么如何让这种情况也适合于事件处理模式呢? 在App Inventor这种基于事件处理的语言中,应用的启动也被视为一种事件。如果你想在应用打开的同时 实现某些功能,可以拖出Screen1.Initialize事件块,并将某些函数调用块放在其中。 例如,在第三章打地鼠游戏中,在应用启动的同时,通过调用MoveMole过程,将地鼠放在一个随机的位 置,如图14-8所示。 App Inventor 编程实例及指南 - 218 -本文档使用 看云 构建 图 14-8 应用启动时,使用Screen1.Initialize事件块来放置地鼠图 14-8 应用启动时,使用Screen1.Initialize事件块来放置地鼠 计时器事件计时器事件 应用中的某些活动是由时间的流逝而触发的,比如动画,可以理解为计时器事件触发了角色的移动。App Inventor有一个Clock组件,用于触发计时器事件。例如,如果想让一个球在一定时间间隔内,在屏幕上水 平移动10个像素,就可以像图14-9那样来设置块。 图 14-9 一旦Clock1.Timer开始运行(计时),使用计时器事件块来移动球图 14-9 一旦Clock1.Timer开始运行(计时),使用计时器事件块来移动球 动画事件动画事件 在canvas范围内的图形对象(sprites精灵),它们的活动将触发动画事件,具体地说,当两个sprites发生 碰撞,或一个sprites到达canvas的边界时,将触发动画事件。因此可以编写游戏或其他交互式动画程序, 利用动画事件来定义游戏或动画的情节。更多信息请参见第17章。 外部事件外部事件 当香港老钱庄868525,(六合娃娃从接收到来自GPS卫星的位置信息时,将触发一个外部事件;同样,当香港老钱庄868525,(六合娃娃收到短信时,也会触发 此类事件(图14-10)。 App Inventor 编程实例及指南 - 219 -本文档使用 看云 构建 图 14-10 当香港老钱庄868525,(六合娃娃收到短信时,触发Texting1.MessageReceived事件图 14-10 当香港老钱庄868525,(六合娃娃收到短信时,触发Texting1.MessageReceived事件 这类向设备输入外来信息的行为都被视为外部事件,用户点击按钮也属于此类事件。 因此你所创建的应用,从本质上讲是一系列的事件处理程序:一个是对应用的初始化,有些是响应最终用 户的输入,有些由事件触发,有些则有外部事件触发。你的任务是以事件处理的方式构思应用,然后设计 对每个事件的响应方式。 事件处理程序可以提问事件处理程序可以提问 对事件的响应不总是单线条的菜谱,程序可以提问,也可以重复某些操作。“提问”意味着就应用中的数 据进行提问,并根据答案决定下一步的行进方向(分支)。我们把应用中的提问称为“条件分支”,如图 14-11所示。 图 14-11 事件处理程序可以根据对条件问句的回答来执行不同的分支图 14-11 事件处理程序可以根据对条件问句的回答来执行不同的分支 测试条件会如此设计:“分数到达100了吗?”或者“我刚收到的短信来自小明吗?” 测试也可以是的更为复杂的规则,包含多种关系操作符(小于、大于、等于)及逻辑运算符(and、or、 not)。 App Inventor 编程实例及指南 - 220 -本文档使用 看云 构建 在App Inventor中可以使用if块、ifelse块来设定条件行为,例如,在图14-12中,当玩家的分数为100点 时,程序将显示“你赢了!”。 图 14-12 一旦玩家成绩达到100点,用if块来报告获胜图 14-12 一旦玩家成绩达到100点,用if块来报告获胜 本书第18章中将详细讨论条件块。 事件处理程序可以重复执行某些块事件处理程序可以重复执行某些块 应用不但可以提问并依据答案执行不同分支,还可以多次重复执行某些操作。App Inventor提供了两种用 于重复执行的块:foreach及while do。两种块中都会包含其它块。对于列表中的每一项,都会执行一次 foreach块中的所有块,例如,如果你想给今晚六彩现场开奖结果号码列表中的每个人都发送同一条短信,你可以利用图14- 13中的块来实现。 图 14-13 foreach块中的块将对列表中的每一项都执行一次操作图 14-13 foreach块中的块将对列表中的每一项都执行一次操作 foreach块中的块会重复多次,例如,3次,因为PhoneNumbers列表中只有三项。因此“想你...”这样的 短信将发往这三个号码。本书第20章将详细讨论重复块。 事件处理程序可以实现存储功能事件处理程序可以实现存储功能 在事件处理程序运行过程中,通常会记录某些信息,有些信息可以保存在内存中,被称作变量。变量在块 编辑器中定义,与组件的属性类似,但与任何组件无关。例如,在游戏类应用中,可以定义一个叫 做“score(分数)”的变量,当用户执行了某些操作时,事件处理程序会相应地修改score的值。变量用 于在应用运行过程中临时保存数据,一旦退出应用,数据将不复存在。 有时不仅需要在运行中保存某些数据,而且要求当退出又重新打开应用时,数据依然存在。例如,你想保 存一个游戏的历史最高得分,就需要长期保存数据,以便下次有人再玩游戏时,可以看到这个分数。在应 用关闭后依然保存下来的数据成为永久性数据,这些数据被保存在某种类型的数据库中。 App Inventor 编程实例及指南 - 221 -本文档使用 看云 构建 在本书的第15章及第22章,我们将分别讨论临时存储(变量)及永久存储(数据库)的使用问题。 事件处理程序可以与Web对话事件处理程序可以与Web对话 有些应用只能使用今晚六彩现场开奖结果或设备的内部信息,但有些则能通过向web service API发送请求的方式与网络进行 通信,这类应用被称为“网络应用”。 Twitter是一个很好的例子,它提供web service供App Inventor的应用访问。你可以编写一个应用,来请 求并显示朋友们刚刚发布的“推文”,也可以随时更新自己的Twitter状态。能够与多个web service进行 对话的应用被称为聚合类应用,我们将在第24章进行探讨。 小结小结 应用开发者必须以两种视角来观察一个应用,一个是最终用户的视角,另一个则是自内向外的程序员的视 角。利用App Inventor来开发应用,首先要设计应用的外观,然后设计应用的行为——一套事件处理程 序,让应用按照你的意图去运行。首先,通过在事件处理程序中拼装配置某些块,来实现对事件的响应, 这些块可能是函数、条件分支、循环操作、web调用、数据库操作,等等;然后在香港老钱庄868525,(六合娃娃中运行应用,来测 试你的程序。当你编写了若干个程序之后,应用的内部结构与它的物理外观之间的关系将逐渐清晰,此 时,你将成了一名真正的程序员。 App Inventor 编程实例及指南 - 222 -本文档使用 看云 构建 第 15 章 软件工程与应用调试 前面几章中讲过的Hello猫咪、打地鼠以及其他应用都是些非常小的软件项目,并不需要用引入软件工程的 概念。工程的概念借用自其他行业,意为设计并建造,教程中的应用就像是用预制件拼装起来的房屋模 型,而软件工程才是设计并建造真正用来居住的房子。这个例子虽然稍显夸张,但一般来讲,某些极其复 杂的建造过程,的确需要大量的前期构思、规划以及技术分析,这些过程都可以归结为工程。 但凡接手过一个相对复杂的项目,你就会理解,只要在功能上稍稍增加一点复杂度,软件工程的复杂程度 就会急剧增加,两者之间绝对不是线性的关系。对于我们大多数人来说,在真正开始面对这样残酷的现实 之前,我们很少能够意识到将要面临的困顿。从这个意义上讲,你要准备学习更多的软件工程的原则及调 试技巧。如果你已经认可这一点,或者,你是为数不多的、希望通过掌握一些技术来克服成长障碍的人, 那么本章就是为你准备的。 软件工程原则软件工程原则 以下是本章所涵盖的一些基本原则: 未来的软件使用者应该尽早,并尽可能多地参与到软件的设计及开发过程中来; 建立一个初始的、简单的原型,并逐步完善; 编码与测试同步进行,不要一次测试太多的代码(App inventor中的块); 开始编码前进行逻辑设计:对功能做纵向切割,对技术或实施的复杂度做分层切割,并各个击破; 对代码块进行注释,以便其他人(和你自己)能理解这些程序; 学会用纸笔来跟踪记录块的执行过程,以便于理解它们的工作机制。 如果能够遵循上述原则,你就可以节省时间,避免挫折,从而制作出优秀的软件。但你很有可能做不到每 次都依原则行事!有些原则看似违背常理。一种自然的倾向是,首先有了一个想法,并假设你了解用户的 需求,然后开始把若干个块拼在一起,直到完成了想象中的任务。现在,让我们回到软件工程的第一个原 则,在正式开始动手之前,看看如何了解用户的需求。 App Inventor 编程实例及指南 - 223 -本文档使用 看云 构建 设计要面对真实的人、现实的问题设计要面对真实的人、现实的问题 电影《梦幻成真》(Field of Dreams)中的男主角Ray听到了一个声音向他低语:“如果你建好了,他们 就会来。”Ray听从了这个声音,在爱荷华州的农场中间建了一个棒球场,果然,在1919年,芝加哥白袜 队(White Sox)和成千上万个球迷出现在这里。 不过你现在必须明白,那个低语的建议绝对不可以用于软件开发。事实上,必须正相反。在软件开发的历 史中,充斥着各类“没问题”的伟大方案(如:“让我们写个软件,告诉人们开车到月球需要多长时 间!”)。一个优秀的(同时也是极有可能获利的)软件的真正目的是解决现实中的问题。想知道问题出 在哪里,就要找到有问题的人,并与他们交谈,这就是通常被称作“以用户为中心”的设计方法,这个方 法同样也可以帮助你做出更好的应用。 如果遇到程序员,你可以问他们,在他们所写的软件中,有多少被真正交付到了最终用户的手中。结果会 让你感到惊讶:即使是对那些伟大的程序员来说,这个比例也还是太小了!许多软件项目驶入了问题的泥 沼而终无见天之日。 以用户为中心的设计理念意味着尽早并尽可能多地替未来的使用者着想,并与他们交流,这种思考与交流 甚至应该在尚未确定目标之前就开始。大多数成功的软件都是针对某个具体的人,试图解决他的特定问 题,也只有这样,最终才能发展成一个伟大的产品。 快速地创建软件原型,并展示给未来的使用者看快速地创建软件原型,并展示给未来的使用者看 如果让最终用户阅读软件功能的说明文档,他们多半不会给出任何有效的回应,他们不会对文档做出反 馈。真正有效的方法是,让他们体验未来软件的交互模式,即软件的原型。原型是一个不完整的、未经重 构的软件版本,创建原型的目的在于充分体现软件所具有的核心价值,而不必注重细节、完整性或漂亮的 用户界面。拿出原型让未来的使用者看,然后安静地倾听他们的反馈。 迭代式开发迭代式开发 在首次明确了软件的具体规格之后,采用迭代式开发。你可能很自然地倾向于将所有组件和块一股脑地添 加到应用中,然后下载到香港老钱庄868525,(六合娃娃上看看它是否好用。举例来说,“答题”应用,在缺乏指导的情况下,多数 初学者会一次性添加所有的块:带有一长串问题及答案的块、浏览问题的块、检查用户答案的块,以及与 每个逻辑细节有关的块,所有的块未经测试就全部罗列在应用中,这种开发方式在软件工程中被称为“大 爆炸”方式。 几乎所有的初学者都会采用这种方式。在旧金山大学(USF)的课堂上,当学生忙于创建应用时,我经常 会问他一个问题:“进展如何?” “我想我做完了,”他说。 “好极了,能让我看看吗?” “哦,还不行,我没带香港老钱庄868525,(六合娃娃来。” “那么你还从来没有运行过这个程序,对吗?”我问。 App Inventor 编程实例及指南 - 224 -本文档使用 看云 构建 “嗯...” 我透过他的肩膀看到了30个左右色彩缤纷的块,但他居然连一个功能都没有测试过。 程序员们很容易着迷,他们沉湎于创建UI(用户界面)并在块编辑器中创建所需的行为。那些块天衣无缝地结 合在一起,优雅地排布在屏幕上,这些让他们感到倾心,却忘记了创建一个让其他人也能使用的、完整 的、通过测试的应用。这听起来像是洗发水的广告,但对于我的学生和那些有志成为程序员的人,这是我 能给出的最好的建议:代码要随写随测,周而复始。代码要随写随测,周而复始。 每次只写少量代码,并随时测试,这个过程本身会变成一种习惯,如此这般,在不久的将来,你会收获令 人惊异且满意的成果(而且几乎杜绝了大而难缠的程序漏洞——bug)。 先设计,后编码先设计,后编码 编程要分两步走:①理解应用的逻辑,②将这些逻辑翻译成某种形式的编程语言。在开始翻译之前,要在 逻辑上花一些功夫:首先要明确应用中将会发生哪些事情,无论是用户引发的,还是应用内部的;其次在 正式开始将逻辑翻译成代码块之前,要明确每个事件处理程序中的逻辑。 有许多专门讨论各种程序设计方法的书籍。有些人喜欢用流程图或结构图来做设计,有些则更愿意将设计 或草图写在纸上,更有人认为所有的“设计”最终应该体现为代码的注释,而不是一个与代码分离的文 档。对于初学者来说,关键是要理解所有的程序在本质上都是一套逻辑,而这种逻辑与具体的编程语言无 关。当然,思考应用的逻辑和翻译为编程语言这两件事有时难免会同步进行,无论这种编程语言是否直 观。因此,在整个逻辑思考阶段,应该远离电脑,想清楚应用最终要实现哪些功能,并以某种方式随时记 录下你的想法,然后让设计文档与应用保持关联,以便其他人也可以从中获益。下面我们就来实践这一过 程。 对代码进行注释对代码进行注释 你已经学过了本书中的教程部分,应该见过块所附带的黄色方框(见图15-1),这就是“注释”。在App Inventor中,任何的块都可以添加注释,方法是在块上单击鼠标右键,并在快捷菜单中选择AddAdd CommentComment。注释丝毫不影响程序的运行。 图 15-1 为测试条件块添加注释——用简洁的语言描述块的作用图 15-1 为测试条件块添加注释——用简洁的语言描述块的作用 那么为什么要做注释呢?想想看,如果你的应用很成功,它的生命周期会很长,即便只是搁下一周的时 间,你都有可能忘记当时的想法,想不起来这些块有什么用处。因此,尽管没有别人会看到你的代码块, App Inventor 编程实例及指南 - 225 -本文档使用 看云 构建 你也应给添加这些注释。 假如你的应用很成功,毫无疑问它会传到很多人手里,人们想了解它、按自己的需要修改它,或者扩展它 的功能,等等。在开源的世界里,很多项目会以现有项目为基础,做进一步的修改和完善,只要你亲身体 验过那些没有代码注释的项目,你就会彻底明白为什么注释是必需的。 为程序添加注释并不是一种自觉地行为,我也从未见到过初学者重视它,然而,我也从未见到过一个经验 丰富的程序员不重视它。 切割、分层、各个击破切割、分层、各个击破 当问题的规模大到难以应对时,解决之道在于将问题分解,分解的方法有两种:第一种方法我们非常熟 悉,即,将问题分解为若干个部分(如A、B、C),然后各个击破;第二种则不太常见:将问题按照从简 单到复杂的顺序逐层分解。对应到App Inventor的编程方法上,就是先添加少量的块来实现简单的功能, 并测试其效果,再逐渐过渡到复杂的功能,以此类推。 让我们以第10章的“出题”应用为例来具体阐述这两种方法。在应用中,用户可以点击“下一题”按钮对 问题进行浏览,也可以检查用户的答案是否正确。从设计角度,可以将应用分解为两个部分:问题浏览及 答案核对,并针对两个部分单独编程。 但在每个部分中,还可以对整个过程按照从简单到复杂的顺序进行分解。例如,问题浏览环节,先创建代 码来显示问题列表中的第一题,并测试其是否有效;然后编写代码来浏览到下一题,暂时不考虑到达最后 一题时可能引起的错误;当测试结果证明可以从头至尾浏览所有问题时,再添加块来处理用户浏览到最后 一题的“特殊情况”。 究竟是将问题分解为几部分,还是按照复杂性分解为若干层,这不是一个非此即彼的问题,但却是一个值 得思考的问题,关键在于哪种方法更适合于你所创建的应用。 理解编程语言:用纸和笔跟踪记录理解编程语言:用纸和笔跟踪记录 应用在运行过程中,仅有部分可见。最终用户只能看到它的外观——用户界面上显示的图形及数据,而软 件的内部运作机制对外部世界来说是不可见的,就像人类大脑的内部机制一样(谢天谢地!)。应用在运 行时,我们既看不到这些指令(块),也看不到跟踪当前正在执行的指令的程序计数器,更无法看到软件 的内部存储单元(应用中的属性及变量)。不过说到底,这正是我们想要的:最终用户只能看到程序需要 被显示的部分,但对于开发者来说,在开发及测试过程中,你需要了解所有正在发生的事情。 作为一个开发者,在开发过程中所看到的代码,都只是些静态视图,因此必须靠想象力来驱动软件的运 行:事件发生了,程序计数器移动到下一个块,并执行这个块,内存单元中的值发生了变化,等等。 编程过程中需要在两种不同的场景之间切换:先从静态模式——代码块开始,并试着想象程序的实际运行 效果;一切就绪后,切换到测试模式——以最终用户的身份测试软件,看它的运行结果是否与预期的结果 相一致。如果不是,必须再切换回静态模式,调整程序,然后再试。如此循环反复,最终获得一个满意的 结果。 App Inventor 编程实例及指南 - 226 -本文档使用 看云 构建 初学者对于计算机程序的运作方式知之甚少,整个过程看起来就像魔术。依照本教程的指导,学习应该从 简单的应用开始(如,点击按钮导致猫叫),再逐渐过渡到较为复杂的应用,而且随着学习的不断深入, 或许还可以根据自己的需要,对教程中的例子做出修改。从初学者到入门者,对程序的内部运作机制有了 一些了解,但依然感到对整个过程无法控制。他们经常会说:“这个不起作用,”或者“它不应该是这样 的。”关键是要理解程序如何实现那些你主管想象出来的功能,而且要说:“我的程序正在做这件事”, 以及“我的逻辑导致了程序的...”。 了解程序运行机制的方法就是剖析一个简单应用的执行过程,在纸上精确地描绘出每个块在执行时,设备 的内部发生了什么。想象用户触发了某个事件处理程序,然后逐步跟踪并记录块的执行效果:应用中的变 量及属性如何改变,用户界面上的组件如何改变。就像文学课上的“精读”环节,这样一步一步的跟踪可 以促使你检查语言中的各个要素(即App Inventor中的块)。 对复杂性的描述几乎是完全抽象的,重要的是你要放慢思路,理清各个块之间的因果关系。最终你会明 白,这些过程控制的规则,并不像最初想象的那样难以理解。 以第8章总统测验为例,如图15-2所示,思考图中的这些块(对原教程做了一点修改)。 图 15-2 应用启动时,将QuestionLabel的Text属性设置为QuestionList列表的第一项图 15-2 应用启动时,将QuestionLabel的Text属性设置为QuestionList列表的第一项 你能理解这些代码吗?你能跟踪这些代码,并说明每一步都发生了什么吗? 首先跟踪所有相关的变量及属性。画出存储单元的表格,这个例子中,表头分别为currentQuestionIndex 和QuestionLabel.Text,如表15-1。 表15-1 记录text属性及index值变化的表格表15-1 记录text属性及index值变化的表格 QuestionLabel.TextQuestionLabel.Text currentQuestionIndexcurrentQuestionIndex 接下来,思考当应用启动时,发生了哪些事——不要以用户的视角来看,而是从应用的内部来分析它的初 始化过程。如果你学过这些教程,你可能知道这个过程,但你可能没有从机制方面去思考过。当应用启动 时: 1. 完成了所有组件的属性设定,它们的值等于在组件设计器中设定的初始值; App Inventor 编程实例及指南 - 227 -本文档使用 看云 构建 2. 完成了所有变量的定义及初始化; 3. 执行了Screen.Initialize事件处理程序中的所有块。 对程序进行跟踪有助于理解程序的运行机制,那么在完成了应用的初始化之后,表格中应该填写什么内容 呢? 如表15-2所示,currentQuestionIndex的值为1,因为应用启动时完成了变量的定义,并将其初始值设为 1;而QuestionLabel.Text的值为第一题,因为在Screen.Initialize中选择了QuestionList列表中的第一 项,并放入了QuestionLabel中。 表15-2 总统测验应用初始化后,QuestionLabel.Text与currentQuestionIndex的值表15-2 总统测验应用初始化后,QuestionLabel.Text与currentQuestionIndex的值 QuestionLabel.TextQuestionLabel.Text currentQuestionIndexcurrentQuestionIndex 哪位总统在大萧条时期实施了“新政”? 1 下面再来跟踪用户点击“下一题”按钮时发生的事情。 图 15-3 用户点击“下一题”按钮时执行的块图 15-3 用户点击“下一题”按钮时执行的块 逐个检查每个块。首先是变量currentQuestionIndex的递增,说得更具体一些,变量当前值是1,经过+1 的运算后,将结果2再赋给变量currentQuestionIndex。接下来看if语句,列表QuestionList的长度为3, 显然currentQuestionIndex的值2小于3,因此if语句的结果是false(假),于是列表中的第2项(第二 题)被写入QuestionLabel.Text中,如表15-3所示。 表15-3 点击“下一题”按钮后的变量及属性值表15-3 点击“下一题”按钮后的变量及属性值 QuestionLabel.TextQuestionLabel.Text currentQuestionIndexcurrentQuestionIndex 哪位总统在1979年实现中美建交? 2 跟踪“下一题”按钮的第二次点击。现在currentQuestionIndex已经递增到3,会发生什么呢?继续阅读 之前,细心地检查一下,看你能否跟踪正确。 在if测试中,currentQuestionIndex的值(3)的确≥列表QuestionList的长度(3),于是 currentQuestionIndex的值被设为1,第一题被写入label,如表15-4所示。 App Inventor 编程实例及指南 - 228 -本文档使用 看云 构建 表15-4 “下一题”按钮被第二次点击时的值表15-4 “下一题”按钮被第二次点击时的值 QuestionLabel.TextQuestionLabel.Text currentQuestionIndexcurrentQuestionIndex 哪位总统在大萧条时期实施了“新政”? 2 我们的跟踪揭露了一个错误:最后一题永远也无法显示! 通过类似的跟踪,最终使你成为一名程序员、工程师。你开始从机制上去理解编程语言,掌握代码中的语 句和词汇,而不是对一些片段的模糊理解。诚然,编程语言是复杂的,但机器对每个“词”都有明确而且 简单的解释,如果理解了块与变量或属性变化之间的对应关系,也就理解了如何编写或修复你的应用,当 然也就实现了对应用的完全控制。 现在如果你告诉朋友们,“我正在学习如何让用户点击‘下一题’按钮来看到下一道题,这实在是太难 了!”他们会以为你疯了。但这个过程的确很困难,困难不在于概念的复杂性,而在于你不得不有意让自 己的脑子慢下来,来搞清楚计算机的每一步处理过程,包括那些你的大脑下意识完成的过程。 应用的调试应用的调试 逐步跟踪不仅是理解编程的方法,同样在调试有问题的应用时,也是一个屡试不爽的方法。 像App Inventor这样的开发工具(通常被成为交互式开发环境,或IDEs-Interactive Development Environments)一般会提供了一种调试工具,相当于纸笔跟踪记录的高科技版本,能够自动完成某些跟踪 过程,这极大地改善了应用开发的进程。这些工具提供了一个描述正在运行的应用的视图,程序员可以在 其中: 在任何一点暂停应用来检查其中的各个变量及属性; 单独执行某些指令(块)来检查它们的执行效果。 监视变量监视变量 说明:监视变量是AI1(App Inventor version1.0)中的功能,目前尚未在AI2中实现。 单独测试块单独测试块 除了可以用监视功能来检查应用运行过程中变量及属性的变化,还有另一个工具“Do It”,可以让你脱离 开程序通常的运行顺序,单独测试某些块的运行。右键点击一个块,在快捷菜单中选择“Do It”,这个块 就会开始执行,如果这个块是一个有返回值的表达式,App Inventor将在块的上方的方框内(在注释块中 插入两行)显示返回值。如图15-4及15-5。 App Inventor 编程实例及指南 - 229 -本文档使用 看云 构建 图 15-4 右键点击事件处理程序中的任何一个块,会弹出快捷菜单图 15-4 右键点击事件处理程序中的任何一个块,会弹出快捷菜单 图 15-5 在快捷菜单中选择“Do It”,可以执行该块,并查看返回值(如果有)图 15-5 在快捷菜单中选择“Do It”,可以执行该块,并查看返回值(如果有) “Do It”在调试块的逻辑错误时非常有用。还是回到“总统测试”例子中的NextButton.Click事件处理程 序,并假设程序中存在逻辑错误,无法浏览所有的问题。调试过程需要在开发环境及测试设备上同时进 行。在用户界面上点击“下一题”按钮,然后回到块编辑器查看是否每次点击都显示了适当的问题。也可 以监视变量index在每次点击时的变化。 但是这类测试只允许检查整个事件处理程序的执行效果,在运行完所有的块之前,你无法检查你要监视的 变量或用户界面。(抓不到逐句的中间状态) App Inventor 编程实例及指南 - 230 -本文档使用 看云 构建 “Do It”允许你减缓测试过程,并检查任何一个块执行完成后的整个应用的状态。一般是从用户界面上的 事件开始跟踪,直到发现问题所在。在发现无法显示最后一题之后,你可能在用户界面上点击“下一 题”一次转到第二题,然后不再继续点击“下一题”,而是在块编辑器中让整个事件处理程序一步一步地 运行。在NextButton.Click事件处理程序中,每次对一个块使用“Do It”让块执行,如图15-6中,先右键 点击第一行的块(让变量index递增),并选择“Do It”。 图 15-6 使用“DoIt”工具,每次只执行一个块图 15-6 使用“DoIt”工具,每次只执行一个块 此时index的值变为3,应用停止执行——“Do It”只能使被选中的块以及它所包含的子块运行,这可以 让测试者检查被监视变量以及用户界面的变化。接下来,选择下一行要测试的块(if测试)并选择“Do It”来执行该行,其中的每一步都能看到每个块的执行效果。 使用“Do It”渐进式开发使用“Do It”渐进式开发 有一点需要强调,这种逐行执行指令的方式不仅仅适用于程序的调试,它同样适用于开发过程中的随时测 试。例如,如果你写了一个很长的公式来计算两个GPS坐标之间的距离,你可能要分步测试这个公式,来 验证这些块的使用是否正确。 启用与禁用块启用与禁用块 另一个有助于渐进式调试应用的方法是启用或禁用某些块,它允许应用中保留有问题的或未经测试的块, 并让系统在运行过程中暂时忽略它们,然后充分调试那些启用状态的块,而不必担心那些有问题的部分。 禁用块很简单,在块上点右键,在快捷菜单中选择Disable Block即可,被禁用的块呈现为灰色,在应用运 行时,这些块被忽略;需要时,还可以重新启用这些块,方法是在块上点击右键并选择Enable Block。 小结小结 App Inventor的伟大之处在于它的易用性——可视化的特点让你可以直接开始一个应用,而不必担心那些 低层的细节。但现实的问题是,App Inventor不可能知道你的应用要做什么,更不知道如何来做。尽管直 接进入组件设计器与块编辑器创建应用是件让人着迷的事情,但这里要强调的是,花一些时间来思考并详 细、准确地设计应用的功能,是非常重要的。这听起来有些烦,但如果你能听取用户的想法、创建原型、 测试并跟踪应用的逻辑,那么创建出精彩应用的目标指日可待。 App Inventor 编程实例及指南 - 231 -本文档使用 看云 构建 App Inventor 编程实例及指南 - 232 -本文档使用 看云 构建 第 16 章 应用中的存储 就像人类需要记忆一样,应用需要存储。本章将探究如何在应用中实现信息的存储。 如果刚刚有人在今晚六彩现场开奖结果里告诉你一家披萨店的今晚六彩现场开奖结果号码,你的大脑中会留下一段记忆;这时,如果有人大声 告诉你一串数字,并要你记住,你也会将它们保存到记忆中。在这种情况下,你未必能清楚地意识到,你 的大脑是在保存或调用信息。 应用同样具备记忆功能,但它的内在机制并不像大脑那样神秘。本章你将学习如何设置应用的存储功能, 如何利用它来保存信息,以及之后如何提取这些信息。 有名称的存储槽有名称的存储槽 应用的存储功能由一组有名称的存储槽(memory slots)组成。一旦组件被拖到应用中,就会自动创建了 一组被称为“属性”的存储槽;也可以定义与特定组件无关的、有名称的存储槽,即变量。如果说属性通 常与应用的外观呈现有关,那么变量则被认为是应用中不可见的“暂时”记忆。 属性属性 在应用中,组件,或者说像Button、TextBox以及Canvas这类可视组件,构成了完整的用户界面。而组件本 身的外观则是又一组属性来确定,属性值就保存在存储槽中。 在组件设计器中,可以直接对属性的存储槽进行修改,如图16-1所示。 App Inventor 编程实例及指南 - 233 -本文档使用 看云 构建 图 16-1 在属性栏中修改存储槽来改变应用的外观图 16-1 在属性栏中修改存储槽来改变应用的外观 图16-1中的Canvas组件具有六个属性:BackgroundColor及PaintColor是保存颜色的存储 槽,BackgroundImage保存了文件名(kitty.png),Visible属性保存了一个布尔值(true或false,依赖 于是否勾选了方框),而Width及Height属性保存了一个数字或某个特定的设置(如,“Fill parent”)。 在组件设计器中设置组件的属性,相当于设置应用启动时的外观。应用的最终用户从未见过应用中有一个 名字为Height、值为300的存储槽,他们只能看见用户界面上有一个300像素高的组件。 定义变量定义变量 像属性一样,变量也是被命名的存储槽,只是与特定的组件无关。在应用中,需要记住某个状态,如果无 法用组件的属性来保存它,就需要定义一个变量来保存它。例如,一个游戏类的应用可能需要记住玩家到 达的等级。如果等级数用Label组件来显示,就不需要定义变量,因为Label组件的Text属性可以用来保存 这个等级。但是,如果等级数不需要显示给用户,就应该定义一个变量来保存它。 另一个使用变量的例子是第8章总统测验。在这个应用中,用户界面上一次只能显示一道测验题,而其他问 App Inventor 编程实例及指南 - 234 -本文档使用 看云 构建 题用户是看不见的,因此,就需要定义一个问题列表的变量来保存它们。 在组件设计器中拖入一个组件,它的属性就自动创建完成了,相比之下,变量的定义需要在块编辑器中直 接拖出一个变量初始化块变量初始化块(initialize global name to),点击块中的“name”为变量命名,并为变量设 置初始值,方法是拖出一个块放入变量初始化块中,可以是number块、text块、color块或者是make a list块。跟随下面的步骤就可以创建一个叫做score的初始值为0的变量。 1. 从块编辑器的Built-in分组中找到Variables,点击打开抽屉并拖出“initialize global name to”块,如 图16-2所示。 图 16-2 拖出变量初始化块图 16-2 拖出变量初始化块 2. 为变量命名:点击变量初始化块中的“name”,并输入“score”,如图16-3所示。 图 16-3 为变量命名图 16-3 为变量命名 3. 为变量设置初始值:从Math抽屉中拖出数字块,将其插入变量初始化块的插槽中,如图16-4所示。 图 16-4 为变量设初始值图 16-4 为变量设初始值 4. 将变量初始值由默认值(0)改为123,如图16-5所示。 图 16-5 修改变量的初始值图 16-5 修改变量的初始值 定义一个变量,就是通知应用建立一个有名称的存储槽,来保存某个值。像属性一样,这些存储槽用户是 看不见的。 变量的初始值在应用启动时就已经被放入存储槽中。可以用数字或文本对变量进行初始化,除此之外,也 可以插入一个“make a list”块,它告诉应用这个变量是一个存储槽的列表,而不是一个单独的值。关于 list的更多内容请参考第19章。 设置及读取变量设置及读取变量 变量定义之后,App Inventor会生成两个属于这个变量的块:set块及get块,只要将鼠标悬停在变量初始 化块中的变量名称之上,就可以呼出到这两个块。如图16-6所示。 App Inventor 编程实例及指南 - 235 -本文档使用 看云 构建 图 16-6 变量初始化块包含访问该变量的set块及get块图 16-6 变量初始化块包含访问该变量的set块及get块 其中的“set global score to”块可以用来修改(设置)变量的值,例如图16-7中,将数字块5放在变量 score的set块中。变量初始化块中的“global”一词意为“全局的”,指的是变量的适用范围,一个全局 变量可以被程序中所有事件处理程序及过程所引用。新版的App Inventor中还可以定义一种“local”变 量,这种变量可以在一个事件处理程序或某个过程的内部进行定义(这里暂不涉及)。 图 16-7 将数字5赋给变量score图 16-7 将数字5赋给变量score 另一个“get global score”块用于从变量中读取变量值。例如,如果你想检查score的值是否大于或等于 100,就可以将“get global score”块插入if块进行测试,如图16-8所示。 图 16-8 使用get global score块来获取变量值图 16-8 使用get global score块来获取变量值 用表达式为变量赋值用表达式为变量赋值 可以用单一的数字5来为变量赋值,不过通常会用一个更为复杂的表达式来为变量赋值(“表达式”是一个 计算机科学的术语,即公式)。例如,在总统测试的应用中,用户点击“下一题”按钮时,要让变量 currentQuestionIndex的值增加1,来显示下一道题;又如在游戏类应用中,如果玩家失败,还有可能将 他的成绩减10分;还有像第3章打地鼠的游戏中,通过改变变量x的值,实现地鼠在Canvas中水平位置的随 机移动。因此可以用若干个块组成的表达式插入“set global score”块为变量score赋值。 变量的递增变量的递增 一种最常见的表达式可能是变量的递增,或根据变量的当前值进行设定。例如,游戏中当玩家获胜一次, 变量score就将增加5,如16-9显示了实现这一行为需要的块。 图 16-9 分数变量递增5图 16-9 分数变量递增5 App Inventor 编程实例及指南 - 236 -本文档使用 看云 构建 如果能够理解这些块的含义,你就离程序员又近了一步。这些块可以理解为“让成绩在现有的值上加1”, 这是变量递增的另一种说法。要理解这些块的工作机制,需要按照从内向外、而不是从左到右的顺序,最 里面的块是“get global score”及数字“5”,它们是最基础的块,然后“+”块执行加法运算,并将结 果设定为变量score的值。 假设存储槽中score的当前值为5,经过这些块的运算,程序执行了以下步骤: 1. 从score的存储槽中读取当前值5; 2. 加上5得到结果10; 3. 将10放回到score的存储槽中(来替代5)。 关于变量递增的更多内容请参见第19章。 构造复杂的表达式构造复杂的表达式 在Math抽屉中(图16-10),App Inventor提供了许多数学函数,就像在电子表格或计算器中见到的一 样。 图 16-10 Math抽屉中的运算符及函数图 16-10 Math抽屉中的运算符及函数 App Inventor 编程实例及指南 - 237 -本文档使用 看云 构建 你可以使用这些块来构造复杂的表达式,并将它们作为赋值表达式插入到“set global to”块中。例如, 要想实现一个图片精灵(image sprite)在canvas范围内的随机水平移动,就需要使用一个乘法块(*)、 一个减法块(-)一个Canvas.Width属性以及一个随机小数函数来组织表达式,如图16-11所示。 图 16-11 使用数学(Math)块来构造上面的复杂表达式图 16-11 使用数学(Math)块来构造上面的复杂表达式 正如在前面变量递增的例子中所说,程序对这些块的解释是遵循从内而外的顺序。假设Canvas的Width属 性值为300,ImageSprite的Width为50,程序将执行以下步骤: 1. 分别从Canvas1.Width及ImageSprite.Width的存储槽中读取300及50; 2. 减法运算:300 - 50 = 250; 3. 调用随机小数函数获得一个1-1之间的随机数(比如说0.5); 4. 乘法运算:250 * 0.5 = 125; 5. 将125放在ImageSprite.x属性的存储槽中。 显示变量显示变量 在前面的例子中,修改一个组件的属性,将直接影响到用户界面的外观,而变量则不然,改变一个变量并 不会直接影响到应用的外观。如果你只是将变量score的值递增,而不设法修改用户界面的话,用户永远都 不知道变化的存在,就像俗话说的“树木落入森林”一般:如果没有人知道它,怎么证明它的存在呢? 有时,当变量变化时,不希望在用户界面上立即显示出来,例如,在游戏中,可能会记录某些统计结果 (如失败次数),只有游戏结束时才会显示其结果。 与组件的属性相比,这是使用变量来存储数据的优势:可以在需要的时间显示必要的数据,也可以使应用 中的计算与用户界面分离,这样做的结果是更易于稍后对用户界面的修改。 例如,在游戏中,可以将成绩直接保存在Label的Text属性中,也可以保存在变量中。如果保存在Label 中,得分时可以让Label的Text属性值递增,用户可以直接看到成绩的变化;如果成绩被保存到变量中,并 用变量的递增记录得分,则需要另外设置块,将变量值显示到Label中。 尽管使用变量保存并显示数据要多出一些步骤,但当你决定要修改应用,以不同的方式在用户界面上显示 成绩时,变量的方法让改变很容易实现。你不必对每个显示组件上的成绩进行修改,它们不需要修改,你 只需要修改那些与显示有关的块。 使用Label而非变量的方法,会让应用变得难于修改,因为,比如说要用一个递增的值来控制label的宽度 (Width),每一次递增都要执行一次对Width属性的修改。 App Inventor 编程实例及指南 - 238 -本文档使用 看云 构建 小结小结 应用启动之后,开始执行一系列的操作,并对发生的事件进行响应。在事件响应过程中,应用有时需要记 住一些东西,如,游戏中每个选手的成绩,或者某个对象的移动方向等。 应用可以用组件的属性来实现存储,但当你需要与组件无关的存储槽时,就需要定义变量。可以将值保存 到变量中,也可以从变量中读取当前值,就像使用组件的属性一样。 无论是属性值,还是变量值,对用户来说都是不可见的。如果你想让用户看到保存在变量中的信息,只要 添加块,就可以用Label或其他用户界面组件来显示这些信息。 App Inventor 编程实例及指南 - 239 -本文档使用 看云 构建 第 17 章 创建动画应用 本章将讨论创建另一类应用的方法,应用中使用了简单的可移动的动画对象。你将学习使用App Inventor 创建二维游戏的基本知识,并熟练使用图片精灵(image sprite)及处理两个物体碰撞一类的事件。 当你在电脑屏幕上看到一个平滑移动的物体时,你实际上看到的是一连串快速移动的图片,每次只移动一 个极小的距离,它利用了人的视觉暂留,从这一点上,它无异于“手翻书”—— 一种通过快速翻页来看到 动画效果的书(这也是那些精美绝伦的动画电影的制作方法)。 在App Inventor中,通过在Canvas组件上放置物体,并让这些物体随时间在Canvas内移动,从而产生出 动画效果。本章将学习使用Canvas的坐标系统,学习利用Clock.Timer事件来触发运动,以及如何控制运 动速度、如何响应两个物体的碰撞事件等等。 在应用中添加Canvas组件在应用中添加Canvas组件 从组件面板的Drawing and Animation组中拖出Canvas组件,然后定义它的Width及Height属性。通常 我们希望Canvas与屏幕等宽,为此将宽度设为“Fill parent”,如图17-1所示。 图 17-1 设置Canvas组件的Width属性图 17-1 设置Canvas组件的Width属性 App Inventor 编程实例及指南 - 240 -本文档使用 看云 构建 可以用同样的方式设定Height属性,但一般会将其设为一个数字(如300像素),以便为Canvas上面或下 面的其他组件留出空间。 Canvas的坐标系统Canvas的坐标系统 Canvas上的图画实际上是一个许多像素构成的表格,像素是香港老钱庄868525,(六合娃娃(或其他设备)屏幕上能够显示的最小的 色块,每个像素都在Canvas上有它的位置(或者说单元格),位置由X-Y坐标系定义,如图17-2所示,X 定义了水平方向上的位置(方向是从左到右),Y定义了垂直方向的位置(从上到下)。 图 17-2 Canvas的坐标系统图 17-2 Canvas的坐标系统 坐标轴的方向定义可能与你的经验不一致,不过位于Canvas左上角的单元格的x、y坐标都为零,因此这个 位置表示为(x=0,y=0)。(这与App Inventor列表中使用的索引值有所不同,索引值从1开始,看起来 更容易理解。)向右移动时,x坐标增大;向下移动时,y值变大。位于左上角单元格右侧的单元格坐标为 (x=1,y=0)。右上角单元格的x坐标等于canvas的宽度减1,多数香港老钱庄868525,(六合娃娃屏幕的宽度都在300左右,但这 里例子中显示的宽度是20,因此右上角的单元格坐标为(x=19,y=0)。 要改变canvas的外观有两种方法:①在上面绘画,或者②在上面放置移动的物体,本章所涉及的是后者, 但我们首先要讨论如何绘画,以及如何通过绘画来创建动画(这也是本书第二章油漆桶中的主要内容)。 Canvas中的每一个单元格都对应显示为一个有颜色的像素。Canvas组件提供的Canvas.DrawLine及 Canvas.Circle块可以用来在canvas上以绘制像素组成的图画。首先需要将Canvas.PaintColor属性设置为 你需要的颜色,然后调用某个具体的绘画块来画出颜色。其中的DrawCircle块可以绘制直径为任意大小的 圆,但如果你将半径设为1,如图17-3所示,那么只能画出一个单独的像素。 App Inventor 编程实例及指南 - 241 -本文档使用 看云 构建 图 17-3 用1个像素画圆,每次只能画1个单独的像素图 17-3 用1个像素画圆,每次只能画1个单独的像素 在块编辑器Built-in组的Colors抽屉中,App Inventor提供了13种常用的颜色,可以用来绘制像素图(或 设置组件背景色)。也可以使用颜色编码方案来获得更为丰富的颜色,颜色编码方案的解释请参见相关 App Inventor文档:http://appinventor.googlelabs.com/learn/reference/blocks/colors.html。 改变canvas外观的第二种方法是在canvas上放置Ball和ImageSprite组件。sprite是一个被放置在场景中的 图形对象,所谓的场景这里指的就是canvas。Ball和ImageSprite组件都属于sprites类型,只是外观不同 而已。Ball为圆形,只能通过改变颜色和半径来改变它的外观,而ImageSprite可以是任何形状; ImageSprite和Ball都只能添加到Canvas中,不可能将它们拖入用户界面中Canvas以外的区域。 用计时事件制造动画用计时事件制造动画 在App Inventor中,为应用添加动画的方法之一就是让物体对计时器事件做出响应,最常用的方法就是让 sprite按照设定的时间间隔,在canvas上进行位置的移动。设定的时间间隔的方法是使用计时器事件最通 用的方法。稍后我们还将讨论另一种方法,即,利用ImageSprite及Ball组件的Speed(速度)及 Heading(方向)属性,通过编程来实现动画效果。 点击按钮以及其他用户触发的事件理解起来非常简单:用户做动作,应用通过执行某些操作来进行响应; 但计时器事件则不然:这类事件不是由最终用户发起,而是由时间的流动来触发。你需要将应用中的这类 香港老钱庄868525,(六合娃娃时钟触发的事件与用户的行为区分开来。 定义计时器事件的第一步是在组件设计器中为应用拖入一个Clock组件。Clock组件有一个关联的 TimerInterval(计时间隔)属性,用来以毫秒为单位定义计时器的计时间隔(1秒=1000毫秒)。如果将 TimerInterval设为500,就意味着每隔半秒钟触发一次计时器事件。计时间隔越小,物体的移动也就越 快。 在设计器中完成Clock的添加以及TimerInterval的设定后,就可以在块编辑器中拖出“when Clock.Timer”事件块,并在其中加入任何你需要的块,这些块将每个一个计时间隔执行一次。 产生运动产生运动 要让sprite随时间移动,就需要用MoveTo函数。在块编辑器的ImageSprite及Ball组件抽屉中可以找到这 个函数。例如,要使一个球在水平方向上穿越屏幕,需要使用图17-4中的块。 App Inventor 编程实例及指南 - 242 -本文档使用 看云 构建 图 17-4 让球在水平方向穿越屏幕图 17-4 让球在水平方向穿越屏幕 MoveTo的作用是在canvas上将物体移动到一个绝对位置,而不是相对位置。因此,为了移动到这个绝对 位置,需要将MoveTo函数的参数设定为当前位置与增量之和。这里我们要实现球的水平移动,只需要将 参数x设定为当前的x值与增量20之和,而y值保持不变(Ball1.Y)。 如果想让球沿着对角线的方向移动,就需要同时设定x、y坐标的增量,如图17-5所示。 图 17-5 设置x、y坐标的增量,实现球在对角线方向的移动图 17-5 设置x、y坐标的增量,实现球在对角线方向的移动 控制速度控制速度 在前面的例子中,球的移动有多快呢?速度取决于两个因素:Clock组件的TimerInterval属性值,以及 MoveTo函数中的参数值。如果计时间隔设为1000毫秒,就意味着每秒钟触发一次计时事件,这样会让运 动变得不流畅。为了得到更为平滑的运动,就需要缩短计时间隔。如果将TimerInterval设为100毫秒,则 球每隔1/10秒移动20像素,或者每秒移动200像素,对于应用的使用者来说,这个速度看起来会平滑得 多。除了改变计时间隔之外,还有一种方法也可以改变速度,你能想到是什么方法吗?(提示:速度与球 移动的频次以及每次的移动量相关。)在保持计时间隔100毫秒不变的情况下,改变MoveTo中的算式也可 以改变移动的速度:让球每次只移动2个像素,即2像素/100毫秒,这相当于20像素/秒。 高级动画功能高级动画功能 这种让物体在屏幕上移动的能力,适合于那些飘来飘去的动画类广告,但要制作游戏或其他的动画应用, 就需要更为复杂的功能。幸运的是,App Inventor提供了几个的高级块,用于处理动画类事件,如物体到 达屏幕边缘及两个物体的碰撞。 在这种情况下,用高级块来侦测两个sprite之间的碰撞这类事件,表明App Inventor已经深入到了程序的 底层细节。其实你自己也可以利用Clock.Timer事件,通过检查每个sprite的xy坐标及Width、Height属性 来检测到这类事件的发生,但这样的程序涉及到非常复杂的逻辑。由于这类事件在许多游戏及其他应用中 很常见,因此App Inventor为你提供了这些功能。 App Inventor 编程实例及指南 - 243 -本文档使用 看云 构建 抵达边界抵达边界 图 17-6 当球到达边缘时让它重回左上角图 17-6 当球到达边缘时让它重回左上角 重新考虑前面的动画,物体在canvas上沿着对角线方向从左上角向右下角移动。依照前面的程序,物体沿 对角线方向移动并将停在canvas的右下角(因为系统不允许sprite对象超出canvas的边界)。 如果想让物体在到达右下角后再重新出现在左上角,可以定义一个事件处理程序Ball.EdgeReached来响应 到达边缘事件。 当Ball碰到canvas的任何一个边时,将触发EdgeReached事件(到达边缘事件,该事件只适用于Sprite及 Ball组件)。这个事件,再加上前面提到的让球沿斜线移动的定时器事件,两个事件共同作用的结果就是, 球从左上角向右下角移动,在到达彼岸猿猴再跳回到左上角,然后继续移动,并再次跳回,循环往复,永 不停止(或者直到接到其他指令)。 注意到在EdgeReached事件中有一个参数,edge1,它代表球碰到的那个边,这里用数字来代表不同的方 向: North = 1 Northeast = 2 East = 3 Southeast = 4 South = -1 Southwest = -2 West = -3 Northwest = -4 CollidingWith事件与NoLongerCollidingWith事件CollidingWith事件与NoLongerCollidingWith事件 射击类、运动类游戏以及其他类型的动画应用通常都会涉及到两个或多个物体之间的碰撞(如,子弹击中 靶子)。 例如,考虑这样一个游戏,当其中的物体与其他物体发生碰撞时,会改变颜色,并发出爆炸声,图17-7中 显示了这样一个事件处理程序。 App Inventor 编程实例及指南 - 244 -本文档使用 看云 构建 图 17-7 当球与其他物体发生碰撞时,变色并发出爆炸声图 17-7 当球与其他物体发生碰撞时,变色并发出爆炸声 NoLongerCollidingWith事件是与CollidedWith相反的事件,当两个碰到一起的物体分开时,触发该事 件。而在游戏中,可能用到图17-8中的块。 图 17-8 当碰撞的物体离开时,球变黑色并停止爆炸声图 17-8 当碰撞的物体离开时,球变黑色并停止爆炸声 注意到CollidedWith及NoLongerCollidingWith事件都有一个参数other,它代表了被撞到的那个物体。 这可以用来处理一个物体(如Ball1)与另一个指定物体之间的相互作用。如图17-9所示。 图 17-9 只有当Ball1碰撞到ImageSprite1时才做相应图 17-9 只有当Ball1碰撞到ImageSprite1时才做相应 之前我们没有提到过这个“ImageSprite组件”块。如果需要对两个组件进行比较(得知究竟是哪一个与 之碰撞),如本例中的情形,就必须指定被比较的具体对象。为此,每个组件都有一个指向它自己的块, 而这个块就在ImageSprite1的抽屉里,排在最后一个的就是。 交互动画交互动画 到目前为止,我们所讨论的动画行为都没有最终用户的参与。毫无疑问,游戏都是交互的,最终用户扮演 着核心的角色,通常他们使用按钮或其他界面对象来控制物体的速度及方向。 作为例子,我们来改变对角线移动的动画,用户可以让移动停止然后再启动。可以通过对Button.Click事 件编程来实现这一点,具体方法是控制clock组件的启用与禁用属性。 在默认情况下,Clock组件的timerEnabled属性是被选中的,可以在事件处理程序中动态地设置它,如设 为false。例如,在图17-10的事件处理程序中,在用户第一次点击按钮时,可以让Clock的计时作用停止运 行。 App Inventor 编程实例及指南 - 245 -本文档使用 看云 构建 图 17-10 当按钮被第一次点击时,停止计时图 17-10 当按钮被第一次点击时,停止计时 在Clock1.TimerEnabled属性被设为false之后,Clock1.Timer事件不再被触发,因此球停止移动。 当然,只是在第一次点击时让运动停止,这样的操作并不能为游戏带来乐趣,需要在事件处理程序中添加 一个ifelse块来控制计时功能的启用与禁用,从而实现对运动的双向控制(运动及停止)。如图17-11所 示。 图 17-11 添加ifelse块,通过点击按钮来控制运动的开始与停止图 17-11 添加ifelse块,通过点击按钮来控制运动的开始与停止 在点击按钮的事件处理程序中,第一次点击按钮,计时器停止计时,按钮上的文字由“停止”变为“开 始”;第二次点击按钮,此时TimerEnabled的值为false,因此执行“else”分支,于是计时器被置于启用 状态,使得物体重新开始移动,按钮上的文字改回“停止”。关于ifelse快的详细信息请参见第18章,另 外,关于用方向传感器创建交互动画的例子,请参见第5章及第23章。 关于没有计时器的sprite动画关于没有计时器的sprite动画 目前为止我们讲述的动画案例都是利用Clock组件的计时功能,计时器事件每触发一次,物体就移动一次。 采用Clock.Timer事件的方案是设定动画最普遍的方案,除了可以移动物体,还可以随时间改变物体的颜 色,改变某些文字(好像应用自己在输入文字一样),或者让应用以某个速度说话,等等。 App Inventor提供了另外一种不需要Clock组件而让物体的移动的方法。你可能已经注意到,ImageSprite 及Ball组件都具有Heading(方向)、Speed(速度)及Interval(间隔)属性。与Clock.Timer方案中定 义事件处理程序相比,这里可以在组件设计器及块编辑器中设置这些属性,来实现对sprite运动的控制。 为了便于描述,我们来重新考虑沿对角线移动的例子。Sprite或ball的Heading属性的取值范围为0-360 度,如图17-2所示。 App Inventor 编程实例及指南 - 246 -本文档使用 看云 构建 图 17-12 Heading属性的取值范围图 17-12 Heading属性的取值范围 如果Heading属性设置为0,则球从左向右移动;如果设为90,则从底向上移动;如果设为180,则从右向 左移动;如果设为270,则从上向下移动。 当然,可以将Heading设定为0-360之间的任何值。要想让球沿对角线从左上角向右下角移动,就需要将 Heading设为315。 此外,还需要设置Speed属性,它可以是0以外的任何值。此处Speed属性对物体的移动作用与MoveTo函 数的作用相同:定义了每个时间间隔(interval)物体移动的像素数,而时间间隔由物体的Interval属性来 定义。 尝试设置这些属性,用Canvas及Ball创建一个测试应用,并点击“Connect AICompanion”,在香港老钱庄868525,(六合娃娃 (或设备)上查看应用。修改Heading、Speed以及Interval属性,看看球是如何运动的。 如果你想通过编程来实现球在左上角与右下角之间做连续往复运动,可以在组件设计器中将球的Heading 属性初始值设为315,然后在块编辑器中添加Ball1.EdgeReached事件处理程序,当球到达边缘时,改变 它的方向。如图17-13所示。 图 17-13 当球到达边缘时改变它的方向图 17-13 当球到达边缘时改变它的方向 小结小结 动画是物体随时间的位置移动或某些属性的变化,App Inventor为提供了几个高级的组件及功能,让动画 的实现变得简单易行。通过对Clock组件的Timer事件进行编程,可以创建任何类型的动画,包括物体的移 动——这是任何类型游戏中最基本的活动。 App Inventor 编程实例及指南 - 247 -本文档使用 看云 构建 Canvas组件在设备的屏幕上定义了一个区域,物体可以在其中移动,并产生交互。Canvas内部只接受两 种类型的组件,即ImageSprite组件及Ball组件。这些组件为处理碰撞及到达边界这样的事件提供了高级功 能。此外,这些组件的Heading、Speed及Interval属性也为运动的实现提供了替代方法。 App Inventor 编程实例及指南 - 248 -本文档使用 看云 构建 第 18 章 程序中的决策:条件块 即使是像口袋里的香港老钱庄868525,(六合娃娃这样小型的电脑,也可以在短短几秒钟内完成超过数千次的操作。更令人惊奇的 是,它们可以基于内存中的数据以及程序员编写的逻辑进行决策。这种决策能力在人们所思考的人工智能 问题中是极为关键的要素,当然也是创建有趣的智能应用的重要组成部分。本章将探索如何在应用中编写 判断选择逻辑。 正如我们在第14章所讨论的,应用的行为由一系列的事件处理程序所定义。每个事件处理程序针对某个特 定事件进行响应,并实现特定的功能。然而,这种响应的过程未必是按线性顺序来实现各项功能,有些功 能只能在一定条件下才能执行。像游戏类的应用可能就会判断分数是否已经达到了100,而位置感知类的 应用可能会问“某个香港老钱庄868525,(六合娃娃是否在某个建筑物的范围之内”。你的应用也可以询问类似的问题,然后根据答 案,继续执行不同的程序分支。 如图18-1,当事件(Event1)发生时,无论如何A功能都会被执行;然后进行一个检测判断:如果检测结 果为真,则执行B1分支;如果结果为假,则执行B2分支;无论执行哪个分支,该事件处理程序的其余部分 (C)都将被执行。 由于像图18-1这样的决策图看起来像一棵树,因此通常会将这种根据判断结果而选择执行的一段程序称 为“分支”。在这种情况下,你会说, “如果测试结果为真,则执行包含B1的分支。” App Inventor 编程实例及指南 - 249 -本文档使用 看云 构建 图 18-1 事件处理程序中,根据条件测试的结果执行不同分支图 18-1 事件处理程序中,根据条件测试的结果执行不同分支 用if及ifelse进行条件测试用if及ifelse进行条件测试 App Inventor提供了两类条件块(如图18-2):if块和ifelse块。可以从Control抽屉里拖出一个if块,然 后点击上面的蓝色图标,弹出可扩充的块,可以根据需要添加任意多个“else”分支。 图 18-2 条件块if及ifelse图 18-2 条件块if及ifelse 可以将任何逻辑表达式(Boolean)插入到if右侧的测试插槽中。逻辑表达式是一个用数学等式,它的返回 值要么是真(true),要么是假(false)。如图18-3,逻辑表达式使用关系运算符(蓝色)以及逻辑运算 符(绿色),对属性值或变量值进行检测。 App Inventor 编程实例及指南 - 250 -本文档使用 看云 构建 图 18-3 用于条件判断的关系及逻辑运算符图 18-3 用于条件判断的关系及逻辑运算符 无论是if块还是ifelse块,只有“if”后面的测试结果为真时,将执行“then”右侧插槽中的块。对于if块, 如果测试结果为假,程序将跳出if块,继续执行if后面的块;而对于ifelse块,如果测试结果为假,将执 行“else”右侧插槽中的块。 因此,对于一个游戏来说,可能会插入一个与成绩有关的逻辑表达式,如图18-4所示。 图 18-4 用于测试成绩值的逻辑表达式图 18-4 用于测试成绩值的逻辑表达式 在本例中,如果成绩到达100,则播放一个声音文件。注意,如果测试结果为假,不执行任何块。如果需 要在测试结果为假时执行某些操作,可以使用ifelse块。 编写一段二选一的决策程序编写一段二选一的决策程序 考虑这样一个应用,无聊的时候也许会用到它:在香港老钱庄868525,(六合娃娃上点击一个按钮,就可以随机地拨打一个朋友的电 话。如图18-5,使用一个random integer(随机整数)块来生成一个数字,然后用ifelse对生成的数字进 行判断,来决定即将拨打的今晚六彩现场开奖结果号码。 图 18-5 用ifelse块判断随机生成的整数来选择要拨打的号码图 18-5 用ifelse块判断随机生成的整数来选择要拨打的号码 App Inventor 编程实例及指南 - 251 -本文档使用 看云 构建 在这个例子中,random integer的参数为1和2,意味着将以相等的几率产生1或2,所产生的随机数保存 在变量randomNum中。 一旦取得了变量randomNum的值,在ifelse块中将变量值与1进行比较:如果randomNum的值为1,程 序将执行第一个分支(then),将今晚六彩现场开奖结果号码设置为“111-1111”;如果变量值不为1,测试结果为假,程 序执行第二个分支(else),今晚六彩现场开奖结果号码被设置为“222-2222”。无论测试结果如何,程序都将拔打今晚六彩现场开奖结果, 因为是在整个ifelse块的下面调用了MakePhoneCall过程。 多重条件判断多重条件判断 许多情况下不只是双重选择,即,可选择的结果不仅仅是两个。例如,也许你希望可以给更多的朋友随机 拨打今晚六彩现场开奖结果,因此就需要在原来的else分支中,再加入一个ifelse,如图18-6所示。 图 18-6 外层条件判断的else分支中加入另一个ifelse条件判断图 18-6 外层条件判断的else分支中加入另一个ifelse条件判断 在这些块中,如果第一个检测条件结果为真,程序将执行第一个“then”分支并拨打号码“111-1111”; 如果第一个测试结果为假,则执行外层的else分支,此时将立即进行另一个测试。因此,如果第一个测试 结果(randomNum=1)为假,而第二个测试结果(randomNum=2)为真,则执行第二个(内层 的)“then”分支,并拨打号码“222-2222”;如果前面两个测试的结果都为假,则执行最下面的内层的 else分支,并拨打第三个号码333-3333。 注意,在修改过的程序中,随机整数生成器(random integer)中的参数2变成了3,因此,将以相等的几 率生成结果1、2或3。 这种在一个条件判断中加入另一个判断的方式称为“嵌套”,在本例中,可以称为“嵌套的if-else块”, 使用这种嵌套的逻辑,可以为随机拨打今晚六彩现场开奖结果的程序提供更多的选择。一般来说,任何程序中都可以使用任 意多层的嵌套。 复杂条件判断复杂条件判断 App Inventor 编程实例及指南 - 252 -本文档使用 看云 构建 除了嵌套,还可以设定更为复杂的检测条件,即,多于一个等式的检测条件。例如这样一个应用,当你 (或你的香港老钱庄868525,(六合娃娃)离开某栋建筑或某个边界时,香港老钱庄868525,(六合娃娃会发出震动。这样的应用适用于那些受控人员,警告他 们不要远离法定的边界;也可以用于家长监视孩子们的行踪;教师可以用它来做自动点名(条件是学生们 都配有Android香港老钱庄868525,(六合娃娃!)。 例如,我们提出这样的问题:香港老钱庄868525,(六合娃娃是否在“旧金山大学哈尼科学中心”范围内?这样的应用要对4个不同的 问题进行一个复杂的检测: 香港老钱庄868525,(六合娃娃所在的纬度低于边界纬度的最大值(37.78034)吗? 香港老钱庄868525,(六合娃娃所在的经度低于边界经度的最大值(-122.45027)吗? 香港老钱庄868525,(六合娃娃所在的纬度高于边界纬度的最小值(37.78016)吗? 香港老钱庄868525,(六合娃娃所在的经度高于边界经度的最小值(-133.45059)吗? 本例中使用了位置传感器(LocatinSensor)组件,即便你没用过这个组件,也能够理解这些程序,在第 23章中将有更多讲解。 使用逻辑运算符and、or及not可以构造出更为复杂的测试条件,可以从Logic抽屉中找到它们。在本例 中,先拖出一个if块以及三个and块,并将and块放在if块的测试插槽中,如图18-7所示。 图 18-7 放在if块测试插槽中的“and”块(选择“External Input/外展式输入”以免块的排列过宽)图 18-7 放在if块测试插槽中的“and”块(选择“External Input/外展式输入”以免块的排列过宽) 然后拖出几个块来组成第一个测试问题,并将其放在and块的第一个测试插槽中,如图18-8所示。 图 18-8 and块中放入了第一个测试问题块图 18-8 and块中放入了第一个测试问题块 App Inventor 编程实例及指南 - 253 -本文档使用 看云 构建 如法炮制出其他几个测试条件,填入其他几个and的测试插槽中,并将整个if块放入事件处理程序 LocationSensor.LocationChanged中,这样就写成了一个检测边界的程序,如图18-9所示。 图 18-9 每次位置更新时,触发该事件处理程序,来检测是否在边界之内图 18-9 每次位置更新时,触发该事件处理程序,来检测是否在边界之内 这些块的功能是,在每次位置传感器读数更新时做出判断,如果香港老钱庄868525,(六合娃娃的位置在边界之内,则发出震动。 OK,到目前为止,应用已经相当酷了,但现在我们来尝试更为复杂的功能,以便你能充分地了解程序中决策 的威力。如何才能让香港老钱庄868525,(六合娃娃仅在越出边界时才发出震动呢?继续学习之前,自己先想想如何来写这样的程 序。 我们的方法是定义一个变量withinBoundary,目的是记住传感器上一次的读数是否在边界内,并根据每一 次后续读数的测试结果对变量值进行修改。withinBoundary是一个布尔(Boolean)类型的变量,与保存 数字或文本的变量相比,它保存的值为true(真)或false(假)。举例来说,如果将变量初始值设为 false,如图18-10所示,这意味着设备不在旧金山大学的哈尼科学中心范围内。 图 18-10 变量withinBoundary为初始化为false图 18-10 变量withinBoundary为初始化为false 对块做出修改,以便在每次位置信息变化时,对变量withinBoundary进行设置,并且只有当香港老钱庄868525,(六合娃娃越出边界 时,才会发出震动。说的更明确一些,香港老钱庄868525,(六合娃娃产生震动的必备条件是(1)变量withinBoundary的值为真, 即意味着上一次读数还在边界内;(2)新的传感器读数超出了边界。图18-11中是修改后的块。 App Inventor 编程实例及指南 - 254 -本文档使用 看云 构建 图 18-11 这些块的功能是:只有当香港老钱庄868525,(六合娃娃从界内移动到界外时,香港老钱庄868525,(六合娃娃才会震动图 18-11 这些块的功能是:只有当香港老钱庄868525,(六合娃娃从界内移动到界外时,香港老钱庄868525,(六合娃娃才会震动 我们来仔细地分析一下。当位置传感器(LocationSensor)获得读数时,首先判断读数是否在边界内,如 果是,将withinBoundary设置为true。由于我们希望只有在香港老钱庄868525,(六合娃娃越出边界时才震动,因此在第一个分支中 不发生震动。 如果执行的是else分支,我们知道新的读数已经超出了边界。此时,我们需要检查上一次的读数:尽管这 次读数超出了边界,但我们希望仅当上次读数在边界内时,才让香港老钱庄868525,(六合娃娃发出震动。withinBoundary变量会告 诉我们上一次的读数,因此我们会检查这个变量,如果检查结果为真,则让香港老钱庄868525,(六合娃娃震动。 一旦确认香港老钱庄868525,(六合娃娃从界内移动到了界外,还有一件事必须要做,你能猜到是什么吗?对,需要重新设置 withinBoundary为false,这样,在下一次收到传感器读数时,香港老钱庄868525,(六合娃娃才不会再次震动。 关于布尔型变量,还有一点需要提示:检查一下这两个if测试,如图18-12,它们的效果一样吗? 图 18-12 你能说出这两个if测试的结果一样吗?图 18-12 你能说出这两个if测试的结果一样吗? 答案是“一样”!唯一的差别在于下边的提问方式实际上更加老练,而上边的测试还要将一个布尔型的变 量(其值只能是true或false)与true进行比较。如果withinBoundary的值为true,将true与true比较,结 果一定是true;如果变量值为false,将false与true比较,结果为false。因此,只需要对withinBoundary App Inventor 编程实例及指南 - 255 -本文档使用 看云 构建 的值进行检测,像右边那样,其结果相同,而且编码更加简洁。 小结小结 头晕了吗?尤其是最后的部分相当复杂!但这类决策方法是高级应用中必须具备的。如果你能一步一步 (或者说一个分支一个分支)地实现这些行为,并做到边做边测试,我们敢断言,你会发现,即便是人工 智能也不是不可能的。它让你头疼,也让你的大脑获得了些许逻辑思维的锻炼,但无疑也是充满乐趣的。 App Inventor 编程实例及指南 - 256 -本文档使用 看云 构建 第 19 章 数据列表编程 如你所见,应用就是处理事件以及作出决策,这一过程是计算机程序的基础,而同样构成程序基础的就是 数据——程序所要处理的信息。程序中很少只用到像游戏中的成绩这样的单个数据,更普遍的是使用复杂 数据——一些相互关联的数据项,必须像设计应用的功能一样,非常细心地组织这些数据。 本章将探讨App Inventor中处理数据的方式,并学习两种数据类型的基本编程方法,两种数据类型为静态 数据(数据的值保持不变)及动态数据(数据由用户生成),然后将学习如何处理更为复杂的包含数据的 数据,即数据项本身也是一组数据。 许多应用中都存在这样复杂的数据,如facebook中的好友列表,测试应用中的问题及答案列表等等,游戏 中也会有角色的列表以及当前最高成绩的列表。 列表变量的使用如同普通的文本及数字变量一样,只是它们不仅仅代表单一的有名称的存储单元,而是表 示一组相互关联的存储单元,例如,考虑表19-1中的今晚六彩现场开奖结果号码列表。 表19-1 列表变量表示一系列的存储单元表19-1 列表变量表示一系列的存储单元 使用索引值(index)来访问列表元素,因此在列表19-1中,index为1时表示第一项111-2222,index为2 时表示第二项333-4444,而index为3时表示555-6666。 App Inventor提供了操作这些数据的块,包括数据的创建、为数据添加元素、从列表中选择指定的项以及 对整个列表的操作,让我们从创建列表开始。 创建列表变量创建列表变量 在块编辑器中,使用“initialize global (name) to”块以及“make a list”块来创建列表变量。例如,假 设你正在写一个“一键发送短信”的应用,通过点击一个按键向今晚六彩现场开奖结果号码列表中的所有成员发送短信。用 如下方式创建一个今晚六彩现场开奖结果号码列表: App Inventor 编程实例及指南 - 257 -本文档使用 看云 构建 1. 从块编辑器的Variables抽屉中拖出一个“initialize global (name)to”块到应用中,如图19-1。 图 19-1 初始化变量的块图 19-1 初始化变量的块 2. 点击文本“name”,将其改为phoneNumbers,如图19-2所示。 图 19-2 将变量重命名为phoneNumber图 19-2 将变量重命名为phoneNumber 3. 从Lists抽屉中拖出“make a list”块插入初始化变量块,如图19-3所示。这是告诉应用,要存储的变量 是一个列表,而非单个值。通过点击“make a list”块上的蓝色增项图标来指定所需存储槽的数量,来增 加数据项,如图19-3所示。 图 19-3 使用“make a list”块来定义列表变量phoneNumber图 19-3 使用“make a list”块来定义列表变量phoneNumber 4. 最后,从Text抽屉中拖出文本块,输入需要的今晚六彩现场开奖结果号码,插入到“make a list”块的数据项插槽中。 图 19-4 当列表中添加了全部数据项,相当于开辟了一个新的存储空间图 19-4 当列表中添加了全部数据项,相当于开辟了一个新的存储空间 数据项的存储中可以插入任何类型的数据,但在本例中,这些数据项是文本类型的对象,而不是数字,因 为今晚六彩现场开奖结果号码中含有一个破折号“-”,这种非数字类型的符号无法输入到数字块中,也无法与数字进行任何 运算(而数字运算必须用到数字块)。 图19-4中所示的块定义了一个名为phoneNumber的变量,在应用启动时,你定义的任何变量就在此时被 创建,而像表19-1中的存储槽也被同时创建并被填写上初始值。一旦有了这个列表变量,就可以使用列表 中的数据开始编程了。 选择列表项选择列表项 App Inventor 编程实例及指南 - 258 -本文档使用 看云 构建 应用中可以使用“select list item”块,并指定索引值(index)来访问列表中指定的数据项。index代表 了该数据项在列表中的位置。因此,如果列表中有三个数据项,就可以用索引值1、2、3来访问这些项。 图19-5中显示了选中列表第二项的块。 图 19-5 选择列表中的第二项图 19-5 选择列表中的第二项 使用选择块“select list item”需要提供两项参数,首先是要查询的列表,将其插入选择块的第一个插槽 中,其次是索引值index,将其插入选择块的第二个插槽中。图19-5中的块是告诉应用从列表 phoneNumber中选出第二个元素。如果列表的定义如表19-1的话,俺么选择的结果就是“333-4444”。 从列表中选择数据项,这仅仅是第一步,通过选择可以实现各种操作,下面将举例说明。 使用Index遍历列表使用Index遍历列表 许多应用中,定义列表的目的是让用户可以遍历(逐个查看)它。第8章总统测验就是一个很好的例子:用 户点击“下一题”按钮,程序从问题劣币哦啊中选择下一道题并显示出来。 但如何实现对下一项的选择呢?图19-5中选择的是列表phoneNumber中的第二项,而遍历列表时,每次 选择的项目序号是不同的,会根据当前选中项在列表中的位置来确定。因此就需要定义一个变量来表示这 个位置,通常用index来作为变量名,初始值通常设为1(列表中的第一个位置),如图19-6所示。 图 19-6 变量index的初始值为1图 19-6 变量index的初始值为1 当用户设法移动到下一项时,可以在当前的index值上加1来实现变量的递增,并使用递增后的值在列表中 做选择。图19-7中显示了实现这一点使用的块。 图 19-7 使index值递增,并用递增后的值选择列表项图 19-7 使index值递增,并用递增后的值选择列表项 举例:遍历画笔颜色列表举例:遍历画笔颜色列表 来看一个例子,用户可以通过点击按钮来为他的房子选择一种可能的粉刷颜色,每次点击,按钮的颜色都 会变化。当用户查阅完全部颜色时,再重新回到第一种颜色。 例子中,可以使用基本色,也可以替换成任何一组颜色,关于颜色的更多信息,可以参见App Inventor文 档(http://appinventor.googlelabs.com/learn/reference/blocks/colors.html)。 App Inventor 编程实例及指南 - 259 -本文档使用 看云 构建 第一步是定义一个颜色列表变量,并以颜色为列表项来初始化列表,如图19-8中所示。 图 19-8 用一组颜色为colors列表做初始化图 19-8 用一组颜色为colors列表做初始化 接下来,定义变量index来跟踪列表的当前项位置,初始值为1。可以给变量一个更有意义的名字,如 currentColorIndex,但如果你的应用中不需要处理其他更多的列表,用index就好。如图19-9所示。 图 19-9 使用index变量来跟踪列表当前项的位置,初始值为1图 19-9 使用index变量来跟踪列表当前项的位置,初始值为1 用户通过点击ColorButton从列表中浏览到下一项(颜色),此时,index值递增,而按钮的 BackgroundColor变为当前选中的颜色,如图19-10所示。 图 19-10 用户通过点击按钮浏览颜色列表——每次点击都会改变按钮颜色图 19-10 用户通过点击按钮浏览颜色列表——每次点击都会改变按钮颜色 先假设我们已经在组件设计器中将按钮的背景颜色设为红色(Red),第一次点击按钮时,index值从初始 的1变为2,按钮颜色变为列表中的第二项——绿色;第二次点击按钮时,索引值从2变为3,按钮变为蓝 色。 想象一下,下一次的点击会出现什么情况? 如果你说会出错,那么你对了!index值将变成4,程序将试图在列表中选择第4项,但列表中只有3项,因 此程序强行关闭,或退出,用户将看到一条如图19-11中所示的错误信息。 图 19-11 当试图从一个只有三项的列表中选择第四项时,程序将提示错误信息图 19-11 当试图从一个只有三项的列表中选择第四项时,程序将提示错误信息 显然,你不想让用户看到这样的信息,为了避免出现这样的问题,需要添加一个if块来检查是否到达了列表 App Inventor 编程实例及指南 - 260 -本文档使用 看云 构建 中的最后一项。如果是,将index值设回1,来显示第一种颜色,如图19-12所示。 图 19-12 使用if块检查索引值index是否大于列表的长度,如果是,将index值重新设为1图 19-12 使用if块检查索引值index是否大于列表的长度,如果是,将index值重新设为1 用户点击按钮时,index值递增,然后检查它的值是否过大。与index值进行比较的是“length of list”, 而不是3,因此,即便是列表中添加了更多的项,程序也能正常运行。通过检查index是否大于列表长度 (而不是与固定的数字3进行比较),可以消除程序中的代码相关性。所谓“代码相关性”是一个编程术 语,举例来说,如果你的应用中某些方面的程序写得过于具体,那么当你想对某处做出修改时(如,列表 的数据项),你不得不找到应用中所有使用过这个list的地方,并对程序块进行逐一修改。 正如你所想象得,这种相关性会让程序在短时间内变得混乱不堪,也会产生更多的错误等待你去排查。事 实上,在“粉刷彩色房屋”应用的设计中,就在我们刚刚完成的程序中,还存在另一个代码相关性问题, 你能找出是什么吗? 如果将颜色列表中的第一项由红色改为其他颜色,应用的运行结果就不再正确,除非你能记得在组件设计 器中修改ColorButton.BackgroundColor属性的初始设定。消除这种代码相关性的方法是,将 ColorButton.BackgroundColor初始值设定为颜色列表中的第一项,而不是某个特定的颜色。由于这一修 改涉及到程序启动时的行为,因此需要在应用启动时调用的Screen.Initialize事件处理程序中进行这一设 定。如图19-13所示。 图 19-13 应用启动时,将按钮的背景色设置为颜色列表中的第一项图 19-13 应用启动时,将按钮的背景色设置为颜色列表中的第一项 创建输入表单及动态数据创建输入表单及动态数据 前面的“粉刷彩色房屋”应用涉及到一个静态列表:程序员(也就是你)定义了列表中的元素,除非你亲 自动手,没有人能修改这些列表项。不过,多数情况下,应用中要处理动态列表:最终用户输入新的数据 项而导致数据的变化,或者从数据库或web信息源加载新数据。本节将讨论一个“随手记”应用:用户在 应用中,通过表单输入笔记,并可预览之前所有输入过的内容。 App Inventor 编程实例及指南 - 261 -本文档使用 看云 构建 定义动态列表定义动态列表 如果希望创建一个空列表,可以使用“create empty list”块来定义,例如,在“随手记”应用中,允许 用户输入笔记,但在定义列表时,不应该有预定义的数据项,具体的定义方法见图19-14。 图 19-14 动态列表的定义中不应该含有任何预定义数据项图 19-14 动态列表的定义中不应该含有任何预定义数据项 添加数据项添加数据项 当第一次启动应用时,notes列表是空的,当用户在表单中输入数据并点击“保存”按钮时,新的笔记内容 将被添加到列表中。表单的设置非常简单,如图19-15所示。 图 19-15 用输入表单想笔记列表中添加新项图 19-15 用输入表单想笔记列表中添加新项 当用户输入一段笔记并点击“保存”按钮,应用将调用“add items to list”函数将新输入的内容添加到 列表中,如图19-16所示。 图 19-16 用户点击“保存”按钮时,调用“add items to list”向列表中添加新内容图 19-16 用户点击“保存”按钮时,调用“add items to list”向列表中添加新内容 “add items to list”块将新的数据追加到列表的结尾,用户每次点击“保存”,就添加一条新笔记,在 Lists抽屉中可以找到这个块。特别注意:还有另一个块“append to list”,它的功能是向一个列表中追 加另一个列表,很少会用到这个块。 显示列表显示列表 对用户来说,列表变量notes的内容是不可见的,还记得之前讲过,应用中的变量是用来保存那些不需要被 用户看到的信息。图19-16中的块实现了一点击按钮就添加新项的功能,但用户看不到任何反馈,除非你 在程序中添加显示列表内容的功能。 在应用的用户界面中显示列表内容最简单的方法就是现实数字和文本的方法:将列表内写入Label组件的 Text属性,如图19-17所示。 App Inventor 编程实例及指南 - 262 -本文档使用 看云 构建 图 19-17 用NotesListLabel的Text属性显示笔记列表图 19-17 用NotesListLabel的Text属性显示笔记列表 可惜这种简单的显示方法看起来不够美观,列表中所有的项被放置在一对小括号内,没有分行,项之间用 空格分隔。如图19-18所示,用户输入了第一条笔记“忘记了让笔记显示出来”,然后又输入第二条“显 示结果被一对括号包围着!”。 图 19-18 列表内容的简单显示方法图 19-18 列表内容的简单显示方法 如果学习过第13章的“亚马逊掌上书店”,你对这个问题应该熟悉。在第20章中,将学习如何用更加复杂 的方式来显示列表内容。 删除列表项删除列表项 使用“remove list item”块可以从列表中删除某一项。如图19-19所示。 图 19-19 删除列表项图 19-19 删除列表项 图19-19从列表notes中删除了第2项,但通常我们不希望只删除某个固定的项(如第2项),而是让用户来 选择需要删除的项。 ListPicker是一个可以用于删除列表项的用户界面组件,它与一个按钮关联,当点击按钮时,ListPicker会 显示列表项,并允许用户选择其中的一项。当用户选中后,应用就可以将其删除。 ListPicker有两个关键事件BeforePicking及AfterPicking,而且每个事件都有两个重要属性:Elements及 Selection,如表19-2所示,只要理解了这两个事件及其属性,ListPicker组件的编程就很容易了。 表19-2 ListPicker组件的两个关键事件及其属性表19-2 ListPicker组件的两个关键事件及其属性 App Inventor 编程实例及指南 - 263 -本文档使用 看云 构建 | 事件 | 属性 | | --- | --- | | BeforePicking:点击按钮时触发 | Elements:选中的列表 | | AfterPicking:用户做出选择时触发 | Selection:用户所选项 | 当用户点击ListPicker的关联按钮时,触发ListPicker.BeforePicking事件,此时用户尚未选择列表项;在 ListPicker.BeforePicking事件处理程序中,可以将ListPicker.Elements属性设置为一个列表变量,例如, 在“随手记”应用中,将Elements属性设置为列表notes,如图19-20。 图 19-20 ListPicker的Elements属性被设置为列表变量notes图 19-20 ListPicker的Elements属性被设置为列表变量notes 这些块将列表notes的内容显示在ListPicker中,如果列表中有两条笔记,其显示如图19-21所示。 App Inventor 编程实例及指南 - 264 -本文档使用 看云 构建 图 19-21 列表notes显示在ListPicker组件中图 19-21 列表notes显示在ListPicker组件中 当用户从列表中选择一项时,将触发ListPicker.AfterPicking事件,在该事件的处理程序中,可以利用 ListPicker.Selection属性来访问用户的所选项。 但是想到“remove item from list”块需要的是索引值(列表中的位置),不是具体的项,而Selection属 性却是实际数据(一条笔记),不是索引值,ListPicker组件不直接提供对列表索引值的访问(在App Inventor的后续版本中将添加此功能)。 变通的方法是利用Lists抽屉中的另一个块:“index in list”。对于给定的文本,该块将返回列表中最先与 该文本匹配的项的位置,使用“index in list”,ListPicker1.AfterPicking事件处理程序将删除用户选中的 项。如图19-22所示。 图 19-22 使用“index in list”块找出要删除项的索引值图 19-22 使用“index in list”块找出要删除项的索引值 AfterPicking事件被触发后,ListPicker1.Selection中包含了用户选中的文本(如“忘记了让笔记显示出 来”)。我们的目标是找到选中项在列表中的索引值,以便将其删除。如果用户选择的是“忘记了让笔记 显示出来”,则“index in list”块将返回1,因为这段文本是列表中的第1项。将索引值保存到变量 removeIndex中,并将它用作“remove list item”块中的index值。 再继续阅读之前,先思考一个问题:这种方法是否总是有效呢? 回答是肯定的,但条件是列表中没有重复的项。比如说,用户输入的第2条和第10条笔记都是“今天过得 太好了!”。如果此时用户点击“删除列表项”按钮(其实是ListPicker),并选中了第10项,那么被删除 的将是第2项,而非第10项。“index in list”块只能返回第一个匹配项,然后就停在那里,因此也就找不 App Inventor 编程实例及指南 - 265 -本文档使用 看云 构建 出应该被删掉的内容相同的第10项。需要对列表进行遍历,并使用适当的条件判断(见第18章)来查看是 否还有其他匹配项,并将其删除。 列表中的列表列表中的列表 列表项可以使数字、文本、颜色、布尔值(true/false),也可以是数据(维基:在计算及数据处理中,数 据往往表示一种结构,如表格[由行和列组成]、树[一组有父子关系的节点]或者图形[一组连接起来的节 点]。数据通常是测量的结果,可以被可视化成图形。),这是一种常见的数据结构。例如,一个数据的列 表可以将第8章总统测试转变为一个多选题测验。我们来重温一下总统测试中数据的基本结构:一个问题列 表和一个答案列表,如图19-23所示。 图 19-23 一个问题列表和一个答案列表图 19-23 一个问题列表和一个答案列表 每当用户回答完一个问题,程序通过与AnswerList中的当前项进行对比来判断回答是否正确。 为了实现多选测验,需要为每个问题提供一个可供选择答案的列表。多选列表可以表示为一个数据列表变 量,将三个“make a list”块放在一个外层“make a list”块中,来定义这个变量,如图19-24所示。 图 19-24 通过在外层“make a list”块中插入若干个“make a list”块来构造出一个数据列表图 19-24 通过在外层“make a list”块中插入若干个“make a list”块来构造出一个数据列表 变量answerChoices中的每个数据项本身也是一个由三个数据项组成的列表,如果从answerChoices列表 中选择一项,选择的结果将是一个列表。现在填好多选答案的双重列表,那么如何向用户显示这些数据 呢? 在“随手记”应用中,使用了一个ListPicker来向用户显示选项。假如索引值为currentQuestionIndex, 则事件处理程序ListPicker.BeforePicking将写成图19-25中显示的样子。 App Inventor 编程实例及指南 - 266 -本文档使用 看云 构建 图 19-25 使用ListPicker向用户展示多选列表图 19-25 使用ListPicker向用户展示多选列表 这些块将选取并显示answerChoices中的当前项对应的子列表,供用户选择。如果currentQuestionIndex 为1,ListPicker将显示图19-26中的列表。 图 19-26 向用户展示第1题的多选答案图 19-26 向用户展示第1题的多选答案 用户选择之后,用图19-27中的块对答案进行检查。 图 19-27 检查用户选择的答案是否正确图 19-27 检查用户选择的答案是否正确 这些块中,用户从ListPicker中选择的答案将与正确答案进行比较,而正确答案保存在另一个列表 AnswerList中(注意answerChoices只提供选项而不代表答案)。 小结小结 你能想到的几乎每个应用中都会用到数据,理解它们的运行机制是编程的基础,本章探索了一种最常用的 编程模式:使用索引变量,从列表的第一项开始,通过变量的递增实现对每个列表项的处理。如果能理解 并在自己的程序中加以运用,那么你的确是一名程序员了。 然后我们讲到了列表处理的其他方式,包括一个典型的让用户添加并删除列表项的表单。如此的编程还需 要另一个层次的抽象能力,你必须假想数据的存在,因为直到用户输入某些数据之前,这些数据都是空 App Inventor 编程实例及指南 - 267 -本文档使用 看云 构建 的。如果你能理解这一点,你甚至可以考虑辞掉现在的日常工作了。 最后我们介绍了复杂的数据结构——数据列表。这显然是一个不太容易理解的概念,但我们利用一些固定 的数据对问题进行了探索:多选测验中的可选择答案列表。如果你对此以及本章的其余部分都有所掌握, 那么你的期末考试题是:使用数据列表创建一个应用,但要求使用动态数据!一个例子就是允许用户在应 用中创建他们自己的多选测验,这个功能甚至比第10章的出题应用还要强大。祝你好运! 在你思考如何处理这些列表时,要知道我们的探索还没有结束。在下一章中,我们将继续讨论并重点讲解 略有不同的列表循环:对列表中的每一项实施一些列的操作。 App Inventor 编程实例及指南 - 268 -本文档使用 看云 构建 第 20 章 循环 计算机最擅长做的事情就是“重复”——像儿童一样不厌其烦地重复做一件事,而且重复的速度很快,可 以在1毫秒内列出你的全部Facebook好友。 本章将学习如何用有限的几个块来编写可以重复执行的程序,而不必反复拷贝粘贴同一段代码;还将学习 与列表有关的操作,如给今晚六彩现场开奖结果号码列表中的每个号码发送一条短信,以及为列表项排序。通过学习,你将 了解到如何用循环块来有效地简化程序。 控制程序的执行:分支及循环控制程序的执行:分支及循环 在前几章中,我们学习了用一组事件处理程序来定义应用中的行为:事件以及对事件做出响应的函数。在 这些响应函数中,程序通常不是按照线性的顺序执行,有些程序块只能在满足某些条件时才能执行。 重复块是程序的另一种非线性运行方式。就像if及ifelse块让程序产生分支一样,重复块让程序循环执行, 换句话说,在执行完一组指令后,重新跳回到这组指令的起点并再次运行,如图20-1所示。在应用的运行 过程中,内部的计数器会跟踪即将执行的下一步操作,因此,对于整个事件处理程序来说,从头至尾的每 一步操作都在程序计数器的监控之下(有条件地)完成。程序计数器随着这些重复执行的块循环,不断地 重复这些功能。 图 20-1 让程序循环执行的重复块图 20-1 让程序循环执行的重复块 在App Inventor中有两种类型的重复块:foreach及while.foreach,其作用是对列表中的每一项实施某些 特定的操作,如,向今晚六彩现场开奖结果号码列表中的每个号码发送一条短信。 App Inventor 编程实例及指南 - 269 -本文档使用 看云 构建 块while的应用比foreach要普遍,while块中的程序块会一直重复运行,直到某个条件不再满足。while块 可用于数学公式的计算,如求n个连续自然数的和,或求n的阶乘,此外,while也可以用于同时处理两个列 表;foreach每次只能处理一个列表。 使用foreach对列表实施迭代使用foreach对列表实施迭代 在第18章里,我们讨论了一个“随机拨号”应用。这种随机拨打朋友今晚六彩现场开奖结果的方式有时能拨通,但如果你有 一个像我这样的朋友,这种呼叫却不总是能得到应答。可以采取另一种方式,给所有列表中的朋友发短信 说“想你”,然后看谁最先回复你(或许还有更令人愉快的方式!)。 这个应用可以通过点击一次按钮向多个朋友发送短信,最简单的方法是,先写好发给一个人的代码块,然 后拷贝粘贴并修改接收人的今晚六彩现场开奖结果号码,如图20-2所示。 图 20-2 拷贝并粘贴向不同号码发送短信的块图 20-2 拷贝并粘贴向不同号码发送短信的块 如果只有少量的块,用这种“强力”的拷贝粘贴方式也还说得过去,但是像朋友列表这样的数据表会时常 变化,而你不希望每次添加或删除一个今晚六彩现场开奖结果号码,都要动手去修改程序。 块foreach提供了一个更好的解决方案,可以定义一个包括所有今晚六彩现场开奖结果号码的列表变量phoneNumberList, 然后用foreach块将发送一次短信的块包围起来,从而实现群发功能,如图20-3所示。 App Inventor 编程实例及指南 - 270 -本文档使用 看云 构建 图 20-3 使用foreach块对列表中的每一项执行同一套指令图 20-3 使用foreach块对列表中的每一项执行同一套指令 上述代码可以解读为: 对于phoneNumberList列表中的每一项(今晚六彩现场开奖结果号码),设置Texting对象的PhoneNumber属性为列表中 的项,并发送该条短信。 对于foreach块,一个必须的参数是一个列表,它所要处理的列表,将列表插入“in list”参数插槽。此 时,从phoneNumberList变量的初始化块中拖出“get global phoneNumberList”块,并插入“in list”插槽,以便为即将发送的短信提供今晚六彩现场开奖结果号码列表。 foreach块的第一行使用了foreach自带的占位符变量,在默认情况下,变量名为item,你可以修改它,也 可以就用默认值,该变量代表了列表中正在被处理的当前项。 foreach中的所有块都将对列表中的每一项执行同样的操作,其中的占位符变量(例子中的 phoneNumber)始终保存的是当前正被处理的项。如果列表中有三项,则foreach中包含的块将被执行三 次,这些块可以说是从属于foreach块,或处于foreach块的内部,这些内部块执行到最后一行时,我们所 说的程序计数器将要循环回第一行。 循环过程详细分析循环过程详细分析 我们来详细地分析一下foreach块的运行机制,因为理解循环是编程的基础。当点击TextGroupButton 时,触发事件处理程序,首先执行的是“set Texting1.Message to”块,要将短信内容设置为“想 你...”,这个块只执行一次。 然后开始执行foreach块。在foreach内部块开始执行前,占位符变量item被设置为列表 phoneNumberList的第一项(111-1111),这一步是自动完成的,代替了你自己使用select list item来 调出列表项。在完成将列表中的第一项赋给item之后,foreach内部的块开始第一次运 行,Texting1.PhoneNumber属性被设为item的值(111-1111),并发出短信。 当运行到foreach中的最后一行时(Texting1.SendMessage块),程序将循环会到foreach的首行,并自 动将列表中的下一项(222-2222)设为变量item的值,然后重复操作foreach内部的两个块,即发送短 信“想你...”到号码222-2222。然后程序再次循环会首行,并将item的值设为列表中的第三项(333- App Inventor 编程实例及指南 - 271 -本文档使用 看云 构建 3333),并执行第三次重复操作,第三次发送短信。 由于列表中最后一项,即本例子中的第三项已经被处理完毕,因此foreach循环到此结束,程序将跳出循 环,这意味着程序计数器将继续下移来处理foreach下面的块。在本例中,foreach之后没有块,因此整个 事件处理程序结束。 书写可维护的代码书写可维护的代码 在最终用户看来,使用foreach的方法还是“强力”的拷贝粘贴法,在最终结果上并无分别,但从程序员的 角度来看,foreach方法让代码有更好的可维护性,即使数据(今晚六彩现场开奖结果号码列表)是动态输入的,程序也可以 适用。 可维护软件指的是可以很容易地对软件进行修改,而不会引入程序的漏洞。使用foreach方法,一旦需要修 改短信接收人,只需要修改列表变量,而丝毫不需要修改程序的逻辑(事件处理程序)。相反,采用强力 的方法,如果需要添加新的接收人,则需要在事件处理程序中添加新的块。任何时候,只要你改动了程序 的逻辑,都会冒带来漏洞的风险。 更重要的是,即便今晚六彩现场开奖结果列表是动态的,即,不仅是程序员,最终用户也可以向列表中添加新的号 码,foreach方法也能奏效。在我们的例子中只有三个固定的号码,而且号码直接写在了代码中,与此相 比,采用动态数据的应用,其信息来源可能是最终用户,或其他来源。如果你要重新设计应用,让最终用 户来输入今晚六彩现场开奖结果号码,你就必须使用foreach方法,因为在你写程序的时候,根本无法知道会有哪些号码,因 此也就无从采用强力的拷贝粘贴法。 foreach的第二个例子:显示列表foreach的第二个例子:显示列表 显示列表项最简单的方式就是将列表变量插入Label的Text属性,如图20-4所示。 图 20-4 列表的简单显示方法:将列表直接插入label图 20-4 列表的简单显示方法:将列表直接插入label 这样做的结果是,列表项在label中显示为一行,项之间以空格分隔,整个列表被一对括号包围:(111- 1111 222-2222 333-3333)。 这些号码可能显示为多行或单行,取决于号码的多少。最终用户能看到这个数据,也可能将它们当做今晚六彩现场开奖结果 号码的列表,但这样的显示方式很不美观。通常会将列表项分行显示或用逗号分隔。 为了适当地显示列表,需要将每个列表项转换为一段带格式的单独的文本。文本对象通常有字母、数字、 标点符号组成,但也可能包含特殊的控制字符,它们对应一些不可见的字符,如tab被表示为\t(更多关于控 制字符的内容,请查阅文本表示的统一码[Unicode]标 准:http://www.unicode.org/standard/standard.html)。 App Inventor 编程实例及指南 - 272 -本文档使用 看云 构建 为了逐行显示我们的今晚六彩现场开奖结果号码列表,需要一个换行符“\n”。当“\n”出现在一段文本中,意味着“到下 一行来显示后面的东西”。因此文本对象“111-1111\n222-2222\n333-3333”将显示为: 111-1111 222-2222 333-3333 要构造出这样的文本对象,需要用到foreach块,将每个列表项附加换行符后再添加到 PhoneNumberLabel.Text属性中,如图20-5所示。 图 20-5 使用foreach处理列表:在每个列表项后添加换行符图 20-5 使用foreach处理列表:在每个列表项后添加换行符 我们来跟踪一下这些块的作用。在第15章中讨论过在程序运行过程中跟踪变量及属性变化的相关内容,在 foreach块中,我们考虑每一次迭代之后的值,所谓一次迭代,就是foreach循环执行一次。 在foreach之前,PhoneNumberLabel的Text属性被初始化为空文本;从foreach开始,程序会自动将列表 的第一项赋给占位符变量phoneNumber。然后将PhoneNumberLabel.Text、\n、phoneNumber连接 起来之后,再将其设为PnoneNumberLabel.Text的属性值。这样,在完成foreach的第一次迭代后,相关 的变量值如表20-1所示。 表20-1 第一次foreach迭代之后的变量值表20-1 第一次foreach迭代之后的变量值 phoneNumberphoneNumber PhoneNumberLabel.TextPhoneNumberLabel.Text 111-1111 \n111-1111 此时已经是foreach内的最后一行,程序进入第二次迭代,下一个列表项(222-2222)被设为占位符变量 phoneNumber的值,并重复执行foreach内部的块:将PhoneNumberLabel.Text的原值(\n111- 1111)与“\n”及phoneNumber(此时是222-2222)连接起来。第二次迭代后,变量及属性值如表20- 2所示。 表20-2 第二次foreach迭代之后的变量值表20-2 第二次foreach迭代之后的变量值 phoneNumberphoneNumber PhoneNumberLabel.TextPhoneNumberLabel.Text 222-2222 \n111-1111\n222-2222 列表中的第三项被设为phoneNumber的值,第三次重复运行foreach内部的块,在完成最后一次迭代后, 最终结果如表20-3所示。 App Inventor 编程实例及指南 - 273 -本文档使用 看云 构建 表20-3 第三次foreach迭代之后的变量值表20-3 第三次foreach迭代之后的变量值 phoneNumberphoneNumber PhoneNumberLabel.TextPhoneNumberLabel.Text 333-3333 \n111-1111\n222-2222\n333-3333 三次迭代完成之后,label包含了所有的今晚六彩现场开奖结果号码,文本变得很长,在foreach执行完成 后,PhoneNumberLabel.Text的显示如下: 111-1111 222-2222 333-3333 用while实现迭代用while实现迭代 循环块while的使用比foreach要稍显复杂,但while块的优势在于它的通用性:foreach可以遍历一个列 表,而while可以为循环设定任意的条件。随便举个例子,假设你想给今晚六彩现场开奖结果号码表中每隔一个人发短 信,foreach则做不到,但while中可以将每次循环中index的递增值设为2。 在第18章中,条件测试的结果将返回一个值:true或false,在while-do块中也包含了一个想if块一样的条 件测试。如果while测试的结果为true,程序会执行while内部的块,然后返回并再次进行条件测试。只要 测试结果为true,while内部的块就会重复运行。当测试值为false时,程序将跳出循环(如同foreach中一 样)并继续执行while下面的块。 使用while同步处理两个列表使用while同步处理两个列表 关于while的更具启发性的例子中,涉及到了一种常见的情形,即,需要同步处理两个列表。例如,在总统 测试(第10章)应用中,有两个分别存放问题和答案的列表,以及一个变量index来跟踪当前的问题序 号。为了同时显示问题-答案对,需要同步遍历两个列表,并从两个列表中获取序号为index的项。foreach 只允许遍历一个列表,但在while循环中,则可以使用index从每个列表中抓取对应的项。图20-6中显示了 用while块逐行显示问题-答案对的方法。 App Inventor 编程实例及指南 - 274 -本文档使用 看云 构建 图 20-6 使用while循环逐行显示问题-答案对图 20-6 使用while循环逐行显示问题-答案对 由于用while替代了foreach,因而需要直接初始化index、检查是否到达列表结尾、在每次循环中选择各个 列表中对应的项,并使得index递增。 使用while做公式计算使用while做公式计算 这里是使用while循环的另一个例子:与列表无关的重复操作。想想看,图20-7中的块在做什么?高水平? 要想弄清楚,就要跟踪每一个块(关于程序跟踪的更多内容见第15章),随着程序的进展,跟踪每个变量 的值。 图 20-7 你能说出这些块的功能吗?图 20-7 你能说出这些块的功能吗? 当变量number的值小于或等于变量N时,while中的块将重复执行。在这个应用中,N值等于最终用户在 界面上的文本框(NTextBox)中输入数字,假设用户输入3。当程序运行到while块时,程序中的变量如表 20-4所示。 表20-4 程序运行到while块时,各个变量的值表20-4 程序运行到while块时,各个变量的值 NN numbernumber totatota 3 1 0 在第一次循环中,while块询问:number值小于或等于(≤)N 吗?第一次询问得到的结果是true,于是 执行while中的块:total值等于它现在的值(0)加上number(1),number值递增1。第一次while循环 之后,各变量的值如表20-5所示。 表20-5 while中的块完成第一次循环使用,各个变量的值表20-5 while中的块完成第一次循环使用,各个变量的值 NN numbernumber totaltotal 3 2 1 App Inventor 编程实例及指南 - 275 -本文档使用 看云 构建 第二次循环中,继续测试“number≤N”,结果仍然是true(2≤3),因而while内部的块再次运行。total 值等于它自身(1)加上number(2),number继续递增。第二次迭代完成时,各变量的值如表20-6所 示。 表20-6 两次循环结束时,各个变量的值表20-6 两次循环结束时,各个变量的值 NN numbernumber totaltotal 3 3 3 程序再次返回到条件测试,这次的结果仍然是true(3≤3),于是while内的块第三次运行。现在total值为 它自身(3)加上number(3),结果为6;number递增到4,如表20-7所示。 表20-7 三次循环之后各个变量的值表20-7 三次循环之后各个变量的值 NN numbernumber totaltotal 3 4 6 在完成第三次迭代之后,程序再次返回测试“number≤N”,或“4≤3”,此时结果为false,因此while内 部的块不再执行,事件处理程序完成。 现在该知道这些块的作用了吧?它们在做一个最基本的数学运算:数字计算。每当用户输入数字,程序就 给出从1到N的自然数的和,这里的N就是输入的数。在这个例子中,我们假设用户输入了3,因此加和的 结果是6;如果用户输入4,最后的结果为10。 小结小结 计算机擅长于做重复的事情。想象一下所有的银行账户都要做利息的累计核算,所有计算学生平均绩点的 成绩处理,以及日常生活中计算机所做的各种无计其数的重复的工作。 App Inventor 提供了两种用于循环操作的块。foreach块适合于针对列表中的每一项实施一组相同的操 作。与那些具体的数据相比,foreach更适合于处理抽象的列表,其编码更具可维护性,尤其是对于动态数 据来说,foreach是必需的。 与foreach相比,while则更为通用:既可以处理单个列表,也可以同步处理两个列表,还能进行公式计 算。在执行while循环时,只要条件测试结果为真,while内部的块就会顺次执行;在内部块运行完成后, 程序将返回并重新进行条件测试,直到测试结果为false,则循环结束。 App Inventor 编程实例及指南 - 276 -本文档使用 看云 构建 第 21 章 定义过程 像App Inventor这类的编程语言通常会提供一组基本的内置功能,对于app inventor来说,就是一组基本 块。编程语言还提供一种功能扩展的方法,即,向语言中添加新的子程序(块)。【在计算机科学中,子 程序(英语:Subroutine, procedure, function, routine, method, subprogram),是一个大型程序中 的某一部份代码,由一个或多个语句块组成。它负责完成某项特定任务,而且与其他代码相比,具备相对 的独立性。——译者注】在App Inventor中,通过定义过程(procedure),即,命名一些顺序执行的 块,来实现功能的扩展。应用中可以像调用App Inventor中的预定义块一样,调用这些过程。本章中你将 看到,创建这样抽象的过程的能力对于解决复杂问题是非常重要的,这是创建真正好应用的基石。 当家长对孩子说“睡觉前去刷牙”时,他们的实际含义是“从架子上拿起牙刷牙膏,向牙刷上挤一点牙 膏,在每颗牙齿上刷10秒钟(哈哈!)”,等等。“刷牙”就是一种抽象:为一系列的低级指令起一个公 认的名称。此处,家长要求孩子完成他们已经认可了的“刷牙”的一系列指令。 你也可以在编程中创建这样的有名字的一系列指令,有些编程语言称之为函数(function)或子程序 (subprogram),在App Inventor中,被称为过程(procedure)。过程就是一组顺序执行的有名字的 块,在应用中可以随时随地调用它。 图21-1就是一个过程的例子,它的功能是以英里为单位,计算两个GPS坐标之间的距离。 App Inventor 编程实例及指南 - 277 -本文档使用 看云 构建 图 21-1 计算两点间距离的过程图 21-1 计算两点间距离的过程 不必急于探究这个过程中的内部构件,只要知道对于你所使用的编程语言来说,这样的过程扩展了它的功 能。如果每个家长每天晚上都要向他的孩子解释“刷牙”的步骤,那么这个孩子到了五年级可能还是不会 刷牙。说“刷牙”是一种更有效的方式,而且每个人都会在睡觉之前去刷牙。 同样的道理,在设计或编写一个大型应用时,一旦定义好了distanceBetweenPoints这个过程,你就会忽 略它的内部实现细节,而只是简单地使用(或调用)它的名字。这种抽象能力对于解决大型问题来说是至 关重要的,可以将大型的软件项目分解成若干个便于管理的代码块。 过程还可以有助于减少错误,因为它们可以省去很多冗余的代码:只要在一处定义了过程,应用中就可以 随处调用它。因此,假如应用中要计算你的当前位置与其他10个点之间的最近距离,你不必拷贝粘贴10次 图21-1中的块,相反,你只需要定义这个过程,并在需要时调用它即可。此外,那种拷贝粘贴块的方法还 非常容易引入错误,因为一旦你想修改程序,就必须找到所有的拷贝,并逐个以相同的方式修改它们。想 象一下,你试图在一个有1000行或块的代码中,找到5-10个曾经粘贴过的代码块!与其被迫地拷贝粘贴这 写块,不如用过程在一处将代码块封装起来。 最后,过程将有助于建立代码库,让这些代码在其他应用中可以被重用。即便是创建一个非常具体的应 用,有经验的程序员总会在必要时设法考虑重用其他应用中的部分代码。有些程序员从未创建过应用,他 们只是专注与创建可重用的代码库,以便其他程序员以此来创建他们自己的应用。 消除冗余消除冗余 看一下图21-2中的代码块,能否发现其中的冗余。 App Inventor 编程实例及指南 - 278 -本文档使用 看云 构建 App Inventor 编程实例及指南 - 279 -本文档使用 看云 构建 图 21-2 "随手记"应用中的冗余代码图 21-2 "随手记"应用中的冗余代码 这里的冗余代码指与foreach块有关(实际上是整个foreach块以及它上面的"set NotesLabel.Text to"块),例子中的三个foreach的作用都是显示笔记列表,只是使用的场合有所不同:当添加新项、删除 某一项,以及应用启动从数据库加载列表时。 作为一个有经验的程序员,一旦看到这样的代码,脑子里会立即敲响警钟,甚至不必等到开始拷贝粘贴第 一段程序中的代码,他们知道最好是将这些冗余的代码封装在一个过程里,这样既保证程序有很好的可读 性,也可以使后来的修改变得容易。 因此,有经验的程序员会创建一个过程,将冗余代码块放在其中,并在原来使用冗余代码的地方调用这一 过程。应用的执行结果完全一样,但更易于维护,也让其他程序员更容易地加以利用。这种代码(块)的 重新整理的过程成为重构。 定义过程定义过程 我们来创建一个过程,实现图21-2中那些冗余代码的功能。在App Inventor中,定义过程几乎与定义变量 一样简单:从Procedures抽屉中拖出一个“to procedure”块或“to procedure result”块。如果过程 需要通过计算返回一个结果,则使用后者(我们将在本章稍后的部分讨论它)。 在拖出“to procedure”块后,可以修改过程名称:点击默认名称“procedure”并输入新名称。由于冗 余代码块的作用是显示笔记列表,因此重构时将过程名设为“displayList”,如图21-3所示。 App Inventor 编程实例及指南 - 280 -本文档使用 看云 构建 图 21-3a 点击默认名称“procedure”图 21-3a 点击默认名称“procedure” 图 21-3b 将过程名改为“displayList”图 21-3b 将过程名改为“displayList” 下一步是向过程中添加块,此时就用现有的冗余块,将它们从事件处理程序中拖出并放在displayList块 中,如图21-4所示。 图 21-4 封装了冗余代码的过程displayList图 21-4 封装了冗余代码的过程displayList 现在我们可以用过程来显示笔记列表了,在应用的任何一处,都可以很容易地调用它。 调用过程调用过程 像“displayList”和“刷牙”这样的过程是一个包含了某种功能的实体,它们只有在被调用时,才能体现 出这种功能。因此,以上我们只是创建了过程,却并没有调用它。调用它意味着要运行它,或者说来实现 它。 在App Inventor中,可以从Procedures抽屉中拖出一个以“call”开头的块来调用一个过程。每当定义了 一个新的过程,procedures抽屉中就会显示一个新的块,即定义一个过程,就是向Procedures抽屉中添 加一个新块,如图21-5所示。 图 21-5 定义好一个过程后,Procedures抽屉中就会出现一个新的“call” 块图 21-5 定义好一个过程后,Procedures抽屉中就会出现一个新的“call” 块 App Inventor 编程实例及指南 - 281 -本文档使用 看云 构建 你一直都在用“call”块来调用App Inventor中的预定义函数,如Ball.MoveTo以及 Texting.SendMessage。当你定义了一个过程,就相当于创建了自己的块,也相当于你扩展了App Inventor语言,新的“call”块让你可以使用自己的创造。 在“随手记”的例子中,三次拖出“call displayList”块来取代三个事件处理程序中的冗余代码, 如,ListPicker1.AfterPicking事件处理程序(删除一条笔记)修改的结果如图21-6所示。 图 21-6 使用“call displayList”来调用放在过程中的那些块图 21-6 使用“call displayList”来调用放在过程中的那些块 程序计数器程序计数器 要理解“call”块的运行机制,要想象应用中有一个指针,它随着块的运行而移动。在计算机科学中,这个 指针被称作程序计数器。 程序计数器随着事件处理程序中的块的运行而移动,当它遇到一个“call”块时,它会跳到所遇到的过程 中,并开始随着过程中的块的执行而移动,;当过程执行完成,程序计数器再跳回到此前的位置 (“call”块处),并从此处开始继续移动。以“随手记”为例,“remove list item”块执行完成后,程 序计数器跳到displayList过程中,并随过程中的块(设置NotesLabel.Text属性为空,以及foreach循环) 移动;最后程序计数器在回到TinyDB1.StoreValue块。 为过程添加参数为过程添加参数 过程displayList将冗余代码重整到一处,这使得程序更加容易理解,你可以在更高层次上理解这些事件处 理程序,而忽略掉如何显示列表的细节。这样做的另一个好处是,如果想要修改列表的显示方式,就只需 修改一处代码(而不是三处)。 App Inventor 编程实例及指南 - 282 -本文档使用 看云 构建 就过程的通用性而言,displayList是有局限的,因为该过程是针对特定的列表(notes)而设定的,而且用 指定的label(NotesLabel)来显示列表内容,它不能用于显示其他列表,比如应用的用户列表,因为过程 中的要素定义的过于具体。 App Inventor以及其他编程语言都提供了一种称为参数的机制,用于构造更为通用的过程。过程为了实现 它的预设功能所必须的信息就由参数来提供,以睡前刷牙为例,有可能将牙膏的类型和刷牙时间设定为刷 牙过程的参数。 通过点击过程块左上角的蓝色标记,就可以为过程设定参数。对于displayList过程,我们定义了一个名 为“list”的参数,如图21-7所示。 图 21-7 在过程中引入了list作为参数图 21-7 在过程中引入了list作为参数 即使是定义了参数,但foreach块中仍然直接引用特定列表“notes”(插入到foreach块的“in list”插槽 中)。而我们希望在过程中使用我们传递的参数list,因此将对“global notes”的引用替换成对“get list”的引用。如图21-8所示。 图 21-8 现在foreach中使用了传递来的参数“list”图 21-8 现在foreach中使用了传递来的参数“list” 新版本的过程更加通用:在调用displayList时,无论传入什么样的列表,displayList都能显示它。在向过 程添加参数时,App Inventor会自动为“call”块添加一个对应的插槽,因此当displayList添加了参数list 之后,“call displayList”块就变成图21-9中的样子。 App Inventor 编程实例及指南 - 283 -本文档使用 看云 构建 图 21-9 现在调用displayList时,需要指明要显示的列表图 21-9 现在调用displayList时,需要指明要显示的列表 过程定义中引入的参数list被称为“形式参数”,而“call”块中与之相对应的插槽被称为“实际参数”。 当在应用中的某处调用过程时,必须为过程中的每个“形式参数”提供一个“实际参数”。 对于“随手记”的应用来说,将列表“notes”作为实际参数添加到“call”块的list插槽中。 ListPicker.AfterSelection的修改结果如图21-10所示。 图 21-10 在调用displayList时,将notes作为实际参数传入图 21-10 在调用displayList时,将notes作为实际参数传入 现在当displayList被调用时,列表notes被传递到过程中,来取代形式参数list。此时,程序计数器随着过 程中的每个块的运行,它的指向是参数list,而实际上处理的是变量notes。 由于有了参数,过程displayList可以用于处理任何列表,而不仅仅是notes。例如,如果“随手记”应用可 以在一组用户中共享,而你想查看一下用户列表,就可以调用displayList并传入userList参数。如图21-11 所示。 图 21-11 过程displayList可用于显示任何列表,而不仅仅是notes图 21-11 过程displayList可用于显示任何列表,而不仅仅是notes 过程的返回值过程的返回值 关于过程displayList的可重用性,还有一个问题需要讨论——你能猜到是什么吗?如前所述,它可以显示 任何数据列表,但也只能在标签NotesLabel中显示。如果你想用其他的界面元素(如另一个label)来显示 App Inventor 编程实例及指南 - 284 -本文档使用 看云 构建 列表(如userList),该如何是好呢? 一个方法就是重构过程——将它的功能从“用指定label显示列表”改为“只返回一个文本对象,它可以被 显示在任何地方”。为此,需要使用“procedure result”块来取代“procedure”块,如图21-12所示。 图 21-12 “procedure result”块图 21-12 “procedure result”块 你会发现与“procedure”块相比,“procedure result”块的底部有一个额外的插槽,将一个变量放入 插槽,这个变量将被返回给调用者。因此,正如调用者可以向过程以参数的方式传入数据一样,过程也可 以以值得方式将数据返回给调用者。 图21-13显示了上述过程的改写版本,现在使用的是“procedure result”块。注意,由于过程的作用变 了,因此名称也由displayList改为convertListToText(将列表转换为文本)。 图 21-13 过程convertListToText返回一个文本对象,调用者可以将其放在任何一个label中图 21-13 过程convertListToText返回一个文本对象,调用者可以将其放在任何一个label中 在图21-13所示的块中,变量text用来保存foreach循环中通过遍历列表而生成的文本。用text变量取代之 前使用的过于具体的NotesLabel组件。在foreach执行完毕后,变量text包含了列表中的所有项,而且项 之间以换行符“\n”分隔(即“item1\nitem2\nite3”)。最后,将变量text插入return插槽,返回给调 用者。 在定义“procedure result”时,与“procedure”相比,对应的“call”块看起来略有不同,如图21-14 中所做的比较。 App Inventor 编程实例及指南 - 285 -本文档使用 看云 构建 图 21-14 下面的有返回值的“call”必须插入到某个插槽中图 21-14 下面的有返回值的“call”必须插入到某个插槽中 不同的是在“call convertListToText”块的左侧有一个插头,这是因为当“call”块运行时,过程在执行 一系列指令后将向“call”块返回一个值,必须有某个插槽可以接收这个返回值。 在这种情况下,调用块“call convertListToText”的返回值可以插入到任何一个label的Text属性中,以 notes列表为例,需要显示列表的三个事件处理程序都可以调用这一过程,如图21-15所示。 图 21-15 将列表notes的内容转换为文本,并用NotesLabel显示出来图 21-15 将列表notes的内容转换为文本,并用NotesLabel显示出来 更重要的是,由于过程的定义更具通用性,不需要引用任何特定list或label,因此应用中可以使用 convertListToText在任何一个label蒸南瓜显示任何一个列表。像图21-16中的例子那样。 图 21-16 这一过程再也不必与一个特定的Label组件捆绑在一起图 21-16 这一过程再也不必与一个特定的Label组件捆绑在一起 在应用中重用块在应用中重用块 通过过程的方式实现代码的重用不必只限于单独的应用,有许多过程,如convertListToText,可以用在你 创建的任何应用中。事实上,有许多组织和编程社区都在为他们感兴趣的领域创建过程代码库,例如动画 过程的代码库。 通常编程语言会提供一个“import(导入)”功能,可以在任何应用中引入其他的代码库。App Inventor 目前没有这项功能,不过正在开发之中。同时,也可以在一个特定的“库应用”中创建一些过程,并复制 该应用的代码,作为一个新建项目的基础代码。 第二个例子:求两点间距离第二个例子:求两点间距离 在displayList(convertListToText)例子中,我们将过程定义描述为一种消除冗余代码的方法:你开始写 代码,随后发现代码存在冗余,于是整理代码消除冗余。无论如何,一个软件的开发人员或开发团队在应 用开发的初期都会创建很多过程,同时也考虑到要重用部分代码。这样的规划可以在项目过程中节省大量 时间。 考虑一项应用:确定离某人当前位置最近的本地医院,某些东西在紧急情况下会派上用场的。以下是这个 应用的高层设计描述: App Inventor 编程实例及指南 - 286 -本文档使用 看云 构建 应用启动时,以英里为单位计算两点之间的距离,起点是当前所在位置,终点是发现的第一家医院。然后应用启动时,以英里为单位计算两点之间的距离,起点是当前所在位置,终点是发现的第一家医院。然后 再寻找第二家医院,以此类推。在求得若干个距离后,判断最短距离的医院,并显示它所在位置的322555现场开奖,香港马会开奖结果直播。再寻找第二家医院,以此类推。在求得若干个距离后,判断最短距离的医院,并显示它所在位置的322555现场开奖,香港马会开奖结果直播。 从以上描述中,你能断定应用中需要什么样的过程吗? 通常,一段描述中的动词提示了所需的过程。重读一遍描述,正如“等等”所提示的,这是另一个线索。 这种情况下,“求出两点之间的距离”与“判断这些距离中最短的”成为两个必需的过程。 现在考虑设计一个过程distanceBetweenPoints(两点间距离)。在设计过程时,首先要确定过程的输入 及输出:调用者需要向过程传递实现过程的功能所需的参数,而过程要向调用者返回执行结果。在这里, 调用者需要向过程传递两个点的经度及纬度值,如图21-17所示;而过程的任务是以英里为单位返回两点 之间的距离。 图 21-17 调用者想过程传递了4个参数,并收到一个距离图 21-17 调用者想过程传递了4个参数,并收到一个距离 图21-18中显示了我们在本章开始时提到的那个过程,使用公式求得两个GPS坐标点之间的近似英里数。 图 21-18 过程distanceBetweenPoints图 21-18 过程distanceBetweenPoints 图21-19显示了对上述过程的两次调用,每次都会求出当前位置与指定医院之间的距离。 App Inventor 编程实例及指南 - 287 -本文档使用 看云 构建 图 21-19 两次调用distanceBetweenPoints过程图 21-19 两次调用distanceBetweenPoints过程 第一次调用中,起点为用户当前所在位置的LocationSensor(位置传感器)读数,终点是St. Mary's hospital(圣玛利亚医院),计算的结果保存在变量distanceStMarys中;第二次调用也类似,只是将终点 的数据改为CPMC Hospital(加州太平洋医疗中心医院)的经纬度。 接下来程序比较两个距离并返回最近的医院。但是如果还有更多的医院,那就需要在一个距离列表中进行 比较,并找到最小值。依你所学,你能写出这个过程吗?将其命名为findMinimum,接受一个数值列表作 为参数,并返回最短距离在列表中的索引值。 小结小结 像App Inventor这样的编程语言提供了一个内置功能的基本集,而过程是一种新功能的提取,它扩充了 app inventor语言。App Inventor不提供显示列表的块,于是由你来做;那么是否需要一个计算两个GPS 坐标间距离的块呢?答案是靠我们自己来创造。 想要建造大型的、可维护的软件,以及在解决复杂问题时免于不断地纠缠于细节之中,则定义高级过程的 能力是至关重要的。过程是将代码块封装起来,并起一个名字。在编写过程时,你会关注这些块的细节, 但对程序的其他部分而言,这个过程只是一个抽象的名字,你可以在更高层次上来引用它。 App Inventor 编程实例及指南 - 288 -本文档使用 看云 构建 第 22 章 数据库 Facebook的数据库中,有每位用户的账户信息、好友列表以及发布的信息,Amazon的数据库中有你能买 到的任何东西,而Google的数据库中有互联网上的每个页面的信息。你自己的应用虽然没有那么大的规 模,但一个正规的应用都会用到数据库组件。 在大多数的编程环境中,编写与数据库通信的应用是一种高级编程技术:要搭建数据库(软件)服务器, 如Oracle或MySQL等,并编写程序与数据库建立连接。在大学里,这些内容通常要在软件工程或数据库这 样的高级课程中才会涉及。 App Inventor承担了与数据库(以及许多其它有用的事情)有关的这部分繁琐的设置,在这个语言中,提 供了数据库组件,将数据库通信简化为单纯的读写操作。应用可以直接将数据保存在Android设备上,也 可以保存到集中式网络数据库中,从而实现在不同设备与其他人之间的数据共享。 保存在变量及组件属性中的数据属于临时存储:如果用户在表单中输入某些信息然后关闭应用,那么当应 用重新打开时,这些信息将不复存在。想要长期保存信息,就需要将它们保存到数据库中。数据库中的信 息被称为永久信息,因为当应用在关闭后重新打开时,数据依然存在。 作为例子,考虑第4章开车不发短信的应用,那个繁忙时自动回复短信的应用。这个应用允许用户输入一条 个性化的信息,作为收到短信时的自动回复信息。如果用户将信息改为“我在睡觉,别来烦我”,然后关 闭了应用,当重新打开应用时,定制的自动回复信息依然是“我在睡觉,别来烦我”。因此,定制信息必 须保存到数据库中,在每次启动应用时,再将信息从数据库提取到应用中。 在TinyDB中永久保存数据在TinyDB中永久保存数据 App Inventor提供了两个便于操作数据库的组件:TinyDB及TinyWebDB。TinyDB用于直接在Android设 备上永久保存数据,它适合于那些极其私人化的应用,如开车不发短信,这类应用不需要让数据在不同设 备及人群之间共享。而TinyWebDB则将数据保存到web数据库中,并可实现不同设备之间的共享。能够通 过web数据库访问数据,这是多人游戏及应用的基础,用户可以借此分享信息(如第10章的出题应用)。 这两个数据库组件非常相似,但TinyDB更简单些,因此我们先来研究它。首先,不需要任何设置就可以直 App Inventor 编程实例及指南 - 289 -本文档使用 看云 构建 接使用它,此外,数据直接保存在设备上,并于应用相关联。 使用TinyDB.StroeValue块来实现数据的长期存储,如图22-1所示,这段代码来自于“开车不发短信”。 图 22-1 TinyDB.StoreValue块将数据永久保存到设备中图 22-1 TinyDB.StoreValue块将数据永久保存到设备中 数据库存储中用到了tag-value(标签-值)模式,在图22-1中,数据的标签是“responseMessage”,而 值是用户输入的内的自动回复信息,比如“我在睡觉,别来烦我”。 标签是数据的名称,是信息查询的依据,而致才是数据本身。可以将标签理解为钥匙,必须用它从数据库 中提取已经存储的数据。 同样,可以将App Inventor的TinyDB数据库理解为一个表,其中包含了许多tag-value对儿,在图22-1中 的TinyDB.StoreValue块执行完成后,设备数据库将增加一条输入,如表22-1中所列。 表22-1 存储到数据库中的tag-value对:“responseMessage”-“我在睡觉,别来烦我”表22-1 存储到数据库中的tag-value对:“responseMessage”-“我在睡觉,别来烦我” | tag | value | | --- | --- | | responseMessage | 我在睡觉,别来烦我 | 一个应用中可以有许多tag-value对,用来永久保存需要保留的各种数据项。标签必须是文本,而值既可以 App Inventor 编程实例及指南 - 290 -本文档使用 看云 构建 是单个的数据(一段文本或一个数字),也可以是一个列表。每个标签只能对应一个值,当你使用同一个 标签保存一个新值时,将覆盖原来的值。 从TinyDB中提取数据从TinyDB中提取数据 从数据库中提取数据要用到TinyDB.GetValue块。在调用GetValue块时,通过提供标签(tag)来请求特 定的数据。在“开车不发短信”中,使用在保存数据时(StoreValue)用过的标 签“responseMessage”来请求定制的回复信息。调用GetValue所获得的返回数据,必须插入到一个变 量中。 通常要在应用打开时从数据库中提取数据。App Inventor提供了一个特别的事件处理程序 Screen.Initialize,应用启动时会触发该程序。需要格外小心地处理数据库为空的情况(如,应用第一次启 动时),因此当使用GetValue时,要指定一个“valueIfTagNotThere”参数,一旦数据库为空,则 GetValue将返回该参数值。 图22-2中的块显示了在“开车不发短信”中,如何在应用初始化时,使用Screen.Initialize加载数据。 图 22-2 应用启动时加载数据的一种模式图 22-2 应用启动时加载数据的一种模式 这里将GetValue的返回值写入到ResponseLabel组件中。如果数据库中已经存储过数据,则将读取的数据 写入ResponseLabel中,如果没有与标签responseMessage相对应的数据,则将“我正在开车...”写入 Label。 用TinyWebDB保存并共享数据用TinyWebDB保存并共享数据 TinyDB组件将数据保存在Android设备的本地数据库中,这一点适用于那些不需要数据共享的个人应用, 例如很多人都可以下载“开车不发短信”应用,但每个人都使用个性化的自动回复信息,这类信息不需要 与其他人共享。 当然,更多的应用需要数据共享:像Facebook、Twitter以及像Words With Friends这样流行的多人游 戏,这些应用的数据库必须运行在网络上,而非设备上。另一个例子是第10章的“出题/答题”应用,某人 在香港老钱庄868525,(六合娃娃上生成了一份测试,并将其保存到网络数据库中,这样其他人就可以在其他香港老钱庄868525,(六合娃娃上加载测验,并回 答问题。 TinyWebDB是TinyDB的web版本,可以让应用将数据保存到web上,方法与TinyDB类似,使用 StoreValue与GetValue协议。 默认情况下,TinyWebDB组件使用由App Inventor团队创建的web数据库保存数据, 从http://appinvtinywebdb.appspot.com可以访问到该数据库。该网站包括一个数据库,并能响应来自 App Inventor 编程实例及指南 - 291 -本文档使用 看云 构建 web的保存及提取数据的请求;此外,还提供了一个人类可读的web接口,可以让数据库管理员(也就是 你)能够查看到在此保存的数据。 感兴趣的话,可以在浏览器中访问http://appinvtinywebdb.appspot.com,并检查保存在此的tag-value 类型的数据。 这个默认的数据库仅用于开发,对于所有App Inventor程序员提供了有限的空间和权限。由于所有的App Inventor应用都可以使用该数据库,因此不能确保你的数据不被其它的应用所覆盖。 如果你只是在研究学习App Inventor,或者在项目的早期阶段,默认的web数据库就足够了,但如果你想 创建正式发布的应用,从某种意义上讲,你需要建立自己的web数据库。由于我们正在学习,因此可以使 用默认的数据库。在本章的后面将学习如何创建自己的web数据库,并配置TinyWebDB,来替代默认的数 据库。 在这一节中,我们通过一个投票应用(如图22-3所示)来描述TinyWebDB的用法。该应用具有如下特 性: 图 22-3 投票应用:将投票结果保存到TinyWebDB中图 22-3 投票应用:将投票结果保存到TinyWebDB中 每次应用打开之后,提示用户输入自己的email322555现场开奖,香港马会开奖结果直播,该322555现场开奖,香港马会开奖结果直播既是用户名,又是保存到数据库中的投票 信息的标签(tag); 任何时候用户都可以提交新的投票内容,这种情况下,原有的投票内容将被覆盖; 用户可以看到群组中每个人的投票结果; 为简单起见,需要投票的议题在应用之外发布,如课堂上,教师宣布议题并要求每个学生进行电子投 票。(注意,这个例子的功能可以扩展,在应用中允许用户输入并提示投票议题。) 用TinyWebDB保存数据用TinyWebDB保存数据 TinyWebDB.StoreValue的作用与TinyDB.StoreValue一样,只不过是将数据保存到Web上。在这个投票 的例子中,假设用户会在文本框VoteTextBox中输入投票内容并点击按钮VoteButton发送投票结果。将投 App Inventor 编程实例及指南 - 292 -本文档使用 看云 构建 票结果保存到web数据库中,以便其他人也能看到它,我们将编写如图22-4所示的事件处理程序 VoteButton.Click。 图 22-4 用VoteButton.Click事件处理程序将投票结果保存到数据库中图 22-4 用VoteButton.Click事件处理程序将投票结果保存到数据库中 用于识别数据的标签是用户的email322555现场开奖,香港马会开奖结果直播,之前已经被保存到变量myEmail中(稍后将看到),而要保存的 值是用户在VoteTextBox输入的内容。因此,如果用户的email322555现场开奖,香港马会开奖结果直播是“wolber@gmail.com”,而他的 投票是“Obama”,则作为整体存入数据库的信息如表22-2所示。 表22-2 记录在数据库中的标签(tag)及值(value)表22-2 记录在数据库中的标签(tag)及值(value) | tag | value | | --- | --- | | wolber@gmail.com | Obama | TinyWebDB.StoreValue块将这个tag-value对发送到位于http://appinvtinywebdb.appspot.com的web 数据库服务器中。由于这里用的是默认的服务,会显示来自于各种应用的很多数据,因此在第一个显示窗 口中,有可能看到,也有可能看不到你的数据。如果看不到,可以用页面上的GetValue链接用特定标签来 搜索数据。  测试:用TinyWebDB编程时,使用数据库服务器的web接口来测试是否按要求被保存起 App Inventor 编程实例及指南 - 293 -本文档使用 看云 构建 来。 用TinyWebDB来请求并处理数据用TinyWebDB来请求并处理数据 用TinyWebDB提取数据要比TinyDB复杂得多。由于TinyDB的GetValue操作是直接与Android设备上的数 据库通信,因而可以立即获得返回值,但使用TinyWebDB的应用则需要跨越网络来请求数据,因此需要分 两步来实现。 首先使用TinyWebDB的GetValue请求数据,稍后再来处理TinyWebDB.GotValue事件处理程序。实际 上,TinyWebDB.GetValue应该叫做“RequestValue(请求值)”,因为他只是向web数据库发出请 求,而请求实际上并不能立即“get(得到)”一个值。为了更清楚地了解二者之间的差别,可以对比图 22-5中的TinyDB.GetValue与图22-6中的TinyWebDB.GetValue。 图 22-5 TinyDB.GetValue块图 22-5 TinyDB.GetValue块 图 22-6 TinyWebDB.GetValue块图 22-6 TinyWebDB.GetValue块 TinyDB.GetValue块立即得到返回值,因此该块的左侧有一个插头以便可以将返回值保存到一个变量或属 性中;而TinyWebDB.GetValue块不能立即得到返回值,因此左侧没有插头。 对TinyWebDB而言,当web数据库实现了请求并将数据返回给设备时,将触发TinyWebDB.GotValue事 件。因此整个提取数据过程分为两步,首先在一个地方调用TinyWebDB.GetValue,然后再编写 TinyWebDB.GotValue事件处理程序,来处理实际接收到的数据。像TinyWebDB.GotValue这样的程序有 时被称作回调过程,因为实际上是某些外部实体(这里是web数据库)在处理完你的请求之后,反过来调 用你的程序。就像在一家繁忙的咖啡店点餐一样:你点餐,然后等待咖啡师喊你的名字,你才能真正拿到 你的饮料。在同一时间,咖啡师会按顺序从每个人手里收取点餐单(而且所有人都在等待自己的名字被喊 到)。 GetValue-GotValue连动GetValue-GotValue连动 在我们的例子中,需要保存并提取一个投票者的列表,并最终显示所有人的投票结果。 最简单的方案是在应用启动时,在Screen.Initialize事件中发出请求来提取列表数据。如图22-7所示(在本 例中,用“voterlist”为标签向数据库发出请求。) App Inventor 编程实例及指南 - 294 -本文档使用 看云 构建 图 22-7 在Screen.Initialize事件中请求数据图 22-7 在Screen.Initialize事件中请求数据 当应用从数据库收到投票者列表的数据时,TinyWebDB.GotValue事件被触发,图22-8显示了处理这个返 回列表的块。 图 22-8 使用TinyWebDB.GotValue事件处理程序处理返回的列表图 22-8 使用TinyWebDB.GotValue事件处理程序处理返回的列表 程序GotValue附带了参数valueFromWebDB,其中保存着向数据库请求的数据。像valueFromWebDB这 样的事件附带的参数,只在该事件处理程序范围内有效(隶属于该事件处理程序),因此无法在其他事件 处理程序中引用该参数。 这一点看似有些费解,但一旦你熟悉了这些保存局部数据的参数,你自然会联想到那些适用范围更大的数 据(在整个应用中随处可用):变量。理解了这一点,也就理解了GotValue中的关键一步:将返回的数据 valueFromWebDB转移到一个变量中。这里是将数据转移到变量voterList中,之后可以在其他的事件处理 程序中使用该变量。 通常会在GotValue中同时使用if块,原因是,如果数据库中不存在被请求的数据,则返回值为空文本 (“”),通常这种情况发生在第一次启动应用时。通过检查valueFromWebDB是否为列表,可以确定是 否真的有数据返回。如果valueFromWebDB为空(if的测试结果为假),就不必将其写入变量voterList。 无论是TinyDB还是TinyWebDB,都是以相同的方式来获取数据、检查数据及设置数据(到变量中),不 同的是,这里预期会收到一个列表,因此测试环节上略有差别。 更为复杂的GetValue/GotValue举例更为复杂的GetValue/GotValue举例 在相对简单的应用中,图22-8中所示的代码是一种不错的提取数据的方式,但在投票的例子中,我们需要 更为复杂的逻辑。说明如下: 应用启动时,程序会提示用户输入Email322555现场开奖,香港马会开奖结果直播。可以使用Notifier组件弹出窗口来实现这一功能。 (Notifier在组件设计器组件面板的User Interface中。)用户输入email后,将其保存为变量; 检查完用户的email之后,调用GetValue来提取投票人列表。你能说出为什么吗? 图22-9显示了向数据库请求数据的更为复杂的方案。 App Inventor 编程实例及指南 - 295 -本文档使用 看云 构建 图 22-9 在这个更为复杂的方案里,在获得用户的email之后调用GetValue图 22-9 在这个更为复杂的方案里,在获得用户的email之后调用GetValue 在应用启动时(Screen1.Initialize),Notifier组件提示用户输入他的email322555现场开奖,香港马会开奖结果直播;用户输入后 (Notifier.AfterTextInput),输入的信息保存到变量中,同时用label显示出来,然后调用GetValue来获 得投票人列表。需要注意,这里没有在Screen1.Initialize中直接调用GetValue,因为需要首先设置用户的 Email322555现场开奖,香港马会开奖结果直播。 因此当应用初始化完成后,用这些块来提示用户的Email322555现场开奖,香港马会开奖结果直播,然后以“voterlist”为标签调用 GetValue。当从web上返回列表时,GotValue被触发,以下是后续功能的描述: GotValue将检查到达的数据是否不为空(有人已经使用这个应用,并建立了投票人列表)。如果返回 值中包含数据(投票人列表),则检查此用户的email是否已经在投票人列表中,如果没有,将其添加 至列表,并将更新后的列表保存到数据库; 如果数据库中没有投票人列表,我们将以此用户的email作为唯一的项来创建列表。 图22-10中显示了这一功能所需的块。 App Inventor 编程实例及指南 - 296 -本文档使用 看云 构建 图 22-10 使用GotValue块处理数据库返回的数据,根据不同的返回结果确定要执行的操作图 22-10 使用GotValue块处理数据库返回的数据,根据不同的返回结果确定要执行的操作 在这些块中,第一个if通过调用“is a list?”来检测从数据库返回的值,判断其是否不为空。如果不为 空,返回的数据放入变量voterList中。切记,voterList中只有每个使用过该应用的用户的Email322555现场开奖,香港马会开奖结果直播,但 我们不确定当前用户是否也在此列表中,因此需要检查一下:如果此用户不在列表中,则用“add item to list”块将其添加至列表,并将更新后的列表保存到web数据库。 如果数据库返回的结果不是列表,则执行ifelse块中的“else”分支;这说明还没有人使用过这个应用。此 时需要创建一个新的列表voterList,将当前用户的Email322555现场开奖,香港马会开奖结果直播作为列表的第一项,然后将这个只有一项的列 表保存到web数据库中(同时也希望更多人的加入!)。 用不同的标签请求数据用不同的标签请求数据 到目前为止,投票应用值处理了一个用户列表,每个用户都可以看到其他用户的Email322555现场开奖,香港马会开奖结果直播,但还不能提取 并显示每个用户的投票结果。 此前设定在VoteButton的Click事件中,将用户的Email322555现场开奖,香港马会开奖结果直播与投票结果以“email322555现场开奖,香港马会开奖结果直播:投票结果”的方 式组成tag-value对提交给web数据库。此时如果已经有两个人投票,那么相应的数据库实体中将包含表 22-3中的数据。 表22-3 存储在数据库中的tag-value对表22-3 存储在数据库中的tag-value对 tagtag valuevalue voterlist [wolver@gmail.com,joe@gmail.com] wolber@gmail.com Obama App Inventor 编程实例及指南 - 297 -本文档使用 看云 构建 joe@gmail.com McCain tagtag valuevalue 当用户点击“ViewVotes”按钮时,应用将从数据库中提取所有投票结果并加以显示。现在假设投票人列 表已经提取并保存到变量voterList中,我们可以使用foreach来请求列表中每个人的投票结果,如图22-11 所示。 图 22-11 使用foreach块请求列表中每位成员的投票结果图 22-11 使用foreach块请求列表中每位成员的投票结果 这里对变量currentVotesList进行初始化,来清空列表,目的是为了将最新从数据库中获得的投票结果添 加到列表中。在foreach中使用TinyWebDB.GetValue来处理列表中的每一个Email322555现场开奖,香港马会开奖结果直播:以Email322555现场开奖,香港马会开奖结果直播 (voterEmail)为标签向数据库发送请求。需要注意的是,要等到一系列的请求数据返回时触发GotValue 事件,才能将投票结果添加到currentVotesList中。 在TinyWebDB.GotValue中处理多标签在TinyWebDB.GotValue中处理多标签 我们希望在应用中显示投票结果,事情变得更加复杂了。在点击ViewVotesButton按钮发出请求之后,在 TinyWebDB.GotValue中将收到以每个Email322555现场开奖,香港马会开奖结果直播为标签(tag)的数据,就像“voterlist”标签用于提取 用户Email322555现场开奖,香港马会开奖结果直播列表一样。当应用同时向数据库为不同标签请求多余一项的数据时,就需要在 TinyWebDB.GotValue中编写代码来处理所有可能的请求。(你可能想到编写多个GotValue事件处理程 序,来分别处理每个请求——知道为什么这样做行不通吗?) 为了处理这种复杂的情况,GotValue事件处理程序可以利用自带的参数tagFromWebDB,它会告诉你当 前的返回值来自于哪一个请求。因此,如果标签是“voterlist”,我们可以像之前那样进行处理;如果不 是“voterlist”,我们可以假设它是用户列表中某人的Email322555现场开奖,香港马会开奖结果直播,来源于ViewVotesButton.Click事件处 理程序中发出的请求。当这些请求返回时,我们希望将返回的数据——投票人及投票结果——添加到列表 currentVotesList中,以便于向用户显示。 图22-12中显示了整个TinyWebDB1.GotValue事件处理程序。 App Inventor 编程实例及指南 - 298 -本文档使用 看云 构建 图 22-12 TinyWebDB1.GotValue事件处理程序图 22-12 TinyWebDB1.GotValue事件处理程序 设置Web数据库设置Web数据库 本章前面提到过,设立于http://appinvtinywebdb.appspot.com的默认web数据库仅供原型设计以及应 用的测试,在向真正的用户发布应用之前,需要为应用创建一个专用的数据库。 访问网站http://appinventorapi.com/program-an-api-python/,按照上面的说明就可以创建web数据 库。该网站由本书的作者之一Wolber教授创建,网站提供了示例程序以及设置App Inventor web数据库 及API(应用程序接口)的说明。按照说明,你可以下载相关的程序,并且只要对配置文件进行少量修改,就 可以使用这些程序。经过设置的代码与之前使用的App Inventor默认数据库相同,它运行在Google的应 用引擎上——一个云计算服务,运行在Google服务器上免费的web数据库。这样,你就建起了属于自己的 web数据库(与App Inventor的协议兼容),几分钟就可以运行起来,并用它来创建web移动应用。 一旦创建并部署了属于自己的web数据库(因为只有你知道它的URL322555现场开奖,香港马会开奖结果直播),你就可以用它来创建应用。 不过还需要在应用中修改TinyWebDB组件的ServiceURL属性,以便组件可以用新的定制数据库来保存及 提取数据。图22-13描述了如何操作。 App Inventor 编程实例及指南 - 299 -本文档使用 看云 构建 图 22-13 将ServiceURL属性修改为你的定制数据库的URL322555现场开奖,香港马会开奖结果直播图 22-13 将ServiceURL属性修改为你的定制数据库的URL322555现场开奖,香港马会开奖结果直播 在这个例子中,ServiceURL被设置为http://usfwebservice.appspot.com,是本书的作者之一为他的学生 们创建的一个web数据库(图22-13中"appsport.com"后面的部分被输入框遮挡住了)。设定了 ServiceURL之后,所有的TinyWebDB.StoreValue及TinyWebDB.GetValue的调用都将执行这个特定的 URL。 小结小结 通过TinyDB及TinyWebDB组件,App Inventor可以很容易地实现数据的永久存储。数据以标签-值(tag- value)对的方式存储,保存数据时使用的标签也用于之后对数据的提取。TinyDB用于将数据直接保存在 设备上;当数据需要在香港老钱庄868525,(六合娃娃之间分享时(如多人游戏或投票应用),就需要使用TinyWebDB。 TinyWebDB更为复杂,尤其在获取数据的环节,除了用GetValue来请求数据,还要设置回调过程,即 GotValue事件处理程序,同时还要设置web数据库服务。 一旦你可以得心应手地使用数据库——尤其是掌握了获取、检查及设置数据的要点,要不了多久,你就能 创建更为复杂的应用了。 App Inventor 编程实例及指南 - 300 -本文档使用 看云 构建 第 23 章 传感器 将你的香港老钱庄868525,(六合娃娃指向天空,谷歌星空地图会显示出你正在观看的星群;倾斜香港老钱庄868525,(六合娃娃,可以控制你的游戏;带着你 的香港老钱庄868525,(六合娃娃去散步,一款“面包渣儿”应用将记录下你的途经的路线。所有这些应用之所以能够实现,都是因 为你所携带的移动设备装备了高科技的传感器,可以探测到位置、方向以及加速度。 本章将再次讨论App Inventor的位置传感器、方向传感器以及加速度传感器等组件,其中将学习全球定位 系统(GPS)、方向测量(如倾斜、旋转及摇晃)以及与处理加速度读数相关的数学知识。 创建位置感知应用创建位置感知应用 在智能香港老钱庄868525,(六合娃娃流行之前,计算仅限于桌面电脑。虽然便携式电脑算是移动设备,但与我们今天随身携带的微 型设备相比,不可同日而语。计算已经摆脱了实验室及办公室,在地球上随时随地都在发生。 对计算的普遍性产生深刻影响的是一项新的、有趣的数据,它存在于上述的所有应用中,即:当前的位置 信息。当人们在世界各地游走时掌握他们的行踪,这件事影响深远,它既有可能对我们的生活产生极大的 帮助,但同时也存在侵犯隐私及损害人权的可能。 在“安卓,我的车在哪”的应用中(第7章)就是一个有益的位置感知应用的例子,让我们可以记住之前的 地点,以便稍后还能找回来。这是一个个人应用——位置信息就保存在自己的香港老钱庄868525,(六合娃娃数据库中。 同样的理念也适用于群组。例如,一个徒步旅行者小组可能希望在荒野中查看每个组员的去向,或者一个 商务团队可能希望在一个大型会议上寻找自己的伙伴。这类应用已经出现在市场上,两个典型的应用就 App Inventor 编程实例及指南 - 301 -本文档使用 看云 构建 是“谷歌纵横(Latitude)”(www.google.com/latitude)以及Facebook的“签到 (Place)”(www.facebook.com/places)。由于公众对隐私的担忧,这些应用一经面世便备受争议。 另一类位置感知应用使用了增强现实工具。这类应用利用位置及香港老钱庄868525,(六合娃娃的方向,在自然信息基础上,提供增 强的叠加信息。因此当你用香港老钱庄868525,(六合娃娃指向一栋建筑物时,你会看到它在房地产市场上的价格,或者你在植物园 中欣赏异国花卉时,某个应用会告诉你这株植物的品种。这类应用的早期产品包括世界浏览器(Wikitude ——一款增强现实的实景地图导航应用)、香港老钱庄868525,(六合娃娃实景浏览器(Layar——第一款香港老钱庄868525,(六合娃娃版的增强现实浏览 器)以及谷歌星空地图。 世界浏览器甚至可以让用户通过网站http://wikitude.me在移动云上添加数据。在网站上,选定地图并标 注上你的个人信息,稍后,当你或其他用户在这个位置使用该移动应用时,你发布的信息就会显示出来。 GPSGPS 创建一个位置感知应用,首先需要了解全球定位系统(GPS)的工作原理。GPS数据来自美国政府所保有 的卫星系统,只要在视野开阔地带,至少能看到三颗卫星,你的香港老钱庄868525,(六合娃娃就能获得读数。一份GPS读数包括位 置的纬度、经度及海拔高度。纬度表示与赤道的距离,赤道以北为正值,以南为负值,范围从-90至90.如 23-1显示了厄瓜多尔基多附近的谷歌地图,图中的纬度为-0.01,表示在赤道偏南一点点。 图 23-1 位于赤道上的厄瓜多尔首都基多图 23-1 位于赤道上的厄瓜多尔首都基多 经度是距离本初子午线(零度经线)向东或向西偏离的距离,向东为正值,西为负值,零度经线穿过的最 知名的地点就是格林威治,伦敦附近的一座小镇,皇家天文台的所在地。图23-2中的地图标出了格林威 治,它的经度为0.0。 App Inventor 编程实例及指南 - 302 -本文档使用 看云 构建 图 23-2 格林威治的皇家天文台沿本初子午线射出一道光柱图 23-2 格林威治的皇家天文台沿本初子午线射出一道光柱 经度值从-180到180,图23-3显示了俄罗斯境内的一点,非常靠近阿拉斯加,它的经度为180.0,这个点可 以理解为以格林威治(经度为0.0)为起点绕地球半圈所到达的位置。 图 23-3 在俄罗斯与阿拉斯加边境附近的一点,经度为180图 23-3 在俄罗斯与阿拉斯加边境附近的一点,经度为180 用App Inventor感知位置用App Inventor感知位置 App Inventor为访问GPS信息提供了LocationSensor(位置传感器)组件,该组件具有Latitude(纬 度)、Longitude(经度)及Altitude(海拔高度)三个属性,此外它可以与谷歌地图通信,因此还可以获 得当前街道322555现场开奖,香港马会开奖结果直播的信息。 图23-4中的LocationSensor. LocationChanged是位置传感器组件LocationSensor最关键的事件处理程 序。 App Inventor 编程实例及指南 - 303 -本文档使用 看云 构建 图 23-4 LocationSensor1.LocationChanged事件处理程序图 23-4 LocationSensor1.LocationChanged事件处理程序 两种情况可以触发LocationChanged事件:传感器第一次收到读数时,以及当位置发生一定变化后收到新 的读数时。其中第一次读数通常会延迟几秒钟,有时也会一直没有读数。例如,如果你在室内而且没有连 接WiFi,设备将无法获得读数。香港老钱庄868525,(六合娃娃中也有相关的设置,可以为了省电而关闭了GPS,这是无法获得读数 的另一个可能的原因。除了这些原因,在LocationSensor.LocationChanged事件被触发之前,不能排除 LocationSensor做了不合理的属性设置。 处理这种无法感知位置的情况,有一个方法是创建一个变量lastKnownLocation,并将其初始化为“未 知”,然后让LocationSensor.LocationChanged事件处理程序来修改变量的值,如图23-5所示。 图 23-5 变量lastKnownLocation的值会随位置的改变而改变图 23-5 变量lastKnownLocation的值会随位置的改变而改变 通过编写以上事件处理程序,在第一次获得读数之前显示“未知”,这样就可以始终显示当前位置,或将 位置信息保存到数据库中。这一策略在第4章“开车不发短信”中使用过,即,在自动回复的短信中加入位 置信息:“未知”或最后一次获得的读数。 也可以使用LocationSensor.HasLongitudeLatitude块,直接询问传感器是否具有读数。如图23-6所示。 App Inventor 编程实例及指南 - 304 -本文档使用 看云 构建 图 23-6 用HasLongitudeLatitude块测试传感器是否具有读数图 23-6 用HasLongitudeLatitude块测试传感器是否具有读数 检查边界检查边界 事件LocationChanged的一种通常的用法是检查设备是否在某个边界之内,或在某个设定区域内。例如, 看图23-7中的代码,每次当传感器获得的读数显示某人离开零度经线的距离超过0.1度时,让香港老钱庄868525,(六合娃娃产生震 动。 图 23-7 如果读书远离了零度经线,则香港老钱庄868525,(六合娃娃发出震动图 23-7 如果读书远离了零度经线,则香港老钱庄868525,(六合娃娃发出震动 这种边界检查功能可以有很多应用,例如对于假释犯,如果他们离开家的距离接近规定的合法距离时,应 用会发出警告;或者对教师及家长来说,可以监控孩子是否离开了操场。如果你想看到更为复杂的例子, 参见第18章中关于条件块的讨论。 位置信息的来源:GPS, WiFi以及基站编码位置信息的来源:GPS, WiFi以及基站编码 有几种方法可以确定Android设备的位置,最精确的方法是通过卫星,美国政府维护的组成GPS系统的卫 星,可精确到数米。但是如果在室内,并有高楼或其他物体遮挡,则无法获得读数。需要在开阔地区并且 系统中至少要有三颗卫星。 如果无法使用GPS,或者用户的设备禁用了这一功能,也可以通过无线网络获得位置信息。设备需要在 WiFi路由器附近,当然,你获得的经纬度读数是这台WiFi设备的位置信息。 判断设备位置的第三种方式是通过移动网络的基站编码(Cell ID),基站编码对香港老钱庄868525,(六合娃娃位置的判断来源于香港老钱庄868525,(六合娃娃 与附近基站之间通信信号的强弱,这种方式通常不够精确,除非你周围有很多个基站。不过这种方式与 GPS或WiFi连接相比,是最省电的。 使用方向传感器使用方向传感器 游戏中会用到方向传感器(OrientationSensor),用户通过倾斜设备来控制物体的运动。方向传感器也可 以用作指南针,确定香港老钱庄868525,(六合娃娃所指的方向。 方向传感器有五个属性,除了航空工程师外,大多数人都不熟悉这些参数: 滚动参数Roll(左-右):当设备水平时,Roll的值为0°,当设备向左倾斜时,增加到90°,而当设备向右倾 斜时,减少到-90°。 App Inventor 编程实例及指南 - 305 -本文档使用 看云 构建 倾斜参数Pitch(前-后):当设备水平时,Pitch为0°,当设备头朝下时,Pitch值增加到90°,当设备翻转 至面朝下时,增加到180°。同样,当设备的下端朝下时,Pitch值减小到-90°,当继续翻转至面朝下 时,Pitch值为-180°。 方位角参数Azimuth(指南针):当设备顶端指向正北时,Azimuth的值为0°;指正东时,值为90°;指正 南时,值为180°;指正西时,值为270°。 强度参数Magnitude(滚动球的速度):Magnitude参数的返回值在0-1之间,表示设备的倾斜程度。它 的值表示在设备表面滚动的球体所能施加的力的大小。 角度参数Angle(滚动球的角度):Angle返回设备倾斜的方向。即,在设备表面滚动的球体所能施加的力 的方向。 方向传感器同样提供了方向变化事件,每次当方向发生变化时,会触发该事件。为了进一步探索这些属性 的意义,写一个应用来描述这些属性如何随设备的倾斜而变化。在用户界面中添加五个方向label,另外五 个标签用于显示前面所述的属性当前的值。如图23-8所示添加相关的块。 图 23-8 显示方向传感器数据的代码块图 23-8 显示方向传感器数据的代码块 使用滚动参数Roll使用滚动参数Roll 现在通过用户对设备的倾斜,来实现图像在屏幕上的左右移动,就像在射击或赛车类游戏中那样。拖入一 个Canvas组件,宽度设为“Fill parent”,高度为200像素。然后向Canvas上添加一个ImageSprite组 件,并在Canvas下方添加一个名为RollLabel的Label,来显示Roll属性值。如图23-9所示。 App Inventor 编程实例及指南 - 306 -本文档使用 看云 构建 图 23-9 滚动操作如何控制图像移动的用户界面图 23-9 滚动操作如何控制图像移动的用户界面 方向传感器OrientationSensor的Roll属性表示香港老钱庄868525,(六合娃娃的倾斜方向:向左或向右(即,如果你正握香港老钱庄868525,(六合娃娃并稍向 左倾斜,获得的读数为正值;反之向右倾斜则为负值)。因此,利用图23-10中的事件处理程序,用户可 以实现对运动的控制。 图 23-10 利用OrientionChanged事件来响应Roll属性的变化图 23-10 利用OrientionChanged事件来响应Roll属性的变化 图中的乘法块让roll属性乘以-1,因为向左倾斜时,roll的值为正,但我们希望物体向左移动(因此x坐标的 值变小)。要了解动画应用中的坐标系统的工作原理,参见第17章。 需要注意的是,这段程序是针对纵向模式(正握香港老钱庄868525,(六合娃娃时)编写的,而非横向。事实上,当你过度地倾斜手 机时,屏幕会自动转成横向模式,而图像则被卡在屏幕的左边。这是因为当设备向一侧倾斜时,如向左倾 斜时,获得的roll属性的读数一直是正值,因此图像的x坐标值也一直在变小,如图23-10所示。 如果App Inventor提供了解决上述问题的方法,应该是(1)在香港老钱庄868525,(六合娃娃上取消屏幕的自动旋转功能;或者 (2)区分香港老钱庄868525,(六合娃娃的纵横模式,针对不同模式给出不同的物体运动公式。App Inventor未来会提供这样的支 持,但现在你还需要向用户说明应用的运行方式。 控制运动的方向及速度控制运动的方向及速度 前面的例子中图像可以左右移动,如果想实现任意方向的运动,可以使用OrientationSensor的Angle(角 度)及Magnitude(强度)属性,这正是第5章的游戏中让瓢虫移动的属性。 在图23-11中的块是一个测试程序,用户可以通过倾斜设备来实现任意方向的运动(需要两个Label及一个 ImageSprite). App Inventor 编程实例及指南 - 307 -本文档使用 看云 构建 图 23-11 用角度和强度来实现移动图 23-11 用角度和强度来实现移动 试试看,强度属性的值介于0至1之间,代表设备的倾斜程度,在这段测试程序蒸南瓜,倾斜的程度越大, 图像移动的越快。 香港老钱庄868525,(六合娃娃用作指南针香港老钱庄868525,(六合娃娃用作指南针 指南针应用,以及像谷歌星空这样的应用,需要知道香港老钱庄868525,(六合娃娃所指的方向(东南西北),谷歌星空就是根据手 机的指向,将方向信息叠加在星座信息上。 属性Azimuth可以用于表示方向。Azimuth的取值介于0°至360°之间:正北为0,正东为90,正南为180, 正西为270。因此当Azimuth值为45时,意味着香港老钱庄868525,(六合娃娃指向东北,135时指向东南,225时指向西南,315时 指向西北。 图23-12中的块创建了一个简易的指南针,可以用文字显示香港老钱庄868525,(六合娃娃所指的方向(如西北)。 图 23-12 编程实现一个简易的指南针图 23-12 编程实现一个简易的指南针 你会发现,程序只能显示四个方向之中的一个:东南、东北、西南、西北。你可以挑战一下自己,看能否 修改程序,当香港老钱庄868525,(六合娃娃的指向在某个范围内时,显示四个正方向(正北、正南、正东、正西)。 App Inventor 编程实例及指南 - 308 -本文档使用 看云 构建 加速度传感器加速度传感器 加速度是速度随时间的变化率,如果你踩下油门,车会加速——车速会以一定的比率增加。 在Android香港老钱庄868525,(六合娃娃中内置了加速度计,用于测量加速度,但测量的参照系不是静止的香港老钱庄868525,(六合娃娃,而是自由下落中 的香港老钱庄868525,(六合娃娃:如果你让香港老钱庄868525,(六合娃娃下落,它所记录的加速度读数为0。一句话,读数与重力有关。 如果感兴趣相关的物理知识,可以去查阅相关的书籍,但本小节中,我们将充分讨论加速度计,为你建立 一个良好的开端,并仔细分析一个能够拯救生命的应用。 响应设备的摇晃响应设备的摇晃 如果学习过第1章(Hello猫咪),那么你已经使用过加速度传感器了:使用Accelerometer.Shaking事 件,当香港老钱庄868525,(六合娃娃摇晃时,设备发出猫叫声。如图23-13所示的块。 图 23-13 香港老钱庄868525,(六合娃娃摇晃时发出声音图 23-13 香港老钱庄868525,(六合娃娃摇晃时发出声音 使用加速度传感器的读数使用加速度传感器的读数 像其他传感器一样,加速度计也具备侦测读数变化的事件: AccelerometerSensor.AccelerationChanged,这个事件有三个参数,对应加速度在三个维度上的分量: xAccel:当设备向右倾斜时,其值为正(即,左侧的边缘在上升);当设备向左倾斜时,其值为负(设备 右侧边缘上升)。 yAccel:当设备的底部上升时,其值为正;当设备顶部上升时,其值为负。 zAccel:设备的显示屏朝上时,其值为正;显示屏朝下时,其值为负。 检测自由落体检测自由落体 我们知道,如果加速度的读数为0,那么设备一定在做自由落体运动,基于这一认识,我们可以在 AccelerometerSensor.AccelerationChanged事件中,通过检测读数来模拟自由落体事件。这些代码经过 反复测试,可以用于老年人的自动求救:一旦侦测到发生跌倒,就会自动向外发送短信。 图23-14中显示了这个应用使用的块,当发生自由落体运动时,给出一个简单的报告(用户可以点击“重 置”按钮进行再次检测)。 App Inventor 编程实例及指南 - 309 -本文档使用 看云 构建 图 23-14 当自由落体发生时进行报告图 23-14 当自由落体发生时进行报告 每当传感器获得读数,这些块都要在x、y、z三个维度上进行检查,看是否这些值接近于0(即它们的绝对 值小于1)。如果三者都接近于0,应用将改变label的属性,来表示设备正处于自由落体状态。当用户点 击“重置”按钮时,显示状态的label又被重新设为初始值(“没发生跌落事件”)。 如果你想试用这个应用,可以从这里下载:http://examples.oreilly.com/0636920016632。 用校准值测定加速度用校准值测定加速度 加速度传感器的读数用自由落体时的状态进行校准。如果你想测量设备平放在桌上时的加速度的相对值, 则必须与标准读数进行校准。校准的意思是与标准值进行核对、判定或检测;在本例中,标准值就是将设 备平放在桌上时的读数。 校准需要用户将设备平放在桌面上,然后点击“校准”按钮,这时 应用将读出平面上的加速度值,这些值 会在稍后的AccelerationChanged事件中用来判断新读数的偏差,并显示设备是否在某个方向上进行了快 速的移动。 图23-15中显示了一个样板应用,让用户校准读数并测试加速度。 App Inventor 编程实例及指南 - 310 -本文档使用 看云 构建 图 23-15 校准加速度的读数图 23-15 校准加速度的读数 可以在此下载并安装这一应用:http://examples.oreilly.com/0636920016632/。运行应用,并将香港老钱庄868525,(六合娃娃放 在桌上,点击校准按钮,将显示“在平台上的读数”,此时如果缓慢地拿起香港老钱庄868525,(六合娃娃,“显著变化”区域的读 数不会变化(显示“无”);但如果你快速提起香港老钱庄868525,(六合娃娃,则Z-变化将由“无”变为“有”,如图23-15所 示。同样,如果快速沿桌面移动香港老钱庄868525,(六合娃娃,则X或Y也会有显著加速。在图23-16中显示了设置校准初始值的 块。 图 23-16 校准程序的初始设置图 23-16 校准程序的初始设置 这些块从加速度传感器中获取读数,并显示在三个label中:XCalibLabel、YCalibLabel及ZCalibLabel, 并初始化另外三个显示加速度变化结果的label。 当香港老钱庄868525,(六合娃娃水平放置时,加速度计的zAccel读数大约为9.8,而xAccel及yAccel读数约等于0,这些值的偏差表 明了加速度计的精确度。获得了基准读数之后,可以通过比较新的测量值与基准值之间的偏差,侦测到手 机在x、y或z方向的加速度变化(这种方法与第18章中的边界检测程序相类似)。图23-17显示了这一方法 的具体实现。 App Inventor 编程实例及指南 - 311 -本文档使用 看云 构建 图 23-17 用基准值来侦测加速度变化图 23-17 用基准值来侦测加速度变化 当设备移动时,将触发这段程序。通过测量新的加速度值,并与静止时的基准值进行比较,从而判断加速 度之是否产生了显著变化。假设ZCalib.Text记录的基准值为9.0,此时如果缓慢地拿起香港老钱庄868525,(六合娃娃,那么新的读数 将保持在9左右,并且不会报告有显著变化;但如果是快速地拿起香港老钱庄868525,(六合娃娃,则读数会明显增大,此时程序将报 告加速度“有”显著变化。 小结小结 传感器是移动应用中最富魅力的部分,因为它们实现了用户与环境之间实实在在的交互。无论是用户体 验,还是应用开发,移动计算为我们带来了无限的商机。不过依然要精心地构思一个应用,来决定何时、 何地以及如何使用这些传感器。很多人会担心隐私问题,如果应用中涉及到个人的敏感信息,他们可能会 放弃使用。尽管如此,在游戏、社交网络、旅行以及其他众多的选项中,仍然有无限多种可能开发出有积 极意义的应用来。 App Inventor 编程实例及指南 - 312 -本文档使用 看云 构建 第 24 章 与Web API通信 移动技术再加上无所不在的网络,已经完全改变了我们生活的这个世界。如今坐在公园里就可以打理你的 银行账户,或者在亚马逊书店搜索你正在阅读的图书的评论,或者查阅Twitter,看看世界上其他公园里的 人们都在想些什么。香港老钱庄868525,(六合娃娃只能打今晚六彩现场开奖结果发短信的时代已经过去,它可以让你随时随地访问世界各地的数据。 虽然用香港老钱庄868525,(六合娃娃浏览器可以访问互联网,但由于屏幕太小,而且速度受到限制,因此使用者会感觉不适。如果 能够定制应用,有针对性地从网络上提取少部分信息,以适应香港老钱庄868525,(六合娃娃终端的特点,就可以获比浏览器得更具 吸引力的替代方案。 本章我们将领略从网络获取信息的各类应用,首先创建一个显示游戏排行榜的(图表)应用,然后以 Yahoo财经频道的股票数据为例,讨论如何使用TinyWebDB从网上获取任意类型的信息(不只是图像), 最后讨论如何创建属于自己的网络信息源,以用于App Inventor应用。 创新就是对这个世界的重组,以一种新奇的方式将旧的观念和内容组合在一起。埃米纳姆(Eminem,美 国说唱歌手)的单曲Slim Shady追随了AC/DC(最著名的澳大利亚摇滚乐队)与Vanilla Ice(美国白人说 唱歌手)的风格,并使这种混搭的音乐风行一时。这一类的“模仿”非常普遍,以至于许多艺术家,包括 Girl Talk(专攻混搭及数字音乐的美国音乐家)及Negativland(来自美国加州的一个实验音乐乐队), 都致力于将旧的内容融入某种新的风格。 无独有偶,在网络及移动世界中,网站及应用混合了来自各种渠道的数据及内容,而且很多网站在设计理 念上遵循了互联互通原则(interoperability)。一个典型的混搭网站的例子就是Housing Maps(http://www.housingmaps.com),如图24-1,它从网站 Craigslist(http://www.craigslist.org)上采集房屋租赁信息,并与谷歌地图API结合起来,提供一种新型 的信息服务。 App Inventor 编程实例及指南 - 313 -本文档使用 看云 构建 图 24-1 住房地图(Housing Maps)应用将Craigslist的房屋信息与谷歌地图信息叠加起来图 24-1 住房地图(Housing Maps)应用将Craigslist的房屋信息与谷歌地图信息叠加起来 谷歌地图不仅仅是可供访问的网站,同时也提供相应的应用程序接口服务(web service API),这使 得“住房地图”这类混搭应用成为可能。我们普通人只能通过浏览器访问http://maps.google.com来查看 地图,但像“住房地图”这样的应用可以访问谷歌地图API来实现机器与机器之间的通信。混搭应用处理并 组合来自不同站点(如Craislist及Google Maps)的数据,并将它们以一种更有意义的方式呈现出来。 现在,几乎所有流行的网站都提供这种备选方案:机器对机器的访问。提供数据的一方称为网络服务 (web service),而客户端应用与网络服务之间的通信协议则称为应用程序接口,或API。事实上,术语 API已经成为网络服务(web service)的代名词。 亚马逊网络服务(Amazon Web Service,即AWS)是最早的网络服务之一,由于亚马逊公司向第三方应 用开放了它的业务数据,最终导致图书销量的增加。同样,当2007年Facebook发布了它的API时,也吸引 了无数人的眼球。Facebook的数据不同于图书广告,那么为什么它甘愿让其他应用“偷走”它的数据,同 时也可能拉走它的用户呢(还有广告收入!)?事实上,开放把facebook从一个网站变成了一个平台,这 意味着像快乐农场这样的第三方程序,也可以运行在这个平台上,并利用平台的部分功能。现在,没有人 能质疑Facebook的成功。到2009年Twitter发布时,API访问已经是意料之中的事情,果然,Twitter也如 此行事。现在,如图24-2所示,大多数的网站都同时提供人机访问接口。 App Inventor 编程实例及指南 - 314 -本文档使用 看云 构建 图 24-2 大多数网站同时具备供人类访问的界面及供客户端应用访问的API图 24-2 大多数网站同时具备供人类访问的界面及供客户端应用访问的API 对于我们普通人来说,网络就是一个可供访问的为数众多的网站,而对于程序员来说,它却是一个世界上 最大也最丰富的信息数据库。在网络世界里,机器对机器的通信量正在超过人机之间的通信量。 访问生成图像的网络API访问生成图像的网络API 提示:谷歌图表API现已废弃。在本例中仍可使用它,但总有一天将不可用。尽管如此,本例仍不失为解释 URL(/pdf/链接322555现场开奖,香港马会开奖结果直播/index.html)即其参数的好例子。【原作者给出的网址确已废弃,译者给出了新的网址,现在可用。】 正如在第13章(亚马逊掌上书店)中所见,大多数API都会接受以URL形式发来的数据请求,并会返回数据 (通常以标准格式返回数据,如XML[Extensible Markup Language,扩展的标记语言]、 JSON[JavaScript Object,JavaScript对象表示法])。可以使用TinyWebDB组件与这些API进行通信,本 章稍后将详细讨论这一重点话题。 不过也有些API返回的结果不是数据,而是图像。本节将讨论如何与生成图像的API进行通信,来拓展App Inventor的用户界面能力。 谷歌图表API就是这样一类服务。通过在URL322555现场开奖,香港马会开奖结果直播中加入某些数据,向API发出请求,API将返回一个图表, 你的应用负责显示这些图表。该服务可以生成多种图表,包括条状图、饼状图、地图及文氏图(Venn Diagram,用封闭曲线所包围的面积来表示集合及其关系的图形)。谷歌图表API成为网络服务(web service)互联互通原则的一个典范,它的目的在于增强其他网站的能力。由于App Inventor没有提供多少 所谓的可视化组件,因此能够借用谷歌图表这样的API,对App Inventor来说是至关重要的。 首先要理解发给API的URL322555现场开奖,香港马会开奖结果直播的格式。访问谷歌图表API网站(https://google- developers.appspot.com/chart/interactive/docs/gallery),你将看到如图24-3的页面。 App Inventor 编程实例及指南 - 315 -本文档使用 看云 构建 图 24-3 谷歌图表API生成的各类图表图 24-3 谷歌图表API生成的各类图表 网站提供了完整的说明文档及操作向导,可以交互式地创建图表,并探究如何书写URL322555现场开奖,香港马会开奖结果直播。向导非常好 用,可以通过表单来定义各种类型的图标,并能自动生成你需要的URL322555现场开奖,香港马会开奖结果直播,你还可以反过来用自己的数 据验证这个322555现场开奖,香港马会开奖结果直播的有效性。让我们开始吧,访问网站,跟随向导来创建图表,然后仔细分析生成这些图表 的URL322555现场开奖,香港马会开奖结果直播的格式。看下面的例子,在浏览器中输入以下URL322555现场开奖,香港马会开奖结果直播: http://chart.apis.google.com/chart? cht=bvg&chxt=y&chbh=a&chs=300x225&chco=A2C180&chtt=Vertical+bar+chart| (%E5%9E%82%E7%9B%B4%E6%9D%A1%E7%8A%B6%E5%9B%BE)&chd=t:10,50,60,80,40,60,30"> http://chart.apis.google.com/chart? cht=bvg&chxt=y&chbh=a&chs=300x225&chco=A2C180&chtt=Vertical+bar+chart|(垂直条状 App Inventor 编程实例及指南 - 316 -本文档使用 看云 构建 图)&chd=t:10,50,60,80,40,60,30 你将获得图24-4所示的图表。 图 24-4 谷歌图表API根据URL322555现场开奖,香港马会开奖结果直播生成了这个图表图 24-4 谷歌图表API根据URL322555现场开奖,香港马会开奖结果直播生成了这个图表 要想理解之前输入的URL322555现场开奖,香港马会开奖结果直播,就需要了解URL322555现场开奖,香港马会开奖结果直播的作用。你会发现其中包含了问号(?)及and符号(&)。 其中的?标志着第一个参数的出现,而&号将后续的各个参数分隔开。每个参数都由名称、等号及值组成, 因此在上面调用图表API(http://chart.apis.google.com/chart)的例子中,使用了七个参数,其具体内 容如表24-1所示。 表24-1 图表API中使用的带参数的URL322555现场开奖,香港马会开奖结果直播表24-1 图表API中使用的带参数的URL322555现场开奖,香港马会开奖结果直播 参数参数 值值 参数的含义参数的含义 cht bvg 图标的类型为条状图(bar)、垂直的(verbical)、分组的(grouped)。 chxt y 在y轴上显示数字 chbh a 自动设置条的宽度及间隔 chs 300x225 整个图表尺寸(像素值) chco A2C180 图表中条的颜色(16进制表示法) chd t:10,50,60,80,40,60,30 生成图表的数据,简单的文本格式(t) chtt Vertical+bar+chart (%E5%9E%82%E7%9B%B4%E6%9D%A1%E7%8A%B6%E5%9B%BE) 译者提醒:表格中图表标题一项换行符“|”后的内容与浏览器中输入的“(垂直条状图)”不同,这是因为 App Inventor对中文字符进行了编码的缘故。从浏览器322555现场开奖,香港马会开奖结果直播栏中复制完整322555现场开奖,香港马会开奖结果直播,然后粘贴到块编辑器的文 本块中,就会自动将中文字变成表格中的字符。如果你强行在文本块中输入“(垂直条状图)”,最终在应用 测试时,香港老钱庄868525,(六合娃娃上应该显示中文字符的位置会显示“?”。提醒完毕。 通过修改参数,可以生成不同的图形。想了解更多的图表类型,请查阅下面的API文档: App Inventor 编程实例及指南 - 317 -本文档使用 看云 构建 http://code.google.com/apis/chart/index.html 为图表API设置Image.Picture属性为图表API设置Image.Picture属性 在浏览器中输入上述例子中的URL322555现场开奖,香港马会开奖结果直播,就可以看到图表API生成的图表,如果想在香港老钱庄868525,(六合娃娃上显示该图表,就 需要将Image组件的Picture属性设置为上述的URL。具体操作如下: 1. 创建一个新应用,将Screen1的Title属性设置为“图表应用举例”; 2. 添加Image组件,设置其Width属性为“Fill parent”,Height属性为300; 3. 将Image1.Picture属性设置为上述URL()。在组件设计器中无法Picture属性,因为这一属性只接受加载 的文件,因此需要在块编辑器中进行设置,如图24-5所示,添加Screen1.Initialize事件处理程序,并在其 中设置Image1.Picture属性。 图 24-5 应用启动时,设置image组件的picture属性为一个图表API的URL图 24-5 应用启动时,设置image组件的picture属性为一个图表API的URL 在香港老钱庄868525,(六合娃娃或模拟器中将显示图24-6所示的图像。 图 24-6 香港老钱庄868525,(六合娃娃应用中显示的图表图 24-6 香港老钱庄868525,(六合娃娃应用中显示的图表 动态生成图表API的URL322555现场开奖,香港马会开奖结果直播动态生成图表API的URL322555现场开奖,香港马会开奖结果直播 前面的例子显示了如何在应用中生成一个图表,不过例子中的URL使用的是固定数据 (10,50,60,80,40,60,30)。通常我们需要用动态数据来生成图表,即,数据保存在变量中。例如,在一个 游戏应用中,用户之前的成绩保存在变量Scores中,我们要显示这些成绩。 要创建这样的动态图表,同样需要为图表API生成一个URL,并将变量中的数据植入其中。前面例子的URL 中,用于生成图表的数据是固定的,并用参数chd来声明(chd代表图表数据): App Inventor 编程实例及指南 - 318 -本文档使用 看云 构建 chd=t:10,50,60,80,40,60,30 要生成动态的成绩图表,参数定义的开头是一样的,chd=t;之后的数据要从Scores列表中读取,并将成 绩用逗号逐个连接起来。如图24-7中显示的最终的方案。 图 24-7 向图表API发送动态生成的URL图 24-7 向图表API发送动态生成的URL 我们来详细研究一下些块暗藏机关的块,其中大部分我们之前都使用过。 1. 为了便于理解,我们先编造一组数据,假设之前用户有三次游戏的成绩,保存在列表变量Scores中,分 别为35、85、60。 2. 定义了变量chdPara,用来保存URL中列表数据的部分。在showChartButton.Click事件处理程序中, 第一行将变量chdPara初始化为“chd=t:”。 3. 定义了变量scoreIndex,用于在foreach循环中跟踪当前正在处理的列表项,在Click事件处理程序中的 第二行将其初始化为1; 4. 随后是一个判断,看列表Scores中是否包含列表项(length of list > 0),如果包含列表项,则执行 foreach循环: 针对Scores列表中的每一项(成绩值),用参数chdPara的当前值与列表项连接; 然后又是一个判断——检查当前正在处理的列表项是否不为列表的最后一项,如果不是最后一项,则 App Inventor 编程实例及指南 - 319 -本文档使用 看云 构建 在参数chdPara后面添加一个逗号,如果是最后一项,则不添加任何字符。 在循环的最后一行,将变量scoreIndex的值+1,以便在下一次循环中用于判断列表的最后一项。 5. 循环结束后,将Image1的Picture属性设置为最终的URL,其中第一部分 为:http://chart.apis.google.com/chart? cht=bvg&chxt=y&chbh=a&chs=300x225&chco=A2C180&chtt=Vertical+bar+chart| (%E5%9E%82%E7%9B%B4%E6%9D%A1%E7%8A%B6%E5%9B%BE)&">http://chart.apis.google.co m/chart?cht=bvg&chxt=y&chbh=a&chs=300x225&chco=A2C180&chtt=Vertical+bar+chart| (%E5%9E%82%E7%9B%B4%E6%9D%A1%E7%8A%B6%E5%9B%BE)&,第二部分为变量chdPara。 6. 这里为了跟踪参数值,添加了一个名为chdParaLable的标签,用于显示最终生成的参数。 到此为止,我们生成了动态的URL,这样的方式具有普遍的适用性,例如,假设用户在成绩列表中新增了 若干项,那么这个程序也是好用的。图24-8显示了在香港老钱庄868525,(六合娃娃中应用运行的结果。 图 24-8 应用在香港老钱庄868525,(六合娃娃中运行的效果图 24-8 应用在香港老钱庄868525,(六合娃娃中运行的效果 你可以在任何游戏或应用中,采用本例中的方法来显示各种图表,也可以与其他API进行通信,将更多地内 容植入到自己的应用中,其中的关键是App Inventor提供了可以获取网络图片的Image组件。 与网络数据API通信与网络数据API通信 提示:App Inventor现在提供了一个web组件,可以更容易地访问API数据,虽然下述的TinyWebDB方案 仍然有效,但建议查看以下链接中使用web组件的例子: http://www.appinventor.org/stockmarket-steps 谷歌图表API可以接受请求并返回图片,不过更常见的是返回数据的API,在应用中可以对这些数据进行处 理,并根据需要加以利用。例如,在第13章“亚马逊掌上书店”的应用中,返回的数据是图书的列表,其 中每项数据包含了书名、最低售价以及书号(ISBN)。 App Inventor 编程实例及指南 - 320 -本文档使用 看云 构建 使用App Inventor应用于API通信,并不需要像在图表API的例子中那样,要自己来创建URL,而是更像使 用一个网络数据库(见第22章):只需要在TinyWebDB.GetValue中使用相关的标签即可,实际上是 TinyWebDB组件负责生成了访问API的URL。 不过,TinyWebDB并不能访问所有的API,即使是那些返回标准数据的API,如RSS。TinyWebDB只能访 问那些“披着App Inventor外衣”的网络服务,并遵从特定的通信协议。幸运的是,已经创建了许多这样 的服务,并且还会有更多的服务随之而来。网站http://appinventorapi.com上提供了一些这样的服务。 探索API的网络接口探索API的网络接口 本节将学习使用TinyWebDB获取股票价格信息,信息来源于一个App Inventor兼容的API,网址 是http://yahoostocks.appspot.com。访问该网址,将看到一个如图23-9所示的web接口(人类可访问 的)。 图 24-9 App Inventor兼容的雅虎金融API的web接口图 24-9 App Inventor兼容的雅虎金融API的web接口 在Tag输入框中输入“IBM”或其他股票的代码,网页上将返回股票信息列表,每一项代表一个不同的信 息,后面将解释这些数据的含义。 不过,在web页面上查找股票信息并不是什么新鲜事,它的真实目的是为程序员提供一个机器对机器的访 问接口,从而实现与API之间的底层通信。 通过TinyWebDB访问API通过TinyWebDB访问API 创建股票查询应用的第一步是在组件设计器中拖入一个TinyWebDB组件,该组件只有一个属性可以设置, 即ServiceURL,如图24-10所示,它的默认值为:http://appinvtinywebdb.appspot.com,指向默认的 web数据库。而这里我们要访问的雅虎股票API,因此将其设置为http://yahoostocks.appspot.com,与 你之前在浏览器322555现场开奖,香港马会开奖结果直播栏中输入的URL相同。 App Inventor 编程实例及指南 - 321 -本文档使用 看云 构建 图 24-10 将ServiceURL属性设置为图 24-10 将ServiceURL属性设置为http://yahoostocks.appspot.comhttp://yahoostocks.appspot.com 下一步是调用TinyWebDB.GetValue,向网站请求数据。这个操作可以放在一个Button.Click事件中:当 用户在香港老钱庄868525,(六合娃娃的应用界面中输入股票代码并点击“提交”按钮时,执行此调用;或者将其放在 Screen.Initialize事件中,在应用启动时,自动获取某个股票的信息。无论哪种情况,都需要为GetValue 设置tag——某个股票的代码,如图24-11所示,就像在网站http://yahoostocks.appspot.com上的操作 一样。 图 24-11 请求股票信息图 24-11 请求股票信息 在第10章的“出题”应用中,我们已经讨论过数据库组件TinyWebDB,它的通信方式是异步的:应用中 调用TinyWebDB.GetValue请求数据,之后程序将继续运行,必须为这次请求提供另一个事件 TinyWebDB.GotValue的处理程序,当请求的数据从网络服务端返回时,来接收并处理这些数据。通过在 用户界面http://yahoostocks.appspot.com上的操作,我们已经知道返回的数据为列表,每个列表项代表 股票的不同信息(如,第二项代表股票的收盘价)。 客户端的应用可以利用网络所提供的部分或全部信息,如,如果你想显示股票的当前价格,并与开盘价进 行比较,你就可以按照图24-12的方式来组织数据。 图 24-12 使用GotValue时间来处理从Yahoo返回的数据图 24-12 使用GotValue时间来处理从Yahoo返回的数据 如果从网页http://yahoostocks.appspot.com上直接向API提交请求,你会看到返回列表的第2项的确是股 票的当前价格,而第5想是当前价格与当天开盘价之间的差。这个例子只是简单地从API的返回值中提取部 分信息,并用两个label显示出来:PriceLabel与ChangeLabel,如图24-13所示。 App Inventor 编程实例及指南 - 322 -本文档使用 看云 构建 图 24-13 股票应用的运行效果图 24-13 股票应用的运行效果 创建自己的App Inventor兼容的API创建自己的App Inventor兼容的API 在终端应用与网络之间,TinyWebDB起到了桥梁的作用。App Inventor程序员只需要依照GetValue内置 的简单的tag-value协议,就可以实现应用与网络服务之间的通信。这种方式让程序员免于亲手处理那些标 准格式的数据,如XML或JSON。 这种方便的代价是,用App Inventor开发的应用只能与少数网络服务通信,这些网络服务遵从 TinyWebDB所设定的协议,协议中要求返回特殊格式的数据,因此API不得不提供对应格式的数据,如 XML或JSON。如果找不到可用的与App Inventor兼容的API,那么就要靠那些有能力的程序员来创建。 从前,创建API是一件非常困难的事情,不但需要了解编程及网络协议,还要搭建服务器来运行自己创建的 服务,另外还需要建立数据库来保存数据。但现在这件事变得容易多了,你可以借助于云计算工具,如谷 歌公司的应用引擎(Google's App Engine)以及亚马逊公司的弹性计算云(Amazon's Elastic Compute Cloud),来部署自己创建的网络服务。这些平台不仅可以接受委托管理你的服务,还能在不必支付费用 的情况下,让数以千计的用户访问你的服务。可以想象,这些平台为创新提供了巨大的支持。 定制的模板代码定制的模板代码 编写API看似令人望而生畏,但令人欣慰的是你不必从零做起。利用某些现成的模板程序让创建App Inventor兼容的API变得非常容易。这些程序由python语言编写,并使用了谷歌应用引擎(App Engine)。模板程序提供了一段样板代码,可以将数据编辑成App Inventor所需要的格式,还提供了一个 函数get_value,你可以按自己的需要进行修改。 下载模板程序及使用说明,并将其部署到谷歌应用引擎服务器上,网址 是http://appinventorapi.com/using-tinywebdb-to-talk-to-an-api/。你会发现这个链接与第21章创建 定制数据库时使用的网址都指向了相同的appinventorapi.com。实际上创建API类似与创建定制数据库, 只是不必保存及提取数据,而是通过调用其他服务来获取所需要的数据。 为了创建自己的API,要先下载模板程序,并对几个关键代码做出修改,再上传到谷歌应用引擎。创建一个 用TinyWebDB可以访问的API只是几分钟的事情。 以下是从模板程序中选出的一段代码,需要对其进行修改(不必理会那些“#”号后面的文字,它们就像 App Inventor中的注释一样,用来说明接下来的代码的功能): App Inventor 编程实例及指南 - 323 -本文档使用 看云 构建 def get_value(self, tag): #在这个简单的例子中,仅返回hello:tag,其中的tag来自于客户端应用 value="hello:"+tag value = "\""+value+"\"" # 如果value由多个单词组成,为其添加引号 if self.request.get('fmt') == "html": WriteToWeb(self,tag,value ) else: WriteToPhone(self,tag,value) 这段代码属于一个名为get_value的函数(与App Inventor中的procedure相同),当你使用 TinyWebDB.GetValue函数调用某个API时,需要调用这个函数。tag是函数的参数,并于GetValue中发送 的tag相对应。 黑体字的代码是需要修改的部分。默认情况下,该函数从发来的请求中提取tag,并返回“hello:tag”。 (也就是说,如果在调用该函数时使用的tag为“joe”,那么函数将返回“hello:joe”)。通过设定变 量value的值就可以实现这一点,随后value值将传递给另一个函数:如果请求来自于web,则传给函数 WriteToWeb,如果请求来自香港老钱庄868525,(六合娃娃,则传给WriteToPhone。  提示:即使你从未见过Python或其他程序的代码,根据使用App Inventor的经验,你也 可以读懂上面的代码。其中第一行“def get_value”是对过程的定义,“vlue=...”行是为变量value 赋值,“if...”后面的代码看起来很熟悉。是的,与App Inventor相比,它们的基本概念是相同的, 只是用文字取代了块。 为了定制这段代码,需要将粗体字替换成你需要的某种计算,目的是为了给变量value赋值。通常你的API 需要调用其他的API(被称为“封装”调用,更具体地说,就是get_value函数将调用其他的API)。 许多API过于复杂,拥有几百个函数以及复杂的用户认证方案。而另一些则相当简单,你可以找到一些例 程,并在网络上访问它们,如下节所述。 封装雅虎金融API封装雅虎金融API 本章所使用的App Inventor专用雅虎股票API就是通过对上述模板程序的修改而获得,该模板程序可以从 网上搜索到。为了将雅虎股票API封装成App Inventor可以调用的API,开发者(Wolber教授)在网 站http://www.gummy-stuff.org/Yahoo-data.htm【网站322555现场开奖,香港马会开奖结果直播已经不存在了——译者注】上搜 索"Python Yahoo Stocks API",并发现了如下格式的URL: http://download.finance.yahoo.com/d/quotes.csv?f=sl1d1t1c1ohgv&e=.cs v&s=IBM 上述URL将一个文本本件“quotes.csv”下载到本地计算机,文件中包含了如下格式的字符串: "IBM",183.76,"5/29/2014","4:02pm",+0.68,183.68,183.78,182.33,2759978 之后Wolber教授又在网站http://www.goldb.org/ystockquote.html【该网站可访问,但代码已经更新, App Inventor 编程实例及指南 - 324 -本文档使用 看云 构建 找不到本书中采用的代码了。——译者注】上发现了可以访问雅虎股票API的Python代码。通过几次快速 的剪切粘贴及编辑,为App Inventor封装的API就创建出来了,具体修改方式如下: def get_value(self, tag): # Need to generate a string or list and send it to WriteToPhone/ WriteToWeb # Multi-word strings should have quotes in front and back # e.g., # value = "\""+value+"\"" # call the Yahoo Finance API and get a handle to the file that is returned quoteFile=urllib.urlopen("http://download.finance.yahoo.com/d/quotes.csv?f=sl1d1t1c1ohgv&e=.c sv&s="+tag) line = quoteFile.readline() # there's only one line splitlist = line.split(",") # split the data into a list # the data has quotes around the items, so eliminate them i=0 while i<len(splitlist): item=splitlist[i] splitlist[i]=item.strip('"') # remove " around strings i=i+1 value=splitlist if self.request.get('fmt') == "html": WriteToWeb(self,tag,value ) else: WriteToPhone(self,tag,value) 那行粗体的代码通过对urllib.urlopen函数的调用来访问雅虎API(这是Python语言访问API的方法之一)。在 URL中有一个参数f,它表明你想获得的股票数据的类型(这个参数有点像谷歌图表API中的神秘参数)。数据 保存在变量line中,其余的代码将返回值分解为列表,移除每个列表项中的引号,并将结果发给请求者 (电脑上的web页面或香港老钱庄868525,(六合娃娃上的App Inventor应用)。 小结小结 大多数网站以及许多移动应用并非孤岛,它们遵从互联互通原则,利用其它网站的功能来实现自己的目 标。在App Inventor中,可以创建独立的应用,如游戏、测验等,但这还远远不够,你迟早会遇到访问 web的问题。是否可以为我平时等车的公交车站写一个应用,来预计下一班车何时到达呢?是否可以让应 用给我facebook中的部分好友发送短信呢?再有,应用是否能够发tweet呢?App Inventor中有两种方式 可以连接到网络:①将Image.Picture属性设置为某个返回图像的URL;②使用TinyWebDB从某些专用的 API上获取数据。 App Inventor不支持对任意API的访问,程序员需要创建遵从特定协议的“封装”API来实现对web的访 问。一旦有了封装的API,App Inventor程序员就可以像访问数据库一样,使用TinyWebDB.GetValue来 访问需要的API。实际上,相对于编写App Inventor应用来说,编写API对程序员来说是一个更大的挑战, 但如果你有兴趣学习,可以查阅一些Python的书籍及课程(O'Reilly出版社有若干这类的书),然后就可 以开练了。 App Inventor 编程实例及指南 - 325 -本文档使用 看云 构建
还剩325页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 6 人已下载

下载pdf

pdf贡献者

aderson

贡献于2017-05-08

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf