chapter 9 定位與 google 地圖 -...

Post on 20-Sep-2019

7 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Chapter 9 定位與 Google 地圖

作者: 林致宇

定位與 Google地圖的結合產生了許多的應用,除了一般人較為熟知的行車導航

系統之外,位置感知服務(Location-Based Service, LBS)也帶來龐大的商機,位置

感知服務的應用範圍很廣泛,例如找出使用者附近的餐廳、停車場等資訊都是屬

於位置感知服務。此外導覽系統也是很常見的一種應用,例如校園導覽系統、博

物館導覽系統、購物商場導覽系統等,結合了定位技術後,系統可提供使用者更

精確的資訊。本章我們將學習如何在 Android 系統中獲得位置資訊,此外也會學

習結合定位與 Google 地圖做出一個簡易的導覽系統。

需注意的是,本章 9.2至 9.4節的內容屬於 Google Maps Android API v1,此 API

已經於 2012年 12月 3日廢除,2013年 3月 3 日以後讀者就無法再利用 9.2所提

的方法獲得開發金鑰,然而若讀者有舊的開發金鑰,仍可繼續使用。為了讓讀者

快速進入 Google Maps Android API v2,本章會於 9.5節中做說明。

9.1 取得所在位置

在一節中,我們會完成一個應用程式,這個應用程式會將使用者所在位置的經緯

度顯示給使用者,請讀者引進光碟中『\程式範例\Chapter9\MyLocation』這個專

案,然而在深入探討程式碼之前,筆者先介紹Android系統提供的兩種定位方法:

透過 GPS(Global Positioning System,全球定位系統):GPS 藉助衛星來定位

[1],能夠獲得精確的位置,然而無法使用於無法接收衛星訊號的地方,例

如室內。Android 系統裡這種定位方法的定位提供者(Provider)名為"gps"。

透過行動通訊基地台(Cell tower)或者無線區域網路存取點(WiFi access

points)來定位:其是藉助一些定位技術(如三角定位)來估算出使用者所在位

置,獲得的位置資訊較不精確,然而可於室內使用。Android 系統裡這種定

位方法的定位提供者(Provider)名為"network"。

接下來我們便要開始解說這個應用程式是如何設計的,首先我們必須在

AndroidManifest.xml 聲明這個應用程式會希望系統能允許它存取從 GPS 或基地

台得到的位置資訊,這麼做是為了安全的考量,例如假設有一個應用程式,使用

者認為這個應用程式是離線運作的,可是它卻是會連上網路,進行一些資料傳輸,

使用者卻渾然不知,對使用者來說不是一件很沒有安全感的事嗎?因此 Android

系統設計了一個『許可』機制,若應用程式想連上網際網路,它必須在

AndroidManifest.xml 中表明它希望得到連上網際網路的許可,使用者在安裝這個

應用程式時,會得知這個應用程式希望得到哪些許可,如此使用者可決定要不要

安裝這個應用程式,若讀者想瞭解更多關於『許可』的說明或者想知道 Android

系統提供了哪些『許可』,可至 Android 開發者網站閱讀相關文件[2][3]。

在這個應用程式中,我們需要得到兩個許可,第一個是

ACCESS_FINE_LOCATION,應用程式得到許可後會獲得存取精確位置(例如由

GPS 所提供)的權限,第二個是 ACCESS_COARSE_LOCATION,應用程式得到

許可後會獲得存取不精確位置(例如由基地台所提供)的權限,為了得到許可,程

式開發者需要在 AndroidManifest.xml 中使用<uses-permission>標籤聲明,整個

AndroidManifest.xml 的內容如下,讀者可發現我們只需要將<uses-permission>標

籤置於<manifest>標籤下,且利用 android:name 屬性指定欲取得的許可,即可完

成聲明的動作:

1 <?xml version="1.0" encoding="utf-8"?>

2 <manifest xmlns:android=

3 "http://schemas.android.com/apk/res/android"

4 package="lincyu.mylocation"

5 android:versionCode="1"

6 android:versionName="1.0">

7 <application android:icon="@drawable/icon"

8 android:label="@string/app_name">

9 <activity android:name=".MyLocation"

10 android:label="@string/app_name">

11 <intent-filter>

12 <action android:name="android.intent.action.MAIN"/>

13 <category

14 android:name="android.intent.category.LAUNCHER"/>

15 </intent-filter>

16 </activity>

17 </application>

