首页 > 代码库 > Google 地图 API for Android

Google 地图 API for Android

原文:Introduction to Google Maps API for Android
作者: Eunice Obugyei
译者:kmyhy

从健康类 app Runkeeper 到游戏 app 精灵宝可梦,位置服务对现代 app 来说越来越重要。

在本文中,我们将创建一个 app,名字就叫做 City Guide。这个 app 允许用户搜索一个地点,使用 Google 地图显示这个地点的位置并监听用户的位置改变。

我们将学习如何使用 Google 地图 API for Android,Google 的位置服务 API 和 Google 的 Places API for Android 完成如下工作:

  • 显示用户当前位置
  • 在地图上显示和自定义大头钉
  • 查询给定坐标的位置信息
  • 监听位置变化
  • 搜索兴趣点

注意:本 Google 地图 API 教程假设你熟悉基本的 Android 开发。如果你第一次接触 Android 开发,请阅读我们的 Android 入门教程 来学习基础知识。

开始

打开 Android Studio,在快速启动菜单中选择 Start a new Android Studio project:

技术分享

在创建新项目对话框,New Project 视图,输入 app 名称 City Guide,选择保存地址,点击 Next。

技术分享

在 Target Android Devices 窗口,勾选 Phone and Tablet 选框,选择你想要 app 支持的 minimum SDK。从 Minimum SDK 的下拉框中选择 API 14。然后点 Next。

技术分享

在 Add an Activity to Mobile 窗口,选择 Google Maps Activity 然后点 Next。

技术分享

在 Customize the Activity 窗口,点击 Finish,完成项目的创建。

技术分享

Android Studio 将启动 Gradle 并编译项目。这会花几分钟。
打开 MapsActivity.java。它应该是这个样子:

package com.raywenderlich.cityguide;

import android.support.v4.app.FragmentActivity;
import android.os.Bundle;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;

// 1
public class MapsActivity extends FragmentActivity implements OnMapReadyCallback {

  private GoogleMap mMap;

  // 2
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_maps);
    // Obtain the SupportMapFragment and get notified when the map is ready to be used.
    SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
        .findFragmentById(R.id.map);
    mapFragment.getMapAsync(this);
  }

  // 3
  @Override
  public void onMapReady(GoogleMap googleMap) {
    mMap = googleMap;

    // Add a marker in Sydney and move the camera
    LatLng sydney = new LatLng(-34, 151);
    mMap.addMarker(new MarkerOptions().position(sydney).title("Marker in Sydney"));
    mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney));
  }
}
  1. MapsActivity 实现了 OnMapReadyCallback 接口并继承了 FragmentActivity。
  2. 这个类覆盖了 FragmentActivity 的 onCreate() 方法。
  3. 同时覆盖了 OnMapReadyCallback 的 onMapReady() 方法。这个方法在地图准备就绪时调用。在这个方法中,创建了一个 marker(大头钉),坐标位于澳大利亚悉尼,然后将 marker 方到地图上。

Android Studio 在 manifests/AndroidManifest.xml 中添加了如下代码:

  1. 一个 ACCESS_FINE_LOCATION 权限声明。要访问用户的精确位置,这必不可少。
  2. 添加了一个 com.google.android.geo.API_KEY 的 meta-data。这保存了 API key。

Android Studio 也在 build.gradle 中添加了一个 Google Play Service 的依赖。这个依赖将 Google 地图和定位服务 API 暴露给 app 使用。

compile ‘com.google.android.gms:play-services:VERSION_HERE‘

当编译完成后,运行 app 你会看到:

技术分享

你看到一个空白窗口,上面没有地图;你还没有为 Google Map 创建 API key。我们将在下一节创建。

注意:如果你使用模拟器,模拟器所安装的版本必须满足 build.gradle 文件中 Google Play Service 所要求的版本。如果你看到提示需要升级模拟器的 Google Play Service 版本,你可以从 Android Studio SDK 管理器中下载最新的 Google APIs 并安装到虚拟设备,或者降低 gradle 依赖中的版本。

使用 Google 地图 APIs

要使用任何 Google 地图 API,都需要创建一个 API key 并从开发者控制台中启用所需的 API。如果你没有 Google 账号,现在就去创建它——免费的!

创建 API Key

打开 res/values/google_maps_api.xml,你会看到:

技术分享

现在复制粘贴上图中的链接到浏览器中。
在 Enable an API 页,选择 Create a project 然后点 Continue。

技术分享

在下一页,点 Create API key 按钮。

