Android Google 地圖 API for Android

 

從健康類 app Runkeeper 到遊戲 app 精靈寶可夢,位置服務對現代 app 來講愈來愈重要。html

在本文中,咱們將建立一個 app,名字就叫作 City Guide。這個 app 容許用戶搜索一個地點,使用 Google 地圖顯示這個地點的位置並監聽用戶的位置改變。java

咱們將學習如何使用 Google 地圖 API for Android,Google 的位置服務 API 和 Google 的 Places API for Android 完成以下工做:android

  • 顯示用戶當前位置
  • 在地圖上顯示和自定義大頭釘
  • 查詢給定座標的位置信息
  • 監聽位置變化
  • 搜索興趣點

開始

打開 Android Studio,在快速啓動菜單中選擇 Start a new Android Studio projectgit

在建立新項目對話框,New Project 視圖,輸入 app 名稱 City Guide,選擇保存地址,點擊 Next。web

在 Target Android Devices 窗口,勾選 Phone and Tablet 選框,選擇你想要 app 支持的 minimum SDK。從 Minimum SDK 的下拉框中選擇 API 14。而後點 Next。api

在 Add an Activity to Mobile 窗口,選擇 Google Maps Activity 而後點 Next。服務器

在 Customize the Activity 窗口,點擊 Finish,完成項目的建立。app

Android Studio 將啓動 Gradle 並編譯項目。這會花幾分鐘。 
打開 MapsActivity.java。它應該是這個樣子:ide

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 使用。

當編譯完成後,運行 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,你會看到:

在下一頁,點 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();
}
注意:關於用戶權限的完整介紹超出了本文的範疇,請參考運行時請求受權的文檔。

獲取當前座標

定位服務的最多見任務是得到用戶當前座標。咱們經過 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;

聲明一個 LocationRequest 變量以及一個保存位置更新狀態的變量。
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="@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

有問題和建議,請在下面留言。