Android 智慧型手機程式設計 Android Google Maps 建國科技大學資管系饒瑞佶 2012/4 V1 2012/8 V2 2013/5 V3 V4 2014/10 v5
Google Maps Reference: https://developers.google.com/maps/documentation/android/start#creating _an_api_project 取得 Google Map API Step1: 先進入 JDK 目錄
Google Maps 透過 keytool.exe 建立認證指紋 SHA1 需要 debug_keystore 路徑 ( 每個作業系統位置不同 )
新版 IDE 已經列出 SHA1 碼 下一步驟用
重做一次認證 輸入 keytool -list -v -keystore "C:\Documents and Settings\Administrator\.android\debug.keystore 預設密碼是 android 來自 Eclipse 內的 default keystore 檔案位置要看作業系統
Google Maps 產生認證指紋 要的是 SHA1 編碼
Google Maps V1 進入 Google Map API Key 申請頁面 http://code.google.com/intl/zh-tw/android/add-ons/googleapis/maps-api-signup.html ( 不再使用 ) 目前不用 輸入認證指紋碼
Google Maps Android API Version 1 開發法 已經停用 http://cheng-min-itaiwan.blogspot.tw/2013/04/google-mapsandroid-api-v2-android.html 新版 API 需要配合 Google Play Services
進入申請頁面 https://developers.google.com/maps/documentation/javascript /tutorial#api_key 需要登入 Google
Google Maps 進入 Google Map API Key 申請頁面 Reference: 這裡有做法 https://developers.google.com/maps/documentation/android/start#creating _an_api_project
使用 SHA1 與 package Name
result
使用 API Key
首先將 API key 加入 AndroidManifest.xml <meta-data android:name="com.google.android.maps.v2.api_key" android:value="your_api_key"/>
加入使用權限
加入使用權限到 AndroidManifest.xml 改成自己的 package name <permission android:name="ctu.rcjao.helloandroid.permission.maps _RECEIVE android:protectionlevel="signature"/> <uses-permission android:name="ctu.rcjao.helloandroid.permission.maps _RECEIVE"/> 改成自己的 package name
加入使用權限到 AndroidManifest.xml
Use permission <uses-permission android:name="android.permission.internet"/> <uses-permission android:name="android.permission.access_network_state"/> <uses-permission android:name="android.permission.write_external_storage"/> <uses-permission android:name="com.google.android.providers.gsf.permission.read_gservices"/ > <!-- The following two permissions are not required to use Google Maps Android API v2, but are recommended. --> <uses-permission android:name="android.permission.access_coarse_location"/> <uses-permission android:name="android.permission.access_fine_location"/>
再加入 use-feature
uses-feature <uses-feature android:glesversion="0x00020000" android:required="true"/>
最後加入地圖
Xml 檔案 加入下列 code <fragment xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/map" android:layout_width="match_parent" android:layout_height="match_parent" class="com.google.android.gms.maps.supportmapfragment"/>
JAVA 程式 public class Map extends FragmentActivity { @Override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.map); } }
執行 結果不能跑, 有錯誤!
打開 SDK 中的 SDK Manager, 找到 Extras 並安裝 Android Support Library 及 Google Play Services 兩個項目
匯入 Google Play Services 函式庫 File > Import > Android > Existing Android Code Into Workspace 路徑 : android-sdk\extras\google\google_play_services\libproject\google-play-services_lib
匯入完成
專案要加入這個 library
還有要開 google Maps Android API v2 不是 v3 這個喔!
執行 還是不行 最新版的還要再加上 <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
執行 需要實體手機 過程中可能需要更新 Google Play
Google Maps 實體手機
發佈到 AVD2.2
AVD 設定 使用 SDK 4.0.3
Download Google Play Service APK 由 http://d-h.st/1mr 下載 < com.android.vending-4.0.25.apk> 將下載的安裝到 AVD
有了, 但是還是不行!
設定模擬器顯示地圖 首先在模擬器中安裝 Google Play Service 修改 System 目錄之檔案權限, 允許寫入權限 adb shell mount -o remount,rw -t yaffs2 /dev/block/mtdblock0 /system adb shell chmod 777 /system/app adb push GoogleLoginService.apk /system/app/ adb push GoogleServicesFramework.apk /system/app/ adb push Phonesky.apk /system/app/
adb install Maps_6.12.0.apk adb install com.google.android.gms-3.apk Google Play Service 要夠新 漫長的等待.
如果出現要 update Google Play Service!
通常是失敗, 因為不支援線上更新 下載新的 APK 重裝就可以了 這裡我們裝 com.google.android.gms- APK4Fun.com.apk 如果無法安裝, 請先將 avd 內的 Google Play Services 移除
result 如果是灰底, 沒有地圖 1. 請檢查 API KEY 2. 請重新調整 AVD 參數, 例如放大 Internal Storage 或是 SD card size, 然後重新發布程式
在 Google Maps 上加標示 SDK 至少要 4.0 @SuppressLint("NewApi") public class Main extends FragmentActivity { // 要顯示的座標 static final LatLng CTU = new LatLng(24.06660656734983, 120.54975986480713); private GoogleMap map; @SuppressLint("NewApi") @Override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); // 取得地圖物件 map = ((MapFragment) getfragmentmanager().findfragmentbyid(r.id.map)).getmap(); // 建立紅色氣球標示 Marker mk = map.addmarker(new MarkerOptions().position(CTU).title(" 建國科技大學 ").snippet(" 資管系 ")); // 設定縮放大小是 16, 且將標示點放在正中央 map.movecamera(cameraupdatefactory.newlatlngzoom(ctu, 16)); } } 如果更早的 SDK 也要可以用 map = ((SupportMapFragment) getsupportfragmentmanager().findfragmentbyid(r.id.map)).getmap();
result
修改 UI 的 xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" 原地圖 android:orientation="vertical" > 設定 layout_weight <fragment android:layout_weight="1" android:id="@+id/map" android:layout_width="match_parent" android:layout_height="match_parent" class="com.google.android.gms.maps.supportmapfragment"/> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_weight="0" > <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text=" 地圖 " /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text=" 衛星 " /> </LinearLayout> </LinearLayout> 加入這段來切換地圖種類
result
Oncreate 中加入下列 code Button btn_normalview=(button)findviewbyid(r.id.button1); Button btn_satellitetview=(button)findviewbyid(r.id.button2); btn_normalview.setonclicklistener(new Button.OnClickListener() { public void onclick(view arg0) { map.setmaptype(googlemap.map_type_normal); // 顯示地圖模式 } }); btn_satellitetview.setonclicklistener(new Button.OnClickListener() { public void onclick(view arg0) { 星模式 } }); map.setmaptype(googlemap.map_type_satellite); // 顯示衛
地址轉座標功能將下列 code 加入 UI (fragment 前 ) <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" android:layout_weight="0" > <EditText android:id="@+id/edittext1" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" > <requestfocus /> </EditText> <Button android:id="@+id/button3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text=" 顯示 " /> <TextView android:id="@+id/textview1" android:text="" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout>
地址轉座標功能 Button btn_address_to_geo=(button)findviewbyid(r.id.button3); btn_address_to_geo.setonclicklistener(new Button.OnClickListener() { public void onclick(view arg0) { try { EditText inputaddress=(edittext)findviewbyid(r.id.edittext1); tv1=(textview)findviewbyid(r.id.textview1); Geocoder geocoder = new Geocoder(Main.this, Locale.getDefault()); List<Address> georesults = geocoder.getfromlocationname(inputaddress.gettext().tostring(), 5); while (georesults.size()==0) { georesults = geocoder.getfromlocationname(inputaddress.gettext().tostring(), 5); } if (georesults.size()>0) { Address addr = georesults.get(0); Double latitude = addr.getlatitude() * 1E6; Double longitude = addr.getlongitude() * 1E6; tv1.settext(latitude + "/" + longitude); } } catch (Exception e) { tv1.settext(" 轉換失敗 "); } } });
result 實機 模擬器
建立 showlocation 方法 // 顯示座標點 private void showloaction(double d,double e,string title, String snip){ LatLng CTU = new LatLng(d, e); // 取得地圖物件 map = ((SupportMapFragment) getsupportfragmentmanager().findfragmentbyid(r.id.map)).getm ap(); // 建立紅色氣球標示 Marker mk = map.addmarker(new MarkerOptions().position(CTU).title(title).snippet(snip)); // 設定縮放大小是 16, 且將標示點放在正中央 map.movecamera(cameraupdatefactory.newlatlngzoom(ctu, 16)); }
呼叫 showlocation 方法 showloaction(24.06660656734983, 120.54975986480713," 建國科技大學 "," 資管系 ");
呼叫 showlocation 方法 Button btn_address_to_geo=(button)findviewbyid(r.id.button3); btn_address_to_geo.setonclicklistener(new Button.OnClickListener() { public void onclick(view arg0) { try { EditText inputaddress=(edittext)findviewbyid(r.id.edittext1); tv1=(textview)findviewbyid(r.id.textview1); Geocoder geocoder = new Geocoder(Main.this, Locale.getDefault()); List<Address> georesults = geocoder.getfromlocationname(inputaddress.gettext().tostring(), 5); while (georesults.size()==0) { georesults = geocoder.getfromlocationname(inputaddress.gettext().tostring(), 5); } if (georesults.size()>0) { Address addr = georesults.get(0); Double latitude = addr.getlatitude() * 1E6; Double longitude = addr.getlongitude() * 1E6; tv1.settext(latitude + "/" + longitude); showloaction((double)(addr.getlatitude()), (double)(addr.getlongitude()),inputaddress.gettext().tostring(),""); } } catch (Exception e) { tv1.settext(" 轉換失敗 "); } } });
AVD 多數執行有問題, 最好使用手機測試 輸入地址 轉換地址成座標 座標 顯示座標點
不用註冊 Google Maps API 也可以 使用 Intent
顯示 Google Maps 地圖 public class MapIntent extends Activity { @Override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); Uri uri=uri.parse("geo:24.06660656734983,120.54975986480713"); Intent it=new Intent(Intent.ACTION_VIEW,uri); startactivity(it); } }
利用 Intent 玩 Google Maps
Google Maps street view Uri uri=uri.parse("google.streetview:cbll=46.813 812,-71.207378&cbp=1,99.56,,1,- 5.27&mz=21"); 只支援美加地區 AVD 是看不到的!
更多 Google Maps 路徑規劃 Uri uri = Uri.parse("http://maps.google.com/maps? f=d&saddr=startlat%20startlng&daddr=endlat%20endlng& hl=en"); Intent it = new Intent(Intent.ACTION_VIEW, uri); startactivity(it); //where startlat, startlng, endlat, endlng are a long with 6 decimals like: 50.123456
整合 GPS
加入對應的 xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <TextView android:id="@+id/textview1" android:text="" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <fragment android:layout_weight="1" android:id="@+id/map" android:layout_width="match_parent" android:layout_height="match_parent" class="com.google.android.gms.maps.supportmapfragment"/> </LinearLayout>
AndroidManiFest.xml 需要開放以下權限 <uses-permission android:name="android.permission.access_coarse_l OCATION"></uses-permission> <uses-permission android:name="android.permission.access_fine_loc ATION"></uses-permission>
偵測是否開啟 GPS private LocationManager mlocationmanager; // 如果沒有開啟 GPS--------------------- mlocationmanager=(locationmanager)(this.getsystemservice(context. LOCATION_SERVICE)); if(mlocationmanager.isproviderenabled(locationmanager.gps_prov IDER) mlocationmanager.isproviderenabled(locationmanager.network_p ROVIDER)){ }else{ // 到系統開啟 GPS 與 WIFI 服務的畫面 startactivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)); } //-------------------- 如果沒有開啟 GPS
Android GPS 運作方式 使用 LocationManager: 判定是否有提供定位服務 ( 硬體 GPS 或 WIFI) 建立 LocationProvider, 設定定位參數, 並同時透過 LocationManager 取得座標 ( 硬體 軟體,GPS 或是 WiFi) 設定 LocationManager 的 Listener 事件, 偵測事件的改變 MapController 負責控制 Google Maps
GPS 訊號抓取主體 宣告 GPS 訊號擷取主體 沒有 GPS 時觸發
補充說明 mlocationmanager =(LocationManager)(this.getSystemService(Context.LOCATION_SERVICE)); if(mlocationmanager.isproviderenabled(locationmanager.gps_provider) mlocationmanager.isproviderenabled(locationmanager.network_provider)){ }else{ // 到達系統開啟 GPS 與 WIFI 服務的畫面 startactivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)); } //-------------------- 如果沒有開啟 GPS 啟動 GPS 如果沒有啟動使用 GPS 服務, 將跳至設定畫面 /* Provider 初始化 */ getlocationprivider(); 要求 GPS 提供服務 /* 設定事件的 Listener */ mlocationmanager.requestlocationupdates(mlocationprivider, 2000, 0, mlocationlistener); 設定 GPS 監聽服務 if(mlocation!=null) // 第一次顯示 { // 取得速度 double speed=mlocation.getspeed()/1000*60*60; // 原單位是 m/s double altitude = mlocation.getaltitude(); tv_show_gps.settext(" 緯度 :" + formatgeo(mlocation.getlatitude()) + " 經度 :" + formatgeo(mlocation.getlongitude()) + " 海拔 :" + altitude + " m 速度 :" + formatspeed(speed) + "km/h"); } TextView tv_show_gps; private ProgressDialog MyDialog; private String mlocationprivider=""; private Location mlocation; 宣告物件 如果有 GPS 訊號, 顯示到畫面上
tv_show_gps=(textview)findviewbyid(r.id.textview1); createcancelprogressdialog(" 定位中 "," 定位中.. 請稍待!"," 取消 "); try{ // 如果沒有開啟 GPS--------------------- mlocationmanager =(LocationManager)(this.getSystemService(Context.LOCATION_SERVICE)); if(mlocationmanager.isproviderenabled(locationmanager.gps_provider) mlocationmanager.isproviderenabled(locationmanager.network_provider)){ }else{ // 到系統開啟 GPS 與 WIFI 服務的畫面 startactivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)); } //-------------------- 如果沒有開啟 GPS mlocationmanager = (LocationManager)getSystemService(Context.LOCATION_SERVICE); // Provider 初始化 getlocationprivider(); // 設定 GPS 的 Listener mlocationmanager.requestlocationupdates(mlocationprivider, 2000, 0, mlocationlistener); if(mlocation!=null) // 第一次顯示 { // 取得速度 double speed=mlocation.getspeed()/1000*60*60; // 原單位是 m/s double altitude = mlocation.getaltitude(); tv_show_gps.settext(" 緯度 :" + formatgeo(mlocation.getlatitude()) + " 經度 :" + formatgeo(mlocation.getlongitude()) + " 海拔 :" + altitude + " m 速度 :" + formatspeed(speed) + "km/h"); }
}catch(exception e){ new AlertDialog.Builder(GPS.this).setTitle(" 系統訊息 ").setmessage(" 無法取得 GPS 座標 ").setpositivebutton(" 確認 ",new DialogInterface.OnClickListener() { public void onclick(dialoginterface dialog, int which) { // TODO Auto-generated method stub MyDialog.dismiss(); } }).show(); }
// 取得 LocationProvider public void getlocationprivider() { Criteria mcriteria01 = new Criteria(); mcriteria01.setaccuracy(criteria.accuracy_fine); mcriteria01.setaltituderequired(true); // 需要高度 mcriteria01.setbearingrequired(false); mcriteria01.setcostallowed(true); mcriteria01.setpowerrequirement(criteria.power_low); mlocationprivider = mlocationmanager.getbestprovider(mcriteria01, true); mlocation = mlocationmanager.getlastknownlocation(mlocationprivider); }
// 偵測位置改變 public final LocationListener mlocationlistener = new LocationListener() { public void onlocationchanged(location location) { // 取得速度 double speed=location.getspeed()/1000*60*60; // 原單位是 m/s double altitude = location.getaltitude(); tv_show_gps.settext(" 緯度 :" + formatgeo(location.getlatitude()) + " 經度 :" + formatgeo(location.getlongitude()) + " 海拔 :" + altitude + " m 速度 :" + formatspeed(speed) + "km/h"); MyDialog.dismiss(); // 結束進度 } public void onproviderdisabled(string provider) { } public void onproviderenabled(string provider) { } public void onstatuschanged(string provider,int status,bundle extras) { } }; //----------------- 偵測位置改變
// format GPS 座標的方法 public String formatgeo(double num) { NumberFormat formatter = new DecimalFormat("###.####"); String s=formatter.format(num); return s; } // format speed 的方法 public String formatspeed(double num) { NumberFormat formatter = new DecimalFormat("###.##"); String s=formatter.format(num); return s; }
// 按 BACK 按鍵時關閉程式 @Override public void onbackpressed() { android.os.process.killprocess(android.os.process.mypid()); HelloGPSActivity.this.finish(); } // 產生定位中視窗 private void createcancelprogressdialog(string title, String message, String buttontext) { MyDialog = new ProgressDialog(this); MyDialog.setTitle(title); MyDialog.setMessage(message); MyDialog.setButton(buttonText, new DialogInterface.OnClickListener(){ public void onclick(dialoginterface dialog, int which){ // Use either finish() or return() to either close the activity or just the dialog android.os.process.killprocess(android.os.process.mypid()); HelloGPSActivity.this.finish(); return; } }); MyDialog.show(); // 顯示進度 }
透過 ddms 送出模擬 GPS 座標到 AVD 需要 <uses-permission android:name="android.permission.access_mock_location"/>
取得 GPS 訊號後, 將其顯示到 Google Maps 上
結合前面的 showloaction 方法 在 onlocationchanged 中加入呼叫 記得加入地圖 private GoogleMap map;
稍微修改 showloaction 方法 Marker mk=null; // 顯示座標點 private void showloaction(double d,double e,string title, String snip){ LatLng CTU = new LatLng(d, e); // 取得地圖物件 map = ((SupportMapFragment) getsupportfragmentmanager().findfragmentbyid(r.id.map)).getmap(); // 建立紅色氣球標示 if(mk!= null) mk.remove(); mk = map.addmarker(new MarkerOptions().position(CTU).title(title).snippet(snip)); // 設定縮放大小是 16, 且將標示點放在正中央 map.movecamera(cameraupdatefactory.newlatlngzoom(ctu, 16)); } 移除前一個氣球
result
控制旋轉不觸發 oncreate AndroidMainfast.xml 中設定允許改變 : <uses-permission android:name="android.permission.change_co NFIGURATION" /> AndroidMainfast.xml 中對要攔截旋轉事件的 Activity 加入屬性 : android:configchanges="orientation"
畫路徑 - 從現在的範例修改 LatLng p1; // 出發點 LatLng p2; // 結束點 加入成全域 加入第一個點 if(mlocation!=null) // 第一次顯示 { // 取得速度 double speed=mlocation.getspeed()/1000*60*60; // 原單位是 m/s double altitude = mlocation.getaltitude(); p1 = new LatLng(mLocation.getLatitude(), mlocation.getlongitude()); tv_show_gps.settext( 緯度 : + formatgeo(mlocation.getlatitude()) + " 經度 :" + formatgeo(mlocation.getlongitude()) + " 海拔 :" + altitude + " m 速度 :" + formatspeed(speed) + "km/h"); }
public void onlocationchanged(location location) { // 取得速度 double speed=location.getspeed()/1000*60*60; // 原單位是 m/s double altitude = location.getaltitude(); tv_show_gps.settext(" 緯度 :" + formatgeo(location.getlatitude()) + " 經度 :" + formatgeo(location.getlongitude()) + " 海拔 :" + altitude + " m 速度 :" + formatspeed(speed) + "km/h"); p2 = new LatLng(location.getLatitude(), location.getlongitude()); showloaction((double)(location.getlatitude()), (double)(location.getlongitude())," 目前 GPS 座標點 ",""); p1=p2; MyDialog.dismiss(); } // 結束進度 取得結束點畫線將結束點變成出發點
// 顯示座標點 private void showloaction(double d,double e,string title, String snip){ LatLng CTU = new LatLng(d, e); // 取得地圖物件 map = ((SupportMapFragment) getsupportfragmentmanager().findfragmentbyid(r.id.map)).getmap( ); // 建立紅色氣球標示 if(mk!= null) mk.remove(); 加入畫線 mk = map.addmarker(new MarkerOptions().position(CTU).title(title).snippet(snip)); // 設定縮放大小是 16, 且將標示點放在正中央 //map.movecamera(cameraupdatefactory.newlatlngzoom(ctu, 16)); PolylineOptions line=new PolylineOptions().add(p1,p2).width(5).color(Color.BLUE); map.addpolyline(line); map.movecamera(cameraupdatefactory.newlatlngzoom(p2, 16)); }