18 <uses-sdk android:minSdkVersion="3" />

19 <uses-permission android:name=

20 "android.permission.ACCESS_FINE_LOCATION">

21 </uses-permission>

22 <uses-permission android:name=

23 "android.permission.ACCESS_COARSE_LOCATION">

24 </uses-permission>

25 </manifest>

接下來我們開始深入探討 Java程式碼,程式碼如下所示:

1 public class MyLocation extends Activity {

2 private MyLocationListener mll;

3 private LocationManager mgr;

4 private TextView tv;

5 private String best;

6

7 @Override

8 public void onCreate(Bundle savedInstanceState) {

9 super.onCreate(savedInstanceState);

10 setContentView(R.layout.main);

11 mgr = (LocationManager)getSystemService(LOCATION_SERVICE);

12 tv = (TextView)findViewById(R.id.loc);

13 mll = new MyLocationListener();

14 Criteria criteria = new Criteria();

15 best = mgr.getBestProvider(criteria, true);

16 Location location = mgr.getLastKnownLocation(

17 LocationManager.GPS_PROVIDER);

18 if (best != null) {

19 location = mgr.getLastKnownLocation(best);

20 }

21 if (location != null) {

22 tv.setText(showLocation(location));

23 } else {

24 tv.setText("Cannot get location!");

25 }

26 }

27

28 @Override

29 protected void onResume() {

30 super.onResume();

31 Criteria criteria = new Criteria();

32 criteria.setAccuracy(Criteria.ACCURACY_FINE);

33 best = mgr.getBestProvider(criteria, true);

34 if (best != null) {

35 mgr.requestLocationUpdates(best, 1000, 1, mll);

36 } else {

37 mgr.requestLocationUpdates(LocationManager.GPS_PROVIDER,

38 1000, 1, mll);

39 }

40 }

41

42 @Override

43 protected void onPause() {

44 super.onPause();

45 mgr.removeUpdates(mll);

46 }

47

48 class MyLocationListener implements LocationListener {

49 @Override

50 public void onLocationChanged(Location location) {

51 if (location != null) {

52 tv.setText(MyLocation.showLocation(location));

53 } else {

54 tv.setText("Cannot get location!");

55 }

56 }

57 @Override

58 public void onProviderDisabled(String provider) {

59 }

60 @Override

61 public void onProviderEnabled(String provider) {

62 }

63 @Override

64 public void onStatusChanged(String provider, int status,

65 Bundle extras) {

66 }

67 }

68

69 public static String showLocation(Location location) {

70 StringBuffer msg = new StringBuffer();

71 msg.append("定位提供者(Provider): \n");

72 msg.append(location.getProvider());

73 msg.append("\n緯度(Latitude): \n");

74 msg.append(Double.toString(location.getLatitude()));

75 msg.append("\n經度(Longitude): \n");

76 msg.append(Double.toString(location.getLongitude()));

77 msg.append("\n高度(Altitude): \n");

78 msg.append(Double.toString(location.getAltitude()));

79 return msg.toString();

80 }

81 }

首先在第 11行,程式利用 Context 類別的 getSystemService方法來取得

LocationManager的物件實體[4]。而在第 13行,程式宣告了一個

MyLocationListener 物件,MyLocationListener類別是定義在 48~67 行,其實作了

LocationListener 介面[5],這個傾聽者可用來處理位置改變或定位提供者改變時

的對應對作,我們可以請 LocationManager 週期性地幫我們取得最新位置並向

LocationManager註冊這個傾聽者,如此當 LocationManager 發現位置改變時,就

會透過傾聽者告知給應用程式,應用程式再做出相對應的動作。第 14行,程式

宣告了一個 Criteria 物件[6],Criteria類別是讓開發者設定選擇定位提供者的“選

擇偏好”,例如希望得到最精確的位置,或者希望能夠以省電為主要考量等。設

定好“選擇偏好”後,Criteria物件就能丟進第 15行的 getBestProvider 方法當參

數,getBestProvider 會回傳一個字串,例如可能是"gps"或"network"。接著程式利

用 getLastKnownLocation 方法取得最新位置,並利用自行定義的 showLocation

方法將位置資訊顯示於 TextView上,showLocation 方法是撰寫於 68~80 行。以

上便是 onCreate方法內的程式說明。

如果使用者想讓 LocationManager 週期性地更新最新位置,必須呼叫

requestLocationUpdates方法,而想停止週期性回報時則呼叫 removeUpdates方法,