技术分享

然后,复制 API key created 对话框中的 API key,点击 Close。

技术分享

回到 google_maps_api.xml, 将 google_maps_key 替换成刚才拷贝的 API key。
运行 app,你会看到地图和地图上的红色大头钉。

技术分享

回到 developer console,打开 Google Places API for Android。我们会在后面用这个 API 查找 Place。

建立 Play Services 连接

在编写 Java 代码之前,我们需要配置一下 Android Studio 让它自动为我们插入 import 语句,节省我们的工作量。
依次打开 Android Studio > Preferences > Editor > General > Auto Import 菜单,选择 Add unambiguous imports on the fly 和 Show import popup 选项,点击 OK。

技术分享

打开 MapsActivity.java ,让 MapsActivity 实现下列接口:

public class MapsActivity extends FragmentActivity implements OnMapReadyCallback, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, GoogleMap.OnMarkerClickListener, LocationListener

import LocationListener 一句产生了歧义,因此告诉 Android Studio 去 Google Mobile Services 进行导入:

import com.google.android.gms.location.LocationListener;

上述代码解释如下:

  • GoogleApiClient.ConnectionCallbacks 提供了一个回调,当客户端和服务器成功建立连接时调用(onConnected()) 或者临时性的断开时调用 (onConnectionSuspended())。
  • GoogleApiClient.OnConnectionFailedListener 提供了一个回调方法 (onConnectionFailed()) ,当客户端连接服务器失败时调用。
  • GoogleMap.OnMarkerClickListener 定义了一个 onMarkerClick() 方法,当大头钉被点击时调用。
  • LocationListener 定义了 onLocationChanged() 方法,当用户位置改变时调用。这个方法只有 LocationListener 注册以后才会调用。

现在,实现上述接口定义的方法。要这样做,可以按以下步骤:

  1. 把光标放在类声明的任意地方,点击类声明上显示的红色灯泡。
  2. 选择 Implement methods。

    技术分享

  3. 在 Select Methods to implement 对话框,点击 OK。

    技术分享

这些方法的实现会添加到类中。

要连接 Google Play Services 库中的 Google API,你需要先创建一个 GoogleApiClient。

在 MapsActivity.java 中添加一个字段:

private GoogleApiClient mGoogleApiClient;

在 onCreate() 中加入:

// 1
if (mGoogleApiClient == null) {
  mGoogleApiClient = new GoogleApiClient.Builder(this)
      .addConnectionCallbacks(this)
      .addOnConnectionFailedListener(this)
      .addApi(LocationServices.API)
      .build();
}

添加两个方法:

@Override
protected void onStart() {
  super.onStart();
  // 2
  mGoogleApiClient.connect();
}

@Override
protected void onStop() {
  super.onStop();
  // 3
  if( mGoogleApiClient != null && mGoogleApiClient.isConnected() ) {
    mGoogleApiClient.disconnect();
  }
}

代码说明:

  1. 如果 mGoogleApiClient 变量为空,进行初始化。
  2. 打开一个后台连接,连接到 Google Play 服务。
  3. 如果客服端不为空且状态为已连接的话,关闭连接。

添加下列代码到 onMapReady():

mMap.getUiSettings().setZoomControlsEnabled(true); 
mMap.setOnMarkerClickListener(this);

这里我们开启了地图的缩放控制并指定了 MapsActdivity 作为回调,这样当用户点击大头钉时能够进行处理。

现在,点击地图上位于悉尼的大头钉,你会看到显示了标题文本:

技术分享

输入另外一个坐标,大头钉会移到你指定的位置。

添加下列代码将大头钉设置到纽约,标题文本设置“My Favorite City”:

LatLng myPlace = new LatLng(40.73, -73.99);  // this is New York 
mMap.addMarker(new MarkerOptions().position(myPlace).title("My Favorite City"));
mMap.moveCamera(CameraUpdateFactory.newLatLng(myPlace));

编译运行。

技术分享

注意,地图自动将中心和大头钉对齐,moveCamera() 的作用就在于次。但是,地图的缩放比例不正确,因为它是缩得太小了。

将 moveCamera() 方法调用修改为:

mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(myPlace, 12));

缩方比例 0 表示将地图缩小为最小的世界地图。大部分地图都支持缩放比例到 20,更远的地区仅仅支持到 13,将它设为二者之间的 12 比较合适,显示较多的细节且不会太近。

运行 app 以查看效果。

技术分享

用户权限

