2.1 问题
需要在地图上为用户显示一个或多个位置。此外,要在同一张地图上显示用户自己的位置。
2.2 解决方案
(API Level 9)
向用户显示地图的最简单方式就是用位置数据创建一个Intent并把它传递给Android系统来启动地图应用程序。另外,Google Play Services库的Google Maps v2库组件提供的Map View和MapActivity可以在应用程序中嵌入地图。
要点:
Google Maps v2是作为Google Play Services库的一部分进行分发的,它在任意平台级别都不是原生SDK的一部分。然而,目标平台为API Level 9或以后版本的应用程序以及Google Play体系内的设备都可以使用此绘画库。
1. 获取API密钥
要开始使用Maps v2,需要创建一个API项目,在该项目内启用Maps v2服务,生成API密钥并包括在应用程序代码中。如果没有API密钥,虽然也可以利用绘图类,但不会向应用程序返回任何地图图块(map tile)。请遵循如下步骤:
(1)进入https://code.google.com/apis/console/,使用你的Google账户登录以访问Google API控制台。
(2)选择Create Project选项,为你的地图建立新的项目。如果已有项目,则可以根据喜好向其中添加Maps v2服务和密钥。在此例中,选择要添加Maps v2的项目。
(3)在导航面板中,选择Services,向下滚动到Google Maps Android API v2并启用该服务。
(4)在导航面板中选择API Access,并且选择Create new Android Key选项。
(5)遵循屏幕上的说明,根据想要使用的应用程序向密钥添加密钥库签名/应用程序包对。在此例中,示例应用程序的包名是com.androidrecipes.mapper,而签名来自开发机器上的调试密钥,通常位于/.android/debug.keystore。
如果在模拟器上测试你的运行代码,模拟器必须使用目标平台为Android 4.3或以后版本的SDK构建,这些版本包含Google API,从而绘图操作可以正常运行。以前版本的SDK捆绑了Map v1 库而不是Google Play Services,因此它们不能用于测试。
如果是通过命令行创建模拟器,目标的名称就是“Google Inc:Google APIs:X”,其中的X指示API的版本。如果在IDE(例如Eclipse)中创建模拟器,目标的命名约定也是类似的:Google APIs(Google Inc.) -X,其中X指示API的版本。
2.满足清单要求
获得了有效的API密钥之后,需要在我们的AndroidManifest.xml文件包括该密钥。下面的代码块必须放在元素内:
<meta-data
android:name="com.google.android.maps.v2.API_KEY"
android:value="YOUR_KEY_HERE" />
此外,Maps v2有一项设备要求,即至少要具备OpenGL ES2.0。我们可以将此要求作为设备特性提出,方法是在元素内添加如下代码块,通常是放在元素的上方:
<!-- Maps v2 requires OpenGL ES 2.0 -->
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
最后,Maps v2 需要一组权限才能与Google Play Services通信以及渲染地图图块。因此,我们必须在元素内再添加一个代码块,通常是放在元素的上方:
<!-- 显示地图所需要的权限 -->
<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" />
将上述内容整合在一起,清单文件如下所示。
AndroidManifest.xml的部分代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidrecipes.mapper" >
<!-- 需要在地图上显示用户的位置 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- 显示地图所需的权限 -->
<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" />
<!-- Maps v2 requires OpenGL ES 2.0 -->
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<!--活动、服务、提供程序等-->
<meta-data
android:name="com.google.android.maps.v2.API_KEY"
android:value="YOUR_KEY_HERE" />
</application>
</manifest>
手边有了API密钥和合适的测试平台之后,就可以开始正式工作了。
2.3 实现机制
要想显示地图,只需要创建一个MapView或MapFragment实例。API密钥在应用程序中全局可用,因此这些元素的任何实例都会使用该值。不需要像在Maps v1中一样将密钥添加到每个实例中。
注意:
除了上述权限之后,还必须为此例添加android.permission.ACCESS_FINE_LOCATION权限。需要此权限的唯一原因是该例会连接到LoactionManager以获得缓存的位置值。
我们将创建一个简单的应用程序,它会将用户上次最新的已知位置显示在Google地图上。
现在,让我们查看构造此视图所需的布局,参见以下代码清单。
res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="Map Of Your Location" />
<RadioGroup
android:id="@+id/group_maptype"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<RadioButton
android:id="@+id/type_normal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Normal Map" />
<RadioButton
android:id="@+id/type_satellite"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Satellite Map" />
</RadioGroup>
<fragment
class="com.google.android.gms.maps.SupportMapFragment"
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
注意:
在XML布局中添加MapView或MapFragment时,必须指定包的完全限定名称,这是由于该类并没有包含在android.view或android.widget中。
在此创建了一个简单布局,其中包括一个选择器,用于切换在MapFragment实例旁边显示的地图类型。以下代码清单显示了控制地图的Activity代码。
显示缓存位置的Activity
public class BasicMapActivity extends FragmentActivity implements
RadioGroup.OnCheckedChangeListener {
private static final String TAG = "AndroidRecipes";
private SupportMapFragment mMapFragment;
private GoogleMap mMap;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//检查 play services 是否激活且为最新版本
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
switch (resultCode) {
case ConnectionResult.SUCCESS:
Log.d(TAG, "Google Play Services is ready to go!");
break;
default:
showPlayServicesError(resultCode);
return;
}
mMapFragment = (SupportMapFragment)
getSupportFragmentManager().findFragmentById(R.id.map);
mMap = mMapFragment.getMap();
//快速检查用户的最新已知位置是否有效, 并且围绕该点将地图居中
// 如果该位置无效,则使用默认位置
LocationManager manager =
(LocationManager)getSystemService(Context.LOCATION_SERVICE);
Location location =
manager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
LatLng mapCenter;
if(location != null) {
mapCenter = new LatLng(location.getLatitude(),
location.getLongitude());
} else {
//使用默认位置
mapCenter = new LatLng(37.4218, -122.0840);
}
//居中地图并同时缩放
CameraUpdate newCamera = CameraUpdateFactory.newLatLngZoom(mapCenter, 13);
mMap.moveCamera(newCamera);
// 连接地图类型选择器 UI
RadioGroup typeSelect = (RadioGroup) findViewById(R.id.group_maptype);
typeSelect.setOnCheckedChangeListener(this);
typeSelect.check(R.id.type_normal);
}
@Override
public void onResume() {
super.onResume();
if (mMap != null) {
//启用地图上用户位置显示功能
mMap.setMyLocationEnabled(true);
}
}
@Override
public void onPause() {
super.onResume();
if (mMap != null) {
//在不可见时禁用用户位置
mMap.setMyLocationEnabled(false);
}
}
/*
* 当 Play Services缺失或版本不对时,
* 客户端库将以对话框的形式帮助用户进行更新。
*/
private void showPlayServicesError(int errorCode) {
// 从 Google Play services 获得错误对话框
Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(
errorCode,
this,
1000 /* RequestCode */);
// 如果 Google Play services 可以提供错误对话框
if (errorDialog != null) {
// 为错误对话框创建新的 DialogFragment
SupportErrorDialogFragment errorFragment = SupportErrorDialogFragment.newInstance(errorDialog);
// 在DialogFragment中显示错误对话框
errorFragment.show(
getSupportFragmentManager(),
"Google Maps");
}
}
/** OnCheckedChangeListener 方法 */
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
switch (checkedId) {
case R.id.type_satellite:
//显示卫星地图
mMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
break;
case R.id.type_normal:
default:
//显示普通地图
mMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
break;
}
}
}
首先执行的操作是确认将正确版本的Google Play Services安装到此设备上。在设备的用户与Google应用程序(如Google Play)交互时,Google会自动管理Google Play Services库。Google Play Services在后台自动更新,因此我们需要使用来自GooglePlayServicesUtil的方法,在运行时确认用户具有我们所需的版本。从isGooglePlayServicesAvaiable()获得的结果将告诉我们服务是否为正确的版本还是需要更新,甚至是需要完全安装。
这个Activity会获得用户最新的位置信息并将该位置设为地图的中心。关于地图的所有控制操作都是通过GoogleMap实例来完成的,而GoogleMap实例则是通过调用MapFragment.getMap()得到的。在此例中,我们使用了地图的moveCamera()方法,通过CameraUpdate对象对地图的显示做了调整。
CameraUpdate用于一次性对地图显示的一个或多个组件进行调整,例如修改缩放以及中心点。地图的缩放级别是2.0和21.0之间的离散值,其中的最小值会使整个世界地图近似为1024dp宽,而每增加一级缩放就会使显示中的世界地图的宽度翻倍。
当用户选择不同的单选按钮时,地图类型会在卫星视图和传统的地图视图之间切换,除了此例中使用的值之外,其他允许的地图类型如下:
- Map_TYPE_HYBRID:在卫星地图上显示地图数据(例如,街道和感兴趣的点)。
-Map_TYPE_TERRAIN :使用地形海拔轮廓线显示地图。
最后,为启用用户位置显示和控件,我们只需要对地图调用setMyLocationEnabled()。该方法将启用位置跟踪并可能开启GPS等元素,因此也应该在不再需要时禁用(当视图不可见时)。
为正确构建此应用程序,以下代码清单显示了build.gradle文件中所需的依赖关系。
build.gradle文件的部分代码
apply plugin: 'com.android.application'
android {
compileSdkVersion 21
buildToolsVersion '26.0.2'
defaultConfig {
applicationId "com.androidrecipes.mapper"
...
}
......
}
dependencies {
compile 'com.android.support:support-v4:21.0.+'
compile 'com.google.android.gms:play-services:6.1.+'
}
这是很好的起点,但或许有点令人厌烦。为引入一些更具交互性的内容,下一节将在地图上创建标识和其他标记,并且会介绍如何定制这些标记。