你把車停得盡量靠近體育館,但演唱會一結束,你卻忘了車停在哪兒,你的同伴也很茫然。幸運的是,你的Android手機還在,它從來不忘事,你新裝了一款熱門應用“Android,我的車在哪兒?”有了這個應用,在停車時點一下按鈕,Android的位置傳感器會“記住”車的GPS坐標和地址。當稍后重新打開應用時,它會指給你從現在位置到停車位置的方向,問題解決了!

## **學習要點**
本章涵蓋如下概念:
* LocationSensor組件:確定Android設備的位置;
* TinyDB組件:直接在設備數據庫中記錄數據;
* ActivityStarter組件:在應用中打開谷歌地圖,并顯示從一個位置到另一個位置的方向。
## **準備開始**
登陸App Inventor網站,開始一個新項目“AndroidWhere”(項目名稱不能有空格),將屏幕標題設置為“Android,我的車在哪兒?”,連接測試手機。
## **設計組件**
應用包含下列可視組件:
* 多個Label組件:顯示當前位置和“記住”的位置信息,有些Label顯示靜態文本,如GPSLabel顯示“GPS:”;其他Label,如CurrentLatLabel顯示來自位置傳感器的數據。給這些Label設定一個默認值(0,0),當GPS取得位置信息時,這個值將隨之改變;
* 兩個Button組件:記錄位置和指示該位置的方向;
以及三個非可視組件:
* LocationSensor組件:獲取當前位置信息;
* TinyDB組件:永久保存位置信息;
* ActivityStarter組件:用于打開谷歌地圖,以獲得當前位置和記住位置之間的路線。
按照圖7-1所示的組件設計器截圖來創建組件。

**圖 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 |
>  提示: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位置信息之外,還通過調用谷歌地圖,獲得了與位置信息相對應的街道地址信息。

**圖 7-2 使用LocationSensor讀取當前位置信息**
事件處理程序還啟用了RememberButton,該按鈕的初始設置為禁用(未選中),因為在傳感器獲得讀數之前,用戶不需要“記住”什么,而現在我們可以為“記住”行為編寫程序了。
>  測試:用手機(wifi與電腦連接)實時測試位置感知應用是無效的。將程序打包并下載到手機上:選擇“build?App(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所示。

**圖 7-3 將當前位置信息寫入“已記錄”label中**
注意到DirectionsButton已可用,這會有點兒小麻煩,因為如果用戶立即點擊DirectionsButton,記住的位置也是當前位置,因而地圖中不會提供方向有關的信息。但是,人們似乎不會這么做,當用戶移動位置時(例如步行到演唱會),則當前位置將偏離已記錄的位置。
>  測試:將應用的新版本下載到手機,并再次測試。當單擊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發送給地圖應用。

**圖 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 | 打開地圖 |
>  測試:用手機下載新的版本并再次測試,一旦取得讀數,單擊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數據。

**圖 7-5 在數據庫中存儲被“記住”的位置信息**
### **啟動應用時讀取“記住”的位置信息**
將數據保存在數據庫中,是為了以后可以調用它。在本應用中,如果用戶在保存了位置信息之后退出應用,那么當應用重新打開時,你希望從數據庫中讀出信息并顯示給用戶。
在前幾章中討論過,應用的啟動會觸發Screen.Initialize事件,而在啟動時從數據庫中讀取數據是一種慣例,我們也不例外。
使用TinyDB.GetValue函數來讀取存儲的GPS數據。要讀取的存儲數據包括地址、緯度及經度,因此要調用GetValue函數三次。像在“開車不發短信”中一樣,要事先檢查數據庫中否保存了數據(如,第一次啟動應用時,TinyDB.GetValue將返回一個空文本)。
挑戰一下自己,看看是否可以獨立創建這些塊,然后再與圖7-6進行比較。

**圖 7-6 在應用啟動時,從數據庫中讀取數據,如果數據不為空則顯示數據**
#### **塊的作用**
理解這些塊的方法是設想用戶的使用過程:用戶首次打開應用,先保存位置信息,稍后再次打開應用。首次打開應用,數據庫中沒有信息可加載,也不必填寫“已記錄”label或啟用DirectionsButton。在后續的使用中,如果確有數據存儲,就要從數據庫中加載這些位置信息。
首先用“address”為tag(標簽)調用TinyDB1.GetValue函數,之前在存儲位置信息時使用過這個tag。讀取的值保存在變量tempAddress中,并檢查其是否為空。
if塊將檢查從數據庫中讀出的數據。如果TinyDB對指定的tag沒有返回值,則返回空文本。首次啟動應用時沒有數據可讀,直到用戶點擊了RememberButton。由于變量tempAddress中保存了數據庫的返回值,因此if塊將檢查tempAddress的長度,如果長度>0,則TinyDB有地址信息返回,也表明經緯度.GetValue讀出經緯度信息。當設置完所有信息,最后啟用DirectionsButton。
>  測試:將新版本應用下載到手機,并再次測試。點擊RememberButton,并確保“記住”讀數。關閉應用并再次打開。那些數據是否還在?
## **完整的應用:Android,我的車在哪兒?**
圖7-7顯示了完整的“Android,我的車在哪兒?”應用中所用到的塊。

**圖 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通信