我们的 app 需要使用 ACCESS_FINE_LOCATION 权限以获得用户定位信息;在 AndroidManifest.xml 中我们已经进行了声明。

从 Android 6.0 开始,用户权限与之前发生了一点点区别。你不会在安装 app 时请求权限,而是在运行时,当权限真正需要用到时才请求。

权限分为两种类别:普通权限和危险权限。对于危险权限需要在运行时向用户请求授权。要求访问用户隐私的权限比如访问用户通讯录、日历、定位等就属于危险权限。

打开 MapsActivity.java 添加下列变量:

private static final int LOCATION_PERMISSION_REQUEST_CODE = 1;

新加一个方法 setUpMap() 。

private void setUpMap() {
  if (ActivityCompat.checkSelfPermission(this,
    android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
    ActivityCompat.requestPermissions(this, new String[]
      {android.Manifest.permission.ACCESS_FINE_LOCATION}, LOCATION_PERMISSION_REQUEST_CODE);  
    return;
  }
}

上述代码判断 app 是否获得了 ACCESS_FINE_LOCATION 权限。如果没有,向用户请求授权。

然后在 onConnectded() 方法中调用这个方法:

@Override
public void onConnected(@Nullable Bundle bundle) {
  setUpMap();
}

编译运行,当请求授权时点击 Allow。

技术分享

注意:关于用户权限的完整介绍超出了本文的范畴,请参考运行时请求授权的文档。

获取当前坐标

定位服务的最常见任务是获得用户当前坐标。我们通过 Google Play 服务定位 API 请求用户设备的最新坐标来实现这个目的。

在 MapsActivity.java, 添加变量:

private Location mLastLocation;

然后,在setUpMap() 最后一句添加代码:

// 1
mMap.setMyLocationEnabled(true);

// 2
LocationAvailability locationAvailability =
    LocationServices.FusedLocationApi.getLocationAvailability(mGoogleApiClient);
if (null != locationAvailability && locationAvailability.isLocationAvailable()) {
  // 3
  mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
  // 4
  if (mLastLocation != null) { 
    LatLng currentLocation = new LatLng(mLastLocation.getLatitude(), mLastLocation
        .getLongitude());
    mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(currentLocation, 12));
  }
}

代码说明:

  1. setMyLocationEnabled 一句打开了 my-location 图层,用于在用户坐标出绘制一个浅蓝色的圆点。同时加一个按钮到地图上,当你点击它,地图中心会移动到用户的坐标。
  2. getLocationAvailability 一句判断设备上的位置信息是否有效。
  3. getLastLocation 一句允许你获得当前有效的最新坐标。
  4. 如果能够获得最新坐标,将镜头对准用户当前坐标。

编译运行,查看效果。你会看到在用户当前坐标有一个浅蓝色的圆点:

技术分享

在模拟器上进行测试

要测试地图类 app,最好用真正的 Android 设备。如果因为某种原因不得不在模拟器上测试,你可以用模拟器模拟出坐标数据。

要做到这个,一种办法是使用模拟器的扩展控制(extended controls)。你需要这样做:

  1. 打开模拟器。在右边面板中,点击 More 按钮(…) 以访问 extended controls。

    技术分享

  2. 在 Extended Controls 对话框的左边,选择 Location。
  3. 在下图指定位置输入经纬度,点击 Send。

    技术分享

大头钉

注意最后一次运行 app 时,用户位置所在的蓝点非常显眼。Android 地图 API 允许你使用大头钉,这是一种图标,用于放在地图上层的指定位置。

在 MapsActivity.java 中添加代码:

protected void placeMarkerOnMap(LatLng location) {
  // 1
  MarkerOptions markerOptions = new MarkerOptions().position(location);
  // 2
  mMap.addMarker(markerOptions);
}
  1. 创建了一个 MarkerOptions 对象并将大头钉要放在的位置设置为用户当前坐标。
  2. 将大头钉添加到地图。

将 setUpMap() 方法替换为:

private void setUpMap() {
  if (ActivityCompat.checkSelfPermission(this,
      android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
    ActivityCompat.requestPermissions(this, new String[]
        {android.Manifest.permission.ACCESS_FINE_LOCATION},LOCATION_PERMISSION_REQUEST_CODE);
    return;
  }

  mMap.setMyLocationEnabled(true);

  LocationAvailability locationAvailability =
      LocationServices.FusedLocationApi.getLocationAvailability(mGoogleApiClient);
  if (null != locationAvailability && locationAvailability.isLocationAvailable()) {
    mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
    if (mLastLocation != null) {
      LatLng currentLocation = new LatLng(mLastLocation.getLatitude(), mLastLocation
          .getLongitude());
      //add pin at user‘s location
      placeMarkerOnMap(currentLocation);
      mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(currentLocation, 12));
    }
  }
}