我們選擇在 onResume 與 onPause方法呼叫上述方法,理由是因為應用程式可能

會因某些原因(例如有電話打進來)而暫時移至背景處理,此時若還繼續更新位置,

有點浪費電源,因此於 onPause方法裡呼叫 removeUpdates 方法,而於 onResume

方法裡呼叫 requestLocationUpdates 方法,requestLocationUpdates 方法有多重定

義,程式採用了下面這個定義:

public void requestLocationUpdates (String provider, long minTime,

float minDistance, LocationListener listener)

第一個參數填入定位提供者。第二個參數填入位置更新的最短間隔時間,以毫秒

為單位。第三個參數則填入位置更新的最短距離,以公尺為單位,這兩個參數決

定了回報的頻率,如果要獲得即時的位置資訊,可以將這兩個參數都設成 0。最

後一個參數則是填入傾聽者,傾聽者會處理位置更新時的對應動作,程式的傾聽

者是撰寫於 48~67 行,當位置更新時,會呼叫 TextView類別的 setText 方法將最

新位置顯示於 TextView。

至此,讀者對於整個程式已經有一定的瞭解了,唯一沒有細談的是 Criteria 類別,

Criteria類別提供了許多方法讓使用者設定定位提供者的“選擇偏好”,請讀者自

行閱讀相關說明文件[6]。

9.2 Google地圖開發金鑰 (v1)

Google地圖是 Android 系統的一大特點,然而基於某些原因 Google 地圖函式庫

是個選擇性的 API,若想在程式內使用相關 API,必須先取得 Google 地圖開發

金鑰[7],在一節中,我們將學習如何取得 Google地圖開發金鑰。

第一步是先找出憑證檔案並取得憑證檔案的 MD5認證指紋,在第四章我們有介

紹過如何利用 keytool 工具產生憑證檔案,使用者只要執行下面的命令,就能獲

得該憑證檔案的MD5 認證指紋,其中 Lincyu 即為第四章中所產生的憑證檔案:

keytool –list –keystore Lincyu

執行結果則如下圖所示:

然而這個憑證是用於上傳至實機上時使用的,在上傳至實機前,若想先使用模擬

器觀看執行結果,是無法使用這個憑證的。事實上,上傳至模擬器的 apk檔案會

使用一個除錯憑證,於Windows XP 下,這個除錯憑證是位於 C:\Documents and

Settings\Kasim\.android,為了能於模擬器上測試 Google地圖相關應用程式,使

用者也最好利用 keytool 取得除錯憑證的MD5 認證指紋,其中 keystore 密碼為

『android』。

取得MD5認證指紋後,我們接著要取得 Google地圖開發金鑰,我們需要透過下

面這個網址來取得:

http://code.google. com/android/maps-api-signup.html

連結上後,看完條款說明並勾選同意,接著輸入MD5認證指紋並按下『Generate

API Key』的按鈕,便可取得 Google地圖開發金鑰。按下按鈕後,在出現的頁面

中,會有類似下面這樣的程式片段:

<com.google.android.maps.MapView

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:apiKey="0ZwL91MFSujDY3L9Q2cjYL5elUTa8G4QpxLyrCQ"

/>

其中 0ZwL91MFSujDY3L9Q2cjYL5elUTa8G4QpxLyrCQ 即為 Google 地圖開發金

鑰。

9.3 MapView

這一節我們將示範如何於自己的應用程式中使用 Google地圖函式庫,讀者可引

進光碟中『\程式範例\Chapter9\AsiaUniversity1』這個專案閱讀範例程式。整個開

發流程可分成三個步驟來說明:

應用程式描述檔(AndroidManifest.xml)的修改

版面設計描述檔的撰寫

Java程式碼的撰寫

9.3.1應用程式描述檔(AndroidManifest.xml)的修改

由於 Google地圖函式庫是個選擇性的 API,因此必須在<application>標籤內使用

<uses-library>標籤讓應用程式能使用相關的函式庫,寫法如下:

<uses-library android:name="com.google.android.maps"/>

此外,由於應用程式需要連接上網際網路抓取圖資,因此利用<uses-permission>

標籤加上需要網際網路存取的許可:

<uses-permission android:name="android.permission.INTERNET">

9.3.2版面設計描述檔的撰寫

第二個步驟是於版面設計描述檔加上一個MapView介面元件,範例中版面設計

