你把车停得尽量靠近体育馆,但演唱会一结束,你却忘了车停在哪儿,你的同伴也很茫然。幸运的是,你的Android手机还在,它从来不忘事,你新装了一款热门应用“Android,我的车在哪儿?”有了这个应用,在停车时点一下按钮,Android的位置传感器会“记住”车的GPS坐标和地址。当稍后重新打开应用时,它会指给你从现在位置到停车位置的方向,问题解决了!
![{%}](https://box.kancloud.cn/2015-08-31_55e3fa4089ec8.png)
## **学习要点**
本章涵盖如下概念:
* LocationSensor组件:确定Android设备的位置;
* TinyDB组件:直接在设备数据库中记录数据;
* ActivityStarter组件:在应用中打开谷歌地图,并显示从一个位置到另一个位置的方向。
## **准备开始**
登陆App Inventor网站,开始一个新项目“AndroidWhere”(项目名称不能有空格),将屏幕标题设置为“Android,我的车在哪儿?”,连接测试手机。
## **设计组件**
应用包含下列可视组件:
* 多个Label组件:显示当前位置和“记住”的位置信息,有些Label显示静态文本,如GPSLabel显示“GPS:”;其他Label,如CurrentLatLabel显示来自位置传感器的数据。给这些Label设定一个默认值(0,0),当GPS取得位置信息时,这个值将随之改变;
* 两个Button组件:记录位置和指示该位置的方向;
以及三个非可视组件:
* LocationSensor组件:获取当前位置信息;
* TinyDB组件:永久保存位置信息;
* ActivityStarter组件:用于打开谷歌地图,以获得当前位置和记住位置之间的路线。
按照图7-1所示的组件设计器截图来创建组件。
![{%}](https://box.kancloud.cn/2015-08-31_55e3fa418ef60.png)
**图 7-1 组件设计器中应用的用户界面**
跟随表7-1,逐个拖出组件,并做相应设置,创建如图7-1所示的用户界面。
**表7-1 应用中的所有组件**
| 组件类型 | 面板中分组 | 命名 | 作用 |
| --- | --- | --- | --- |
| Label | User Interface | CurrentHeaderLabel | 显示标题“当前位置”|
| HorizontalArrangement | Screen Arrangement | CurrentAddrArrangement | 放置地址信息 |
| Label | User Interface | CurrentAddressLabel | 显示“地址:” |
| Label | User Interface | CurrentAddressDataLabel | 显示动态数据:当前地址 |
| 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 | 显示动态数据:已记录的地址 |
| HorizontalArrangement | Screen Arrangement | RememberGPSArrangement | 安置已记录的GPS信息 |
| Label | User Interface | RememberedGPSlabel | 显示“GPS:” |
| Label | User Interface | RememberedLatLabel | 显示动态数据:已记录的纬度 |
| Label | User Interface | Comma2Label | 显示“,” |
| 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”;
* 设置显示动态地址的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 |
> ![](https://box.kancloud.cn/2015-08-31_55e3fa4207fd1.png) 提示:ActivityStarter组件可在应用中打开安装在设备上的任何其他Android应用。要打开地图,表7-2中的属性必须一字不差地输入;要打开其他应用,请参阅[http://appinventor.googlelabs.com/learn/reference/other/activitystarter.html](http://appinventor.googlelabs.com/learn/reference/other/activitystarter.html) 中的App Inventor文档。
## **为组件添加行为**
需要为应用设定如下行为:
* 当LocationSensor读取到位置信息时,将数据填写到相应的Label中,表示传感器已经读取到当前位置信息,用户这时可以选择保存此位置信息;
* 当用户点击RememberButton时,当前位置信息被复制到“已记录的地点”名下的Label中。这些信息要保存到设备数据库中,以便用户关闭并再次打开应用时,数据不会消失;
* 当用户点击DirectionsButton时,打开谷歌地图,并显示“已记录”位置的方向;
* 当应用重新启动时,从数据库中加载“已记录”的位置信息。
### **显示当前位置**
两种情况会触发LocationSensor.LocationChanged事件,(1)传感器首次读取位置信息时;(2)设备的位置变化,传感器读数更新时。首次读数有时仅需几秒钟,但如果GPS卫星信号受到屏蔽,会一直没有读数(也与设备的设置有关)。有关GPS和LocationSensor的更多信息,请参见第23章。
在读取到位置信息时,程序要将数据写到相应的Label中。表7-3列出了所有相关的块。
**表7-3 读取到位置信息时,用户界面显示这些信息所需要的块**
| 块的类型 | 所在抽屉 | 作用 |
| --- | --- | --- |
| LocationSensor1.LocationChanged | LocationSensor | 当手机收到新的GPS读数时,触发该事件 |
| set CurrentAddressDataLabel.Text to | CurrentAddressDataLabel | 将当前地址的新数据写入label |
| LocationSensor1.CurrentAddress | LocationSensor | 该属性保存了街道地址信息|
| 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插槽 |
#### **块的作用**
如图7-2所示,latitude(经度)和longitude(纬度)是LocationChanged事件的参数,因此可以从Variables抽屉中抓取;但CurrentAddress则不是参数,而是LocationSensor的属性,因此要从LocationSensor抽屉里抓取。LocationSensor除了获取GPS位置信息之外,还通过调用谷歌地图,获得了与位置信息相对应的街道地址信息。
![{%}](https://box.kancloud.cn/2015-08-31_55e3fa42548db.png)
**图 7-2 使用LocationSensor读取当前位置信息**
事件处理程序还启用了RememberButton,该按钮的初始设置为禁用(未选中),因为在传感器获得读数之前,用户不需要“记住”什么,而现在我们可以为“记住”行为编写程序了。
> ![](https://box.kancloud.cn/2015-08-31_55e3fa47aab9f.png) 测试:用手机(wifi与电脑连接)实时测试位置感知应用是无效的。将程序打包并下载到手机上:选择“buildApp(provide QR code for .apk)”,按照提示在手机上打开应用。GPS及地址信息显示在屏幕上,同时RememberButton变为可用。
如果没有获得读数,检查一下Android设备的位置及安全性设置,并尝试走到户外。要了解更多信息,请参见第23章。
### **记录当前位置**
当用户点击RememberButton时,当前位置信息被写入“已记录的地点”下方的label中。表7-4显示了实现这一功能所需要的块。
**表7-4 记录并显示当前位置所需要的块**
| 块的类型 | 所在抽屉 | 作用 |
| --- | --- | --- |
| RememberButton.Click | RememberButton | 用户点击按钮时触发该事件 |
| set RememberedAddressDataLabel.Text to | RememberedAddressDataLabel | 将传感器获得的地址信息写入“已记录”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所示。
![{%}](https://box.kancloud.cn/2015-08-31_55e3fa481f8e0.png)
**图 7-3 将当前位置信息写入“已记录”label中**
注意到DirectionsButton已可用,这会有点儿小麻烦,因为如果用户立即点击DirectionsButton,记住的位置也是当前位置,因而地图中不会提供方向有关的信息。但是,人们似乎不会这么做,当用户移动位置时(例如步行到演唱会),则当前位置将偏离已记录的位置。
> ![](https://box.kancloud.cn/2015-08-31_55e3fa47aab9f.png) 测试:将应用的新版本下载到手机,并再次测试。当单击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设定动态参数:起点地址(saddr)和终点地址(daddr)。在前几章中,你已经学会用join块将文本连接起来,这里也是如此。将当前位置和已记录位置的GPS数据插入到URL中,设置ActivityStarter.DataUri属性为URL,然后调用ActivityStarter.StartActivity。表7-5列出了此项功能所需要的块。
#### **块的作用**
用户点击DirectionsButton时,事件处理程序生成一个地图URL,然后调用ActivityStarter打开地图应用并加载地图,如图7-4所示,用join创建的URL发送给地图应用。
![{%}](https://box.kancloud.cn/2015-08-31_55e3fa48bb212.png)
**图 7-4 生成一个URL,用来打开地图并指示方向**
最终的URL包含了地图域名([http://maps.google.com/maps](http://maps.google.com/maps))以及两个URL参数:saddr与daddr,用来指定方向的起点位置及终点位置。在本应用中,saddr被设定为当前位置的纬度和经度,而daddr被设定为已记录的停车位置的纬度和经度。
**表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 | 打开地图 |
> ![](https://box.kancloud.cn/2015-08-31_55e3fa47aab9f.png) 测试:用手机下载新的版本并再次测试,一旦取得读数,单击RememberButton然后走开。当单击DirectionsButton时,地图是否提示您如何追溯你的脚步?点击几次后退按钮。你是否有回到了你的应用?
### **永久保存已记录的位置信息**
现在已经具备了一个全功能的应用:记住起点位置,并从当前用户所在的位置绘制一张回到起点的地图。虽然用户“记住”了位置,但假如应用被关闭,然后再重新打开,“记住”的信息也将消失。实际上你希望用户能够记录下车的位置,关闭应用,走到别处,然后重新启动应用,并获取已记录的车辆所在位置的方向。
如果你能想起“开车不发短信”应用(第4章),说明你的思路是正确的,我们需要使用TinyDB数据库来永久保存这些数据,采取的方案也与之前的应用类似:
1\. 当用户点击RememberButton时,位置信息存储到数据库中;
2\. 当应用启动时,从数据库中加载位置信息并保存到一个变量或属性中。
从修改RememberButton.Click事件处理程序开始,来存储这些要被“记住”的信息。存储纬度、经度和地址三组信息,需要三次调用TinyDB.StoreValue。表7-6列出了所要补充的块。
**表7-6 永久保存位置信息所需要的块**
| 块的类型 | 所在抽屉 | 作用 |
| --- | --- | --- |
| TinyDB1.StoreValue(3) | TinyDB1 | 将数据保存在设备数据库中 |
| “address” | Text | 插入TinyDB1.StoreValue的tag插槽中 |
| LocationSensor1.CurrentAddress | LocationSensor1 | 插入TinyDB1.StoreValue的value插槽中,永久保存地址信息 |
| “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数据。
![{%}](https://box.kancloud.cn/2015-08-31_55e3fa4e7dea2.png)
**图 7-5 在数据库中存储被“记住”的位置信息**
### **启动应用时读取“记住”的位置信息**
将数据保存在数据库中,是为了以后可以调用它。在本应用中,如果用户在保存了位置信息之后退出应用,那么当应用重新打开时,你希望从数据库中读出信息并显示给用户。
在前几章中讨论过,应用的启动会触发Screen.Initialize事件,而在启动时从数据库中读取数据是一种惯例,我们也不例外。
使用TinyDB.GetValue函数来读取存储的GPS数据。要读取的存储数据包括地址、纬度及经度,因此要调用GetValue函数三次。像在“开车不发短信”中一样,要事先检查数据库中否保存了数据(如,第一次启动应用时,TinyDB.GetValue将返回一个空文本)。
挑战一下自己,看看是否可以独立创建这些块,然后再与图7-6进行比较。
![{%}](https://box.kancloud.cn/2015-08-31_55e3fa59050bf.png)
**图 7-6 在应用启动时,从数据库中读取数据,如果数据不为空则显示数据**
#### **块的作用**
理解这些块的方法是设想用户的使用过程:用户首次打开应用,先保存位置信息,稍后再次打开应用。首次打开应用,数据库中没有信息可加载,也不必填写“已记录”label或启用DirectionsButton。在后续的使用中,如果确有数据存储,就要从数据库中加载这些位置信息。
首先用“address”为tag(标签)调用TinyDB1.GetValue函数,之前在存储位置信息时使用过这个tag。读取的值保存在变量tempAddress中,并检查其是否为空。
if块将检查从数据库中读出的数据。如果TinyDB对指定的tag没有返回值,则返回空文本。首次启动应用时没有数据可读,直到用户点击了RememberButton。由于变量tempAddress中保存了数据库的返回值,因此if块将检查tempAddress的长度,如果长度>0,则TinyDB有地址信息返回,也表明经纬度.GetValue读出经纬度信息。当设置完所有信息,最后启用DirectionsButton。
> ![](https://box.kancloud.cn/2015-08-31_55e3fa47aab9f.png) 测试:将新版本应用下载到手机,并再次测试。点击RememberButton,并确保“记住”读数。关闭应用并再次打开。那些数据是否还在?
## **完整的应用:Android,我的车在哪儿?**
图7-7显示了完整的“Android,我的车在哪儿?”应用中所用到的块。
![{%}](https://box.kancloud.cn/2015-08-31_55e3fa59e2e32.png)
**图 7-7 “Android,我的车在哪儿”应用中所有的块**
## **改进**
可以尝试如下改进:
* 创建一个“Android,他们在哪儿?”的应用,让一群人可以了解彼此行踪。无论你在徒步旅行还是在公园散步,这个应用都有助于你节省时间,甚至可能挽救生命。应用中的数据是共享的,因此要使用web数据库,用TinyWebDB组件来替代TinyDB。更多信息请参见第22章。
* 创建一个“行踪”应用,用列表来记录自己的位置改变,即行踪。当记录数达到一定数量时,或超过一定时间时,开始一个新的“行踪”,因为即使是轻微的位移也会产生一个新的位置读数。这类应用需要使用列表来存储位置记录,需要帮助时请参见第19章。
## **小结**
下面是本章涉及到的概念:
* LocationSensor组件:可以报告用户的纬度、经度及当前的街区地址。当传感器首次获得数据或数据发生变化(设备移动)时,将触发LocationChanged事件。有关LocationSensor的更多信息,请参见第23章;
* ActivityStarter组件:可以在一个应用中打开其他应用,包括谷歌地图。对于地图,需要将ActivityStarter的DataUri属性设置为想要打开的地图的URL地址。如果你想显示两个GPS坐标之间的方向,URL应该写成下面的格式,你可以用实际位置的GPS坐标来替换下面的示例数据:http://maps.google.com/maps/?saddr=0.1,0.1&daddr=0.2,0.2;
* join用来将文本片段拼凑(连击)成单一的文本对象,也可以让静态文本与动态数据相连接。对于地图URL来说,GPS坐标就是动态数据;
* TinyDB让数据永久地保存在在手机的数据库中。保存在变量或属性中数据,会随着应用的关闭而丢失,但存储在数据库中的数据,可以在每次启动应用时被载入。有关TinyDB和数据库的详细信息,请参见第22章。
### **资源下载**
[AndroidWhere.aia](http://www.17coding.net/download/7/AndroidWhere.aia)
[AndroidWhere.apk](http://www.17coding.net/download/7/AndroidWhere.apk)
- 简介
- 序言
- 前言
- 第 1 章 Hello 猫咪
- 第 2 章 油漆桶
- 第 3 章 打地鼠
- 第 4 章 开车不发短信
- 第 5 章 瓢虫快跑
- 第 6 章 巴黎地图旅游
- 第 7 章 安卓,我的车在哪?
- 第 8 章 总统测验
- 第 9 章 木琴
- 第 10 章 出题及答题
- 第 11 章 广播中心
- 第 12 章 遥控机器人
- 第 13 章 亚马逊掌上书店
- 第 14 章 理解应用的结构
- 第 15 章 软件工程与应用调试
- 第 16 章 应用中的存储
- 第 17 章 创建动画应用
- 第 18 章 程序中的决策:条件块
- 第 19 章 数据列表编程
- 第 20 章 循环
- 第 21 章 定义过程
- 第 22 章 数据库
- 第 23 章 传感器
- 第 24 章 与Web API通信