setUpMap() 方法中的改变仅仅是调用了 placeMarkerOnMap() 以显示大头钉。
编译运行查看效果。你现在会在用户位置看到一个大头钉:

技术分享

如果你不喜欢 Android 默认的大头钉样式,你也可以创建自己的图片取代。回到 placeMarkerOnMap() 方法,在 MarkerOptions 初始化之后加入下句:

markerOptions.icon(BitmapDescriptorFactory.fromBitmap(BitmapFactory.decodeResource
    (getResources(), R.mipmap.ic_user_location)));

从这里下载自定义大头钉文件 ic_user_location,然后解压缩。将所有文件拷贝到 mipmap 目录:

技术分享

编译运行查看效果。在你当前位置的大头钉现在使用了项目中的 ic_user_location 图片:

技术分享

如果仅仅是修改默认大头钉的颜色呢?请自行进行尝试,如果有难度请参考这个答案:

在 placeMarkerOnMap() 中使用这句:

```java

markerOptions.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN));
“`
这会将默认大头钉的红色换成绿色。

![](https://koenig-media.raywenderlich.com/uploads/2016/10/Screen-Shot-2016-10-02-at-10.57.55-PM.png)

改变地图类型

根据 app 要实现的功能,一般的地图视图可能对你就不够用了。

Android 地图 API 提供了几种地图类型:MAP_TYPE_NORMAL、MAP_TYPE_SATELLITE、 MAP_TYPE_TERRAIN、MAP_TYPE_HYBRID。
在 setUpMain() 方法的 setMyLocationEnabled() 后面加入一句:

mMap.setMapType(GoogleMap.MAP_TYPE_TERRAIN);

GoogleMap.MAP_TYPE_TERRAIN 显示更详细的地形,显示地貌变化:

视图 a more detailed view of the area, showing changes in elevation:

技术分享

其它类型的效果:

GoogleMap.MAP_TYPE_SATELLITE 显示卫星地图,没有文字标注。

技术分享

GoogleMap.MAP_TYPE_HYBRID 显示卫星地图和普通视图的结合。

技术分享

GoogleMap.MAP_TYPE_NORMAL 显示典型的街道地图并标注标签。这也是默认的类型。

技术分享

实现地理编码

现在你已经获得了用户的坐标,如果在用户点击大头钉时显示地理名称就好了。Google 有一个 Geocoder 就是用来干这个的。它将经纬度坐标转换为一个人类可读的地址,或者与此相反。

打开 MapsActivity,添加方法:

private String getAddress( LatLng latLng ) {
  // 1
  Geocoder geocoder = new Geocoder( this );
  String addressText = "";
  List<Address> addresses = null;
  Address address = null;
  try {
    // 2
    addresses = geocoder.getFromLocation( latLng.latitude, latLng.longitude, 1 );
    // 3
    if (null != addresses && !addresses.isEmpty()) {
      address = addresses.get(0);
      for (int i = 0; i < address.getMaxAddressLineIndex(); i++) {
        addressText += (i == 0)?address.getAddressLine(i):("\n" + address.getAddressLine(i));
      }
    }
  } catch (IOException e ) {
  }
  return addressText;
}

关键在于 Address 类是有歧义的,要解决这个问题,需要将 import 语句指定为:

import android.location.Address;

代码说明:

  1. 创建一个 Geocoder 对象,用于将一个经纬度坐标转换成地址或进行相反的转换。
  2. 使用 geocoder 将方法参数接收到的经纬度转换成地址信息。
  3. 如果响应的 addresses 中包含有地址信息,将这些信息拼接为一个字符串返回。

将 placeMarkerOnMap() 方法修改为:

protected  void placeMarkerOnMap(LatLng location) {
  MarkerOptions markerOptions = new MarkerOptions().position(location);

  String titleStr = getAddress(location);  // add these two lines
  markerOptions.title(titleStr);

  mMap.addMarker(markerOptions);
}

在这个方法中添加了一句 getAddress() 调用,并将地址设置为大头钉标题。

编译运行以查看效果。点击大头钉,你会看到地址:

技术分享

点击地图的其他地方,地址消失。
注意,如果你走动位置,蓝点会跟着你一起移动,但大头钉不会。如果你在真机上测试,试着四处移动一下位置。如果在模拟器上测试,将你的坐标用 emulator control 修改到别的地方。

大头钉不会移动是因为我们的代码还不知道什么时候位置发生了变化。小蓝点位置由 Google API 自己管理,而不是我们的代码做的。如果想让 marker 跟随小蓝点移动,需要在代码中接收位置变化通知。

接收位置变化

随时知道用户的位置有助于提供一种良好体验。本节将介绍如何实时接收用户位置的变化。
为了做到这一点,你需要创建一个 location request。
打开 MapsActivity,增加变量:

// 1
private LocationRequest mLocationRequest;
private boolean mLocationUpdateState;
// 2
private static final int REQUEST_CHECK_SETTINGS = 2;
  1. 声明一个 LocationRequest 变量以及一个保存位置更新状态的变量。
  2. REQUEST_CHECK_SETTINGS 是用于传递给 onActivityResult 方法的 request code。
    然后添加方法:
protected void startLocationUpdates() {
  //1
  if (ActivityCompat.checkSelfPermission(this,
      android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED){
    ActivityCompat.requestPermissions(this,
               new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
               LOCATION_PERMISSION_REQUEST_CODE);  
    return;
  }
  //2
  LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest,
  this);
}
  1. startLocationUpdates() 中,如果 ACCESS_FINE_LOCATION 权限未获取,则请求授权并返回。
  2. 如果已经获得授权,请求位置变化信息。

然后添加方法:

// 1
protected void createLocationRequest() {
  mLocationRequest = new LocationRequest();
  // 2
  mLocationRequest.setInterval(10000); 
  // 3
  mLocationRequest.setFastestInterval(5000); 
  mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);

  LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder()
      .addLocationRequest(mLocationRequest);

  PendingResult<LocationSettingsResult> result =
      LocationServices.SettingsApi.checkLocationSettings(mGoogleApiClient,
          builder.build());

  result.setResultCallback(new ResultCallback<LocationSettingsResult>() {
    @Override
    public void onResult(@NonNull LocationSettingsResult result) {
      final Status status = result.getStatus();
      switch (status.getStatusCode()) {
        // 4
        case LocationSettingsStatusCodes.SUCCESS: 
          mLocationUpdateState = true;
          startLocationUpdates();
          break;
        // 5
        case LocationSettingsStatusCodes.RESOLUTION_REQUIRED: 
          try {
            status.startResolutionForResult(MapsActivity.this, REQUEST_CHECK_SETTINGS);
          } catch (IntentSender.SendIntentException e) {
          }
          break;
        // 6
        case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE: 
          break;
      }
    }
  });
}

ResultCallback 类的 import 语句有歧义,因此添加下列 import 语句:

import com.google.android.gms.common.api.ResultCallback;

createLocationRequest() 方法代码说明如下:

  1. 创建一个 LocationRequest 对象,将它添加到一个 LocationSettingsRequest.Builder 对象,并基于用户位置设置的当前状态查询位置变化信息并处理。
  2. setInterval() 指定了 app 多长时间接受一次变化通知。
  3. setFastestInterval() 指定 app 能够处理的变化通知的最快速度。设置fastestInterval 能够限制位置变化通知发送给你的 app 的频率。在开始请求位置变化通知之前,需要检查用户位置设置的状态。
  4. SUCCESS 状态说明一切正常,你可以初始化一个 location request。
  5. RESOLUTION_REQUIRED 状态表明位置设置有一个问题有待修复。有可能是因为用户的位置设置被关闭了。你可以向用户显示一个对话框:

    技术分享

  6. SETTINGS_CHANGE_UNAVAILABLE 状态表明位置设置有一些无法修复的问题。有可能是用户在上面的对话框里选择了 NEVER。

现在添加下列方法:

// 1
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);
  if (requestCode == REQUEST_CHECK_SETTINGS) {
    if (resultCode == RESULT_OK) {
      mLocationUpdateState = true;
      startLocationUpdates();
    }
  }
}

// 2
@Override
protected void onPause() {
  super.onPause();
  LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
}

// 3
@Override
public void onResume() {
  super.onResume();
  if (mGoogleApiClient.isConnected() && !mLocationUpdateState) {
    startLocationUpdates();
  }
}

代码说明:

  1. 覆盖 FragmentActivity 的 onActivityResult() 方法,如果REQUEST_CHECK_SETTINGS 请求返回的是一个 RESULT_OK,则发起位置更新请求。
  2. 覆盖 onPause() 方法,停止位置变化请求。
  3. 覆盖 onResume() 方法,重新开始位置更新请求。

然后,在 onCreate() 方法的最后调用 createLocationRequest() 方法。

createLocationRequest();

然后,在 onConnected() 方法中添加如下语句:

if (mLocationUpdateState) {
  startLocationUpdates();
}

如果用户的位置设置是打开状态的话,启动位置更新。

在 onLocationChanged() 方法中加入:

mLastLocation = location;
if (null != mLastLocation) {
  placeMarkerOnMap(new LatLng(mLastLocation.getLatitude(), mLastLocation.getLongitude()));
}

这里,我们修改 mLastLocation 为最新的位置并用新位置坐标刷新地图显示。

你的 app 现在已经可以接受位置变化通知了。当你改变位置,地图上的大头钉会随位置的改变而变。注意,点击大头钉仍然能够看到地址信息。
编译运行,四处走动查看变化:

技术分享

查询兴趣点

因为 app 是用于扮演一个向导的角色,用户应该能够找到他们感兴趣的地方吧?

这就是 Google Places API 出场的时候了。它让你的 app 能够搜索数百万计的兴趣点和大型机构。Android 库有许多非常酷的功能,其中之一就是 Place Picker,这是一个 UI widget,允许你用寥寥数行代码就实现一个搜索 PIO(兴趣点)的功能。太好了,这是真的吗?你可以试一试。

打开MapsActivity,添加变量:

private static final int PLACE_PICKER_REQUEST = 3;

然后添加下列方法:


private void loadPlacePicker() {
  PlacePicker.IntentBuilder builder = new PlacePicker.IntentBuilder();

  try {
    startActivityForResult(builder.build(MapsActivity.this), PLACE_PICKER_REQUEST);
  } catch(GooglePlayServicesRepairableException | GooglePlayServicesNotAvailableException e) {
      e.printStackTrace();
  }
}

这个方法创建了新的 builder 用于创建 intent,这个 Intent 用于打开一个 Place Picker UI,然后打开这个 PlacePicker Intent。

将下列语句添加到 onActivityResult():

if (requestCode == PLACE_PICKER_REQUEST) {
  if (resultCode == RESULT_OK) {
    Place place = PlacePicker.getPlace(this, data);
    String addressText = place.getName().toString();
    addressText += "\n" + place.getAddress().toString();

    placeMarkerOnMap(place.getLatLng());
  }
}

在这里,如果请求代码是 PLACE_PICKER_REQUEST 且返回码是 RESULT_OK,则读取所选地点的信息。然后放一个大头钉在该位置。

搜索 PIO 基本搞定——剩下的就是调用 loadPlacePicker() 方法。

我们需要创建一个浮动的 Action 按钮(FAB)在地图右下角并用于调用这个方法。FAB 需要使用 CoordinatorLayout,这是 design 支持库中的内容。

首先,打开 build.gradle 添加依赖 Android support design library:

dependencies {
  ...
  compile ‘com.android.support:design:24.1.1‘
}

注意:通常,如果你用的 Android SDK 版本比较新,你可能需要同时升级这个依赖的的版本,以便二者匹配。

然后修改 res > layout > activity_maps.xml 为:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <fragment
        android:id="@+id/map"
        class="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:src=http://www.mamicode.com/"@android:drawable/ic_menu_search"/>

</android.support.design.widget.CoordinatorLayout>

我们在原先的地图上已经有一个用于显示地图的 fragment;现在所做的就是添加一个 FAB。

在 MapsActivity 的 onCreate() 方法,添加如下代码:


FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View view) {
    loadPlacePicker();
  }
});

编译运行,点击地图下方的 search 按钮,会弹出 place picker:

技术分享https://koenig-media.raywenderlich.com/uploads/2016/09/placepickerdemo4.gif” width= “320”/>

结束

从这里下载最终完成的项目。
关于 Google 地图 APIs,本文只介绍了很少一部分。在 Google 官方文档中,有更多关于 web service 和这个 Android API 的内容。

你还可以查看开发者页面中其它定制大头钉的方法。本文中的运行时用户权限检查需要改进,这里也有很好的东西可以参考:关于更高级的权限授权。

更多阅读,请参考开发者页面:Google Places API for Android、接受位置变化通知 和模拟位置数据模拟器的 extendet controls。

有问题和建议,请在下面留言。

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    Google 地图 API for Android