描述檔為/res/layout/main.xml,相關程式碼如下所示:

<com.google.android.maps.MapView

android:id="@+id/map"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:apiKey="0ZwL91MFSujDqqn4vows4Cwmv2Wej5VN6D7LL_g"

android:clickable="true"

/>

其中 android:apiKey屬性的值是填入前一節中所得到的 Google地圖開發金鑰,

要注意的是當於模擬器上測試完畢,欲於實機上測試時,需記得使用正確的

Google地圖開發金鑰。而 android:clickable屬性則是讓這個介面元件可接受點擊

(Click)事件,如此使用者才能拖拉地圖。

9.3.3 Java程式碼的撰寫

最後一個步驟是撰寫相關的 Java程式碼。相關的 Java程式碼內容如下所示:

1 public class AsiaUniversity extends MapActivity {

2 private MapView map;

3 private MapController mc;

4

5 @Override

6 public void onCreate(Bundle savedInstanceState) {

7 super.onCreate(savedInstanceState);

8 setContentView(R.layout.main);

9 map = (MapView)findViewById(R.id.map);

10 mc = map.getController();

11 GeoPoint asiaU = new GeoPoint(24049178, 120687209);

12 mc.animateTo(asiaU);

13 map.setSatellite(true);

14 map.setBuiltInZoomControls(true);

15 }

16

17 @Override

18 protected boolean isRouteDisplayed() {

19 return false;

20 }

21 }

首先,由於這個 Activity會使用到 MapView介面元件[7],這個 Activity應該繼

承MapActivity,它幫我們實作了一些顯示 MapView必要的程式碼。第 9行利用

讀者已經相當熟悉的 findViewByid 方法取得 MapView物件。第 10 行則利用

getController方法取得這個MapView的MapController 物件,MapController類別

提供了許多方法讓我們去控制地圖[7],其中在第 12行我們使用了 animateTo 方

法,使得給定的座標能夠出現在地圖的中央,座標則是使用 GeoPoint 類別來表

示,GeoPoint 建構子的第一個參數是緯度,第二個參數是經度。程式中我們設定

地圖是以亞洲大學為中心呈現。第 13行,程式利用 setSatellite方法將地圖設成

衛星模式,其它類似的方法還包括 setTraffic 與 setStreetView[7]。最後第 14行則

是設定讓地圖出現內建的放大/縮小工具。最後要注意的是,實作MapActivity類

別的類別必須實作 isRouteDisplayed 方法。關於這些類別與方法的使用,可參閱

網站[7]的資料。程式的執行結果如下圖所示,從左側的圖可發現台灣確實位於

地圖的中央,若進一步利用縮放工具調整大小,讀者可發現亞洲大學確實位於地

圖的中央。

9.4 Google地圖進階應用

如果我們只是單純地使用 MapView來顯示地圖資訊,那麼跟直接使用 Google地

圖是一樣的,為了讓自己的應用程式不只是單純地顯示 Google地圖所提供的資

訊,而能顯示出 Google 地圖所沒有提供的資訊,例如附近的停車場、加油站等,

在一節中我們將學習如何在地圖上『畫』出我們想要的資訊。

如果我們想在地圖上畫一些記號,又不想破壞地圖本身,我們該怎麼做呢?我們

可以拿一個透明投影片放在地圖上,額外的記號都畫在透明投影片上。Google

地圖也是採用這樣的做法,我們可以準備好數個透明畫布,稱為 Overlay,把每

個 Overlay畫上記號後,覆蓋在地圖上即可顯示我們想顯示的資訊。

在這一節中我們會設計出三個 Overlay,第一個 Overlay是在使用者最新位置上

畫上記號,第二個 Overlay是將附近幾個重要景點畫上記號,第三個 Overlay是

在地圖上畫上一條直線。讀者可先引進光碟中『\程式範例

\Chapter9\AsiaUniversity2』這個專案,筆者先將程式碼列出,稍後才做深入的探

討:

1 public class AsiaUniversity extends MapActivity {

2 private MapView map;

3 private MapController mc;

4 private MyLocationOverlay mOverlay1;

5 private MarkerOverlay mOverlay2;

6 private DrawOverlay mOverlay3;

7

8 @Override

9 public void onCreate(Bundle savedInstanceState) {

10 super.onCreate(savedInstanceState);

11 setContentView(R.layout.main);

12

13 map = (MapView)findViewById(R.id.map);

14 mc = map.getController();

15 mc.setZoom(17);

16 map.setSatellite(true);

17 map.setBuiltInZoomControls(true);

18

19 List<Overlay> overlays;

20 overlays = map.getOverlays();

21

22 mOverlay1 = new MyLocationOverlay(this, map);

23 mOverlay1.runOnFirstFix(new Runnable() {

24 public void run() {

25 mc.animateTo(mOverlay1.getMyLocation());

26 }

27 });

28 overlays.add(mOverlay1);

29

30 Drawable marker;

31 marker = getResources().getDrawable(R.drawable.asiaicon);

32 mOverlay2 = new MarkerOverlay(marker, this);

33 overlays.add(mOverlay2);

34

35 mOverlay3 = new DrawOverlay();

36 overlays.add(mOverlay3);

37 }

38

39 @Override

40 protected void onResume() {

41 super.onResume();

42 mOverlay1.enableMyLocation();

43 }

44

45 @Override

46 protected void onStop() {

47 mOverlay1.disableMyLocation();

48 super.onStop();

49 }

50

51 @Override

52 protected boolean isRouteDisplayed() {

53 return false;

54 }

55 }

56

57 class MarkerOverlay extends ItemizedOverlay<OverlayItem> {

58

59 Context mCtx;

60

61 private List<OverlayItem> items = new ArrayList<OverlayItem>();

62

63 public MarkerOverlay(Drawable defaultMarker, Context mCtx) {

64 super(boundCenterBottom(defaultMarker));

65 this.mCtx = mCtx;

66 items.clear();

67 items.add(new OverlayItem(new GeoPoint(24045848, 120686010),

68 "資訊大樓", null));

69 items.add(new OverlayItem(new GeoPoint(24047179, 120686011),

70 "管理大樓", null));

71 items.add(new OverlayItem(new GeoPoint(24046066, 120686665),

72 "行政大樓", null));

73 populate();

74 }

75

76 @Override

77 protected OverlayItem createItem(int i) {

78 return items.get(i);

79 }

80

81 @Override

82 public int size() {

83 return items.size();

84 }

85

86 @Override

87 protected boolean onTap(int pIndex) {

88 Toast.makeText(mCtx, "此處是" + items.get(pIndex).getTitle(),

89 Toast.LENGTH_SHORT).show();

90 return true;

91 }

92 }

93

94 class DrawOverlay extends Overlay {

95 GeoPoint gp1, gp2;

96

97 public DrawOverlay() {

98 gp1 = new GeoPoint(24046066, 120686665);

99 gp2 = new GeoPoint(24045848, 120686010);

100 }

101

102 @Override

103 public void draw(Canvas canvas, MapView mapView,

104 boolean shadow) {

105 Projection projection = mapView.getProjection();

106 Point p1 = new Point();

107 Point p2 = new Point();

108 projection.toPixels(gp1, p1);

109 projection.toPixels(gp2, p2);

110

111 Paint paint = new Paint();

112 paint.setColor(Color.RED);

113 canvas.drawLine(p1.x, p1.y, p2.x, p2.y, paint);

114 }

115 }

經過 9.3 節的說明,讀者應該已瞭解 13~17 行的程式,唯一要解說的是第 15行,

MapController類別所提供的 setZoom 方法,可接收值從 1~21的整數,愈接近 1

代表顯示的區域愈大,資料愈粗略;反之,愈接近 21代表顯示的區域愈小,資

料愈詳盡。第 19行我們宣告了一個動態陣列,每個陣列的元素是一個 Overlay

物件[7],一個 Overlay物件就可以視為一張透明畫布。接著在第 20 行,程式利

用MapView類別的 getOverlays 取得動態陣列的物件實體,一開始這個動態陣列

是空的,沒有任何的元素,我們可以利用 add 方法幫這個動態陣列增加元素,在

這個範例程式中我們會增加三個元素,亦即會增加三張透明畫布,這三張透明畫

布會分別做以下的事情:

於目前位置上畫上記號。

將附近幾個重要景點畫上記號。

在地圖上畫上一條直線。

最後當透明畫布畫好後,只要將透明畫布覆蓋在地圖上,我們便能於地圖上顯示

客製化的資訊。下面我們將一一說明每個畫布的設計方法。

9.4.1 於目前位置上畫上記號

Google地圖函式庫提供了一個MyLocationOverlay類別來幫助我們於手機使用者

所在位置上畫上記號。第 22行我們建立了一個 MyLocationOverlay物件,建構

