在Android上玩转Google Maps






4.70/5 (17投票s)
学习如何在 Android 应用中实现一个位置感知的 Google 地图。
引言
在我的一篇文章 - Android 用户交互和传感器 - 中,我解释了在 Android 应用中实现 GPS 的方法。但是,我没有进一步添加地图。在这篇文章中,我将弥补这一点。你将学习在 Android 应用中设置和实现 Google Maps 的基础知识。通过这些,你将创建一个简单的位置感知应用,它使用 "LocationManager" 类来跟踪用户的位置,然后在用户 Android 设备上的 Google Map 上更新该位置。听起来很有趣?继续阅读...
准备工作
Google Maps Android API v2 是目前负责将 Google Map 置于应用中的 API。它处理对 Google Maps 服务器的访问、显示地图以及响应手势。它还提供了许多用于添加和自定义地图的方法。
在开始在应用中使用 Google Maps 之前,你需要准备两样东西——安装 "Google Maps Android API V2" 本身,以及获取一个 "API 密钥" 来访问 Google Maps Android API V2。
-
安装 Google Maps Android API V2 - Google Maps Android API v2 是作为免费分发的 Google Play services SDK 的一部分提供的。
使用 Android SDK 管理器下载 Google Play services SDK。在 Android SDK 管理器中选择 "Google Play services" 进行安装,如图 1 所示。
图 1:下载和安装 Google Play services SDK有关在不同开发环境中设置 Google Play services 的详细说明,请参阅 Google Play services 文档。
在应用的 manifest 文件中声明使用 Google Play services,如下所示:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.peterleow.madgooglemap" > <application ... <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" /> <!-- ... --> </application> </manifest>
-
获取 API 密钥 - 为了使用 Google Maps Android API V2 访问 Google Maps 服务器,你需要从 "Google APIs Console" 获取一个免费的 API 密钥,并提供你的应用的签名证书及其包名。每个密钥都与特定的证书/包对相关联,并支持无限数量的用户。获取密钥后,将其添加到应用的 manifest 文件中,你就可以在应用中实现 Google Maps 了。
有关获取 API 密钥的详细说明,请参阅此 "文档"。但是,如果你使用 Android Studio 创建应用,那么暂时不必阅读它。过一会儿你就会明白为什么。
获取 API 密钥后,将其添加到应用的 manifest 文件中,如下所示:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.peterleow.madgooglemap" > <application ... <meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="@string/google_maps_key" /> <!-- ... --> </application> </manifest>
创建 Google Map 应用
在 Android Studio 中创建一个新项目,我们称之为 "Follow Me",使用 "Google Maps Activity" 模板,如图 2 所示。
![]() |
图 2:“Google Maps Activity”模板
|
使用 "Google Maps Activity" 模板可以为你节省很多获取 API 密钥的工作。你很快就会发现。
![]() |
图 3:设置 Activity
|
看看 Android Studio 使用 "Google Maps Activity" 模板创建了什么。
-
主 Activity 称为 "MapsActivity",它由 Android Studio 自动设置,并已准备好显示 Google Map,如下所示。
package com.peterleow.followme; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.SupportMapFragment; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.MarkerOptions; public class MapsActivity extends FragmentActivity { private GoogleMap mMap; // Might be null if Google Play services APK is not available. @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_maps); setUpMapIfNeeded(); } @Override protected void onResume() { super.onResume(); setUpMapIfNeeded(); } /** * Sets up the map if it is possible to do so (i.e., the Google Play services APK is correctly * installed) and the map has not already been instantiated.. This will ensure that we only ever * call {@link #setUpMap()} once when {@link #mMap} is not null. * <p> * If it isn't installed {@link SupportMapFragment} (and * {@link com.google.android.gms.maps.MapView MapView}) will show a prompt for the user to * install/update the Google Play services APK on their device. * <p> * A user can return to this FragmentActivity after following the prompt and correctly * installing/updating/enabling the Google Play services. Since the FragmentActivity may not * have been completely destroyed during this process (it is likely that it would only be * stopped or paused), {@link #onCreate(Bundle)} may not be called again so we should call this * method in {@link #onResume()} to guarantee that it will be called. */ private void setUpMapIfNeeded() { // Do a null check to confirm that we have not already instantiated the map. if (mMap == null) { // Try to obtain the map from the SupportMapFragment. mMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)) .getMap(); // Check if we were successful in obtaining the map. if (mMap != null) { setUpMap(); } } } /** * This is where we can add markers or lines, add listeners or move the camera. In this case, we * just add a marker near Africa. * <p> * This should only be called once and when we are sure that {@link #mMap} is not null. */ private void setUpMap() { //mMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE); mMap.addMarker( new MarkerOptions() .position(new LatLng(0, 0)) .title("Marker")); } }
此 Activity 中的主要对象是名为 "mMap" 的 "GoogleMap" 对象。 "GoogleMap" 类是 Google Maps Android API 的主类,是所有与地图相关的方法的入口点。要获取 "GoogleMap" 对象,请调用 "SupportMapFragment" 类的 "getMap()" 方法,如下所示:
mMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMap();
Android Studio 非常有用,提供了详细的注释来解释每一段代码的工作原理。所以请仔细阅读。
-
名为 "activity_maps.xml" 的布局文件包含 "SupportMapFragment" 类的组件,用于在应用中放置地图,如下所示。
<fragment xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/map" tools:context=".MapsActivity" android:name="com.google.android.gms.maps.SupportMapFragment"/>
-
名为 "AndroidManifest.xml" 的文件已设置了使用 Google Map 所需的
权限,如下所示。 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.peterleow.followme" > <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MapsActivity" android:label="@string/title_activity_maps" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" /> <meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="@string/google_maps_key" /> </application> <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 ACCESS_COARSE/FINE_LOCATION 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" /> </manifest>
-
除了最后一个缺失的部分——API 密钥——一切都已准备就绪!导航到 "app/src/debug/res/values/" 并打开名为 "google_maps_api.xml" 的文件。你看到了什么?
图 4:google_maps_api.xmlAndroid Studio 已生成 SHA1 证书指纹并将其附加到链接中,如图 4 所示。将此链接复制并粘贴到浏览器中,然后导航到 "Google APIs Console" 以注册 Google Maps Android API V2(图 5)。
图 5:Google APIs Console同意服务条款并继续到下一个屏幕以创建 API 密钥(图 6)。
图 6:创建 API 密钥图 6 中的文本框应已自动填充 SHA1 证书指纹。如果没有,请从图 4 所示的 "google_maps_api.xml" 文件中复制并粘贴。
点击创建按钮并等待。你将看到一个 API 密钥,如图 7 所示。
图 7:API 密钥生成!将 "google_maps_key" 资源的内容替换为此 API 密钥。
就是这样。你刚刚创建了一个显示 Google Map 的应用。在真实设备上启动它。你看到了什么?
![]() |
图 8:初次外观
|
应用显示了一个默认样式的红色标记,位于地图上的纬度零点和经度零点("position(new LatLng(0, 0))"),正如下面代码片段所示。
private void setUpMap() { mMap.addMarker( new MarkerOptions() .position(new LatLng(0, 0)) .title("Marker")); }
如果单击红色标记,将弹出一个标题框,显示 "Marker"("title("Marker")")(图 9)。
![]() |
图 9:太单调了!
|
太单调了!我们来让它变得更有趣一些,好吗?
首先,尝试使用 "MarkerOptions" 类的各种方法来自定义标记,如下所示:
private void setUpMap() { mMap.addMarker( new MarkerOptions() .position(new LatLng(0, 0)) .snippet("Hello World!") .icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_launcher)) .flat(true) .title("I'm here!")); }
然后将其在真实设备上运行。这次(图 10),红色标记已被 ic_launcher 图标替换("icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_launcher))")。触摸图标将弹出一个小框,显示一个标题("title("I'm here!")")和一个小文本("snippet("Hello World!")")。当你用两根手指在屏幕上向下移动地图时,图标也会倾斜("flat(true)")。
![]() |
图 10:自定义标记选项
|
你还可以将地图类型更改为以下五种选项之一——"MAP_TYPE_HYBRID"、"MAP_TYPE_NONE"、"MAP_TYPE_NORMAL"、"MAP_TYPE_SATELLITE" 或 "MAP_TYPE_TERRAIN"。以下代码将以 "MAP_TYPE_SATELLITE" 模式显示地图,如图 11 所示。
private void setUpMap() { // ... mMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE); }
![]() |
图 11:MAP_TYPE_SATELLITE
|
以下代码将以 "MAP_TYPE_HYBRID" 模式显示地图,如图 12 所示。
private void setUpMap() { // ... mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID); }
![]() |
图 12:MAP_TYPE_HYBRID
|
制作位置感知应用
你已经学习了将 Google Map 放入应用的基本知识。让我们为其添加位置感知功能,以便它可以在地图上显示和更新用户的位置。
将必要的包添加到 "MapsActivity" 中,如下所示。
package com.peterleow.followme; import android.location.Criteria; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.SupportMapFragment; import com.google.android.gms.maps.model.BitmapDescriptorFactory; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.MarkerOptions; public class MapsActivity extends FragmentActivity { //...
在 "MapsActivity" 中创建一个名为 "showCurrentLocation(Location location)" 的方法,如下所示。
private void showCurrentLocation(Location location){ mMap.clear(); LatLng currentPosition = new LatLng(location.getLatitude(),location.getLongitude()); mMap.addMarker(new MarkerOptions() .position(currentPosition) .snippet("Lat: " + location.getLatitude() + ", Lng: " + location.getLongitude()) .icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_peterleow)) .flat(true) .title("I'm here!")); // Zoom in, animating the camera. mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(currentPosition, 18)); }
"showCurrentLocation(Location location)" 方法接收一个 "Location" 对象,其中包含用户当前地理位置数据,如纬度、经度和其他信息。它会在地图上以该 "Location" 对象指示的纬度和经度位置添加一个标记,然后通过调用 "GoogleMap" 对象的 "animateCamera()" 方法来动画移动到该位置。
现在的问题是,应用如何以及从哪里获得位置更新?答案在于 "LocationManager" 类。你在我的文章 Android 用户交互和传感器 中已经学过了。简而言之,Android 系统提供 "LocationManager" 类来访问系统位置服务。你可以通过调用其 "requestLocationUpdates()" 方法并传递一个 "LocationListener" 来请求位置更新。当 Android 设备的定位发生变化或定位服务状态发生变化时,"LocationManager" 会调用 "LocationListener" 的各种回调方法。在 "setUpMap()" 方法中设置所有这些,如下所示。
private void setUpMap() { LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); Criteria criteria = new Criteria(); criteria.setAccuracy(Criteria.ACCURACY_FINE); String provider = locationManager.getBestProvider(criteria, true); LocationListener locationListener = new LocationListener() { @Override public void onLocationChanged(Location location) { showCurrentLocation(location); } @Override public void onStatusChanged(String s, int i, Bundle bundle) { } @Override public void onProviderEnabled(String s) { } @Override public void onProviderDisabled(String s) { } }; locationManager.requestLocationUpdates(provider, 2000, 0, locationListener); // Getting initial Location Location location = locationManager.getLastKnownLocation(provider); // Show the initial location if(location != null) { showCurrentLocation(location); } }
应用已注册位置更新,最小间隔为 2000 毫秒。
在你的设备上启动此应用,然后带它出去慢跑或户外散步,记住启用 "位置服务" 并打开设备上的 GPS。看看你的位置是否在地图上更新。图 13 显示了一个示例屏幕截图。
![]() |
图 13:跟着我
|
旅程结束
恭喜!你已经创建了你的第一个位置感知的 Google Map 应用。玩得开心,希望在下一次学习旅程中再见。