子需要兩參數,第一個參數需要填入一個 Context 物件,填入這個 Activity即可,

第二個參數是需要一個 MapView 物件,將之前利用 findViewById 方法取得的

MapView物件填入即可。

第 23行,我們呼叫了MyLocationOverlay類別的 runOnFirstFix方法,runOnFirstFix

是用來設定當新的位置資訊獲得時,程式應該做些什麼事,runOnFirstFix 方法需

要一個 Runnable物件當參數,實作 Runnable 介面的類別需要實作 run方法,當

程式獲得新的位置時,就會產生一個執行緒去執行 run方法內的程式,程式中我

們設定當新的位置獲得時,會移動地圖使得使用者的最新位置是位於螢幕的正中

央。由於MyLocationOverlay類別繼承了 Overlay類別,因此最後第 28 行我們將

mOverlay1 這個MyLocationOverlay物件加進動態陣列裡,如此地圖上就覆蓋了

一張透明畫布。MyLocationOverlay類別會在透明畫布上畫上一個藍色小圓圈代

表使用者的目前位置,如下圖所示(圖中只顯示了一張透明畫布):

此外,這個類別會試著從定位提供者(不管是 GPS 或基地台)獲取位置資訊,為了

節省電源,我們在 onResume方法內呼叫 enableMyLocation 方法,如此程式就會

隨時更新位置資訊,而於 onStop 方法內呼叫 disableMyLocation 方法以便停止位

置更新,最後要提醒讀者的是:別忘了於 AndroidManifest.xml 去聲明希望得到

ACCESS_FINE_LOCATION 與 ACCESS_COARSE_LOCATION 的許可。

9.4.2將附近幾個重要景點畫上記號

在第二張透明畫布裡,我們希望能幫一些重要景點標上記號,我們是利用 Google

地圖函式庫所提供的 ItemizedOverlay類別來達成,在程式 57~92行的地方我們

定義了MarkerOverlay類別,其繼承並實作了 ItemizedOverlay類別,類別內需要

一個 OverlayItem 的串列,程式第 61行,我們利用 ArrayList 類別產生這個動態

串列,串列中的每一個元素即代表一個景點。

程式在第63~74行的地方定義了MarkerOverlay的建構子,建構子需要兩個參數,

第一個參數是一個 Drawable物件,即我們欲顯示於地圖上的圖示,第二個是一

個 Context 物件,這是之後 Toast 介面元件需要用到的參數。在第 64 行,程式呼

叫了 ItemizedOverlay的建構子,其需要一個 Drawable物件當參數,這個 Drawable

物件必須有明確的邊界,ItemizedOverlay類別提供了 boundCenter方法與

boundCenterBottom方法讓我們調整Drawable物件的邊界。接著67~72行的地方,

我們幫 OverlayItem 加上三個元素,亦即三個景點,OverlayItem 物件的建構子需

要三個參數,第一個參數是景點的座標,第二個參數是景點的標題,第三個參數

是景點的片斷說明。加上三個元素後,我們必須呼叫 ItemizedOverlay 類別所提

供的 populate方法,其會呼叫 createItem方法,如此我們才能真正建立景點圖示。

實作 ItemizedOverlay類別的類別需實作 createItem 方法與 size方法,讀者可自行

參閱 76~84行的程式碼。此外程式於 86~91 行還覆寫了 onTap方法,這個方法是

用來設定當使用者輕敲景點圖示後,應該執行的動作,我們利用 Toast 介面元件

來顯示景點的名稱。最後要說明的是,如果需要顯示兩個圖示,例如一個是便利

商店的圖示,另一個是速食店的圖示,只要準備兩張透明畫布即可。下面為只顯

示第二張透明畫布的執行結果:

9.4.3在地圖上畫上一條直線

有時候我們希望能在地圖上畫上各式各樣的圖形,例如圓形、梯形等,在一節中

我們將示範如何在地圖上畫上一條直線,至於其它的圖形請讀者自行參閱相關類

別的說明文件。

要在地圖上畫圖可按下面的步驟進行,首先定義一個繼承 Overlay類別的類別,

並覆寫 draw方法就可以了。程式在第 94行定義了 DrawOverlay類別,其繼承了

Overlay類別,而 Overlay類別裡的 draw方法有多重定義,我們覆寫了下面這個

定義:

void draw(Canvas canvas, MapView mapView, boolean shadow)

其中 Canvas 物件就當於畫布[8],我們可以利用其所提供的方法(Methods)在上面

畫上一些圖案,程式在第 113行利用 Canvas 類別的 drawLine方法畫出直線,

drawLine方法需要五個參數,第五個參數是畫筆,程式在第 111行宣告了一個畫

筆物件,並且在第 112行將畫筆設成紅色。

drawLine方法的第一個與第二個參數是起點的 X軸與 Y軸座標,第三個與第四

個參數則是終點的 X軸與 Y軸座標,這裡所謂的 X 軸與 Y 軸座標是指『螢幕』

的 X軸與 Y軸座標,如果螢幕大小為 320480,則左上角為(0, 0),右下角為(320,

480),如果現在我們現在想在行政大樓與資訊大樓這兩個景點畫上直線,該如何

把經緯度轉換成螢幕座標呢?我們可利用Google地圖函式庫所提供的 Projection

介面,首先利用MapView 類別的 getProjection 取得 Projection 物件,接著再利用

toPixels 方法,就可將經緯度轉換成螢幕座標。

三張透明畫布都準備好後,只要將這幾張畫布加入 Overlay串列即可將這些透明

畫布疊在地圖上,最後執行結果如下圖所示:

9.5 Google Maps Android API v2

9.5.1開發金鑰的取得

所有關於 v2 的說明讀者可參考[9],首先我們一樣利用『keytool -list -v -keystore

mykeystore』查看憑證檔的資訊,然而我們需要的是 SHA1 而不再是 MD5,取得

SHA1 之後,請讀者利用自己的 Gmail 帳號進入 Google APIs Console 並建立一個

新的專案,如下圖所示:

進入 Services 設定選定的 Project 需要哪些服務,將 Google Maps Android API v2

打開,如下圖所示:

接著進入 API Access,點選 Create new Android key,如下圖所示:

於輸入框內輸入 SHA1,需注意的是 SHA1 後必須跟著一個分號「;」,分號後則

必須加上 Package Name。

經過上面的步驟我們便可順利取得我們所需要的 API Key。

9.5.2本地端應用程式與環境設定

在本地端我們首先要確認我們是否有安裝 Google Play services,我們首先開啓

SDK Manager,觀看是否有安裝(位於 Extras 資料夾),若無安裝則進行安裝動

作。

安裝完畢後,我們就可以匯入所需的專案,點選 FileImport後選擇 Android

Existing Android Code into Workspace。匯入

(SDK_Home)/extras/google/google_play_services/libproject/google-play-services_lib

專案,當專案出現在 Project Explorer子視窗時,就代表成功匯入了。

緊接著我們就可以建立我們自己的地圖應用專案,提醒一下的是,專案的 Package

Name必須和之前取得 API Key所輸入的 Package Name 相同,建立好專案後,

AndroidManifest.xml 有幾個地方需要修改:

1 <?xml version="1.0" encoding="utf-8"?>

2 <manifest xmlns:android="http://schemas.android.com/apk/res/android"

3 package="lincyu.example.googlemapv2"

4 android:versionCode="1"

5 android:versionName="1.0" >

6

7 <uses-sdk

8 android:minSdkVersion="8"

9 android:targetSdkVersion="17"/>

10

11 <permission

12 android:name="lincyu.example.googlemapv2.permission.MAPS_RECEIVE"

13 android:protectionLevel="signature"/>

14 <uses-permission

15 android:name="lincyu.example.googlemapv2.permission.MAPS_RECEIVE"/>

16 <uses-permission

17 android:name="android.permission.INTERNET"/>

18 <uses-permission

19 android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

20 <uses-permission

21 android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>

22 <uses-permission

23 android:name="android.permission.ACCESS_COARSE_LOCATION"/>

24 <uses-permission

25 android:name="android.permission.ACCESS_FINE_LOCATION"/>

26 <uses-feature

27 android:glEsVersion="0x00020000" android:required="true"/>

28

29 <application

30 android:allowBackup="true"

31 android:icon="@drawable/ic_launcher"

32 android:label="@string/app_name"

33 android:theme="@style/AppTheme" >

34 <activity

35 android:name="lincyu.example.googlemapv2.MainActivity"

36 android:label="@string/app_name" >

37 <intent-filter>

38 <action android:name="android.intent.action.MAIN" />

39 <category android:name="android.intent.category.LAUNCHER" />

40 </intent-filter>

41 </activity>

42 <meta-data

43 android:name="com.google.android.maps.v2.API_KEY"

44 android:value="AIzaSyCKPTixho4O5db47Xnncq7kUko_xTGuVbM"/>

45 </application>

46 </manifest>

在官方網站中,有一個簡單的範例讓開發者可以快速地測試 Google Maps 是否運

作正常,然而該範例需要支援 API Level 12 (以上)的實機才能執行,因此筆

者於下一節中另外提供了一個簡單範例,於 API Level 8 的實機上就可以執行。

9.5.3範例

1 package lincyu.example.googlemapv2;

2

3 import android.app.Activity;

4 import android.os.Bundle;

5 import android.view.Menu;

6

7 import com.google.android.gms.maps.GoogleMap;

8 import com.google.android.gms.maps.GoogleMapOptions;

9 import com.google.android.gms.maps.MapView;

10 import com.google.android.gms.maps.model.LatLng;

11 import com.google.android.gms.maps.model.MarkerOptions;

12

13 public class MainActivity extends Activity {

14

15 MapView mapview;

16 GoogleMap googlemap;

17

18 @Override

19 protected void onCreate(Bundle savedInstanceState) {

20 super.onCreate(savedInstanceState);

21 setContentView(R.layout.activity_main);

22 mapview = (MapView) findViewById(R.id.map);

23 mapview.onCreate(savedInstanceState);

24 }

25

26 @Override

27 public void onPause() {

28 super.onPause();

29 mapview.onPause();

30 }

31

32 @Override

33 public void onResume() {

34 super.onResume();

35 mapview.onResume();

36

37 googlemap = mapview.getMap();

38 MarkerOptions mo = new MarkerOptions();

39 LatLng ll = new LatLng(24.79702,120.998096);

40 mo.position(ll);

41 mo.title("NCTU");

42 googlemap.addMarker(mo);

43 }

44

45 @Override

46 public void onDestroy() {

47 super.onDestroy();

48 mapview.onDestroy();

49 }

50

51 @Override

52 public boolean onCreateOptionsMenu(Menu menu) {

53 // Inflate the menu; this adds items to the action bar if it is present.

54 getMenuInflater().inflate(R.menu.activity_main, menu);

55 return true;

56 }

57 }

9.6 摘要

本章我們介紹了位置資訊的取得方式與 Google地圖函式庫的使用。Android系統

目前提供了兩種定位方式:GPS 與基地台,我們學習了 LocationManager類別的

使用,此外我們也學會如何於 AndroidManifest.xml 聲明希望得到 GPS 或基地台

所提供的位置資訊。Google地圖函式庫也是本章的重點之一,首先我們學習了

Google地圖開發金鑰的取得方法,接著學習了 MapView類別的使用,而為了讓

地圖能呈現自己設計的地標等資訊,我們也學習了透明畫布的概念與設計方法。

定位與 Google地圖的結合產生了許多新的應用,未來一定會有更多的位置感知

服務被人們所使用。

9.7 作業

1. 撰寫一個台灣大專院校地圖查詢應用程式。這個應用程式一開始會利用列表

介面元件顯示數所大學的名稱,使用者點選下去後,會出現該校的地圖。(提

示:可利用 Google Earth 查詢相關地點的經緯度)

2. 於地圖上標出兩種圖示,例如便利商店的圖示與速食店的圖示。

3. 於地圖上畫上各式個樣的圖形。

9.8 參考資料

[1] Global Positioning System - Wikipedia, the free encyclopedia,

http://en.wikipedia.org/wiki/Global_Positioning_System

[2] Permissions | Android Developers,

http://developer.android.com/guide/topics/security/permissions.html

[3] Manifest.permission | Android Developers,

http://developer.android.com/reference/android/Manifest.permission.html

[4] LocationManager | Android Developers,

http://developer.android.com/reference/android/location/LocationManager.html

[5] LocationListener | Android Developers,

http://developer.android.com/reference/android/location/LocationListener.html

[6] Criteria | Android Developers,

http://developer.android.com/reference/android/location/Criteria.html

[7] Google Maps Android API v2,

https://developers.google.com/maps/documentation/android/start#installing_the_goog

le_maps_android_v2_api

[8] Canvas | Android Developers,

http://developer.android.com/reference/android/graphics/Canvas.html

[9] Google Maps Android API v2 - Google Developers,

https://developers.google.com/maps/documentation/android/

top related