Google 地圖平台 15 週年紀念查看最新消息和公告

在地圖上選取 [目前位置] 和 [顯示詳細資訊]

本教學課程說明如何找出 Android 裝置的目前位置,並顯示該位置 (商家或其他搜尋點) 的詳細資訊。按照本教學課程,使用 Google Play 服務 location API 中的 Maps SDK for AndroidPlaces SDK for Android整合式位置預測提供工具來建立 Android 應用程式。

取得程式碼

從 GitHub 複製或下載 Google Maps Android API 第 2 版範例存放區

設定您的開發專案

如要在 Android Studio 中建立教學課程專案,請按照下列步驟操作。

  1. 下載安裝 Android Studio。
  2. Google Play 服務套件加入 Android Studio。
  3. 如果您在閱讀本教學課程時尚未複製或下載 Google Maps Android API 第 2 版範例存放區,請先複製或下載。
  4. 匯入教學課程專案:

    • 在 Android Studio 中,選取 [File] (檔案) > [New] (新增) > [Import Project] (匯入專案)
    • 下載完成後,請前往您儲存 Google Maps Android API 第 2 版範例存放區的位置。
    • 在這個位置找到 CurrentPlaceDetailsOnMap 專案:
      PATH-TO-SAVED-REPO/android-samples/tutorials/CurrentPlaceDetailsOnMap
    • 選取專案目錄,然後按一下 [OK] (確定)。Android Studio 現在會使用 Gradle 建構工具來建立您的專案。

取得 API 金鑰並啟用必要的 API

如想完成本教學課程,您必須取得獲授權使用 Maps SDK for Android 與 Places SDK for Android 的 Google API 金鑰。

點選下方按鈕即可取得金鑰並啟用 API。

開始使用

詳情請參閱取得 API 金鑰的完整指南。

在應用程式中加入 API 金鑰

  1. 編輯專案的 gradle.properties 檔案。
  2. 將 API 金鑰貼到 GOOGLE_MAPS_API_KEY 屬性的值中:

    GOOGLE_MAPS_API_KEY=PASTE-YOUR-API-KEY-HERE

    在您建構應用程式時,Gradle 會將 API 金鑰複製到應用程式的 Android 資訊清單中。應用程式的 build.gradle 檔案會包含以下這一行,將資訊清單中的字串 google_maps_key 對應到 Gradle 屬性 GOOGLE_MAPS_API_KEY

    resValue "string", "google_maps_key",
            (project.findProperty("GOOGLE_MAPS_API_KEY") ?: "")
    

建構並執行應用程式

  1. 將 Android 裝置連接到電腦。按照操作說明為 Android 裝置啟用開發人員選項,並將系統設定為偵測裝置 (您也可以使用 Android Virtual Device (AVD) Manager 來設定虛擬裝置。選擇模擬器時,請務必挑選包含 Google API 的映像檔。詳情請參閱入門指南)。
  2. 在 Android Studio 中,按一下 [Run] (執行) 選單選項 (或播放按鈕圖示),然後按照系統提示選擇裝置。

Android Studio 會叫用 Gradle 來建構應用程式,然後在裝置或模擬器上執行應用程式。您應該會看到一份地圖,上面有數個以您目前位置為中心的標記,與本頁的圖片類似。

疑難排解:

  • 如果您未看到地圖,請按照上述步驟,檢查您是否已取得 API 金鑰並將其加入應用程式。在 Android Studio 查看 Android Monitor 中的記錄檔,看看是否有關於 API 金鑰的錯誤訊息。
  • 如果地圖只顯示位於雪梨港灣大橋上的一個標記 (應用程式中指定的預設位置),請檢查您是否已授予應用程式位置存取權。應用程式執行時,會依照 Android 權限指南中所述的模式,提示您授予位置存取權。請注意,您也可以直接在裝置上設定權限,方法是選擇 [設定] > [應用程式] > [應用程式名稱] > [權限] > [位置]。如要進一步瞭解如何處理程式碼中的權限,請參閱下方指南,查看如何在應用程式中要求位置存取權
  • 使用 Android Studio 偵錯工具查看記錄檔並為應用程式偵錯。

瞭解程式碼

本教學課程這一段將說明 CurrentPlaceDetailsOnMap 應用程式最重要的部分,協助您瞭解如何建構類似的應用程式。

為 Places API 用戶端執行個體化

下列介面提供了 Places SDK for Android 的主要進入點:

  • GeoDataClient 可存取 Google 的本地位置和商家資訊資料庫。
  • PlaceDetectionClient 可讓您快速存取裝置的目前位置,並可在特定地點回報裝置位置。

LocationServices 介面是 Android 定位服務的主要進入點。

如要使用 API,請在片段或活動的 onCreate() 方法中將 GeoDataClientPlaceDetectionClientFusedLocationProviderClient 執行個體化,如以下程式碼範例所示:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // Construct a GeoDataClient.
    mGeoDataClient = Places.getGeoDataClient(this, null);

    // Construct a PlaceDetectionClient.
    mPlaceDetectionClient = Places.getPlaceDetectionClient(this, null);

    // Construct a FusedLocationProviderClient.
    mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
}

要求位置存取權

您的應用程式必須要求位置存取權,才能判斷裝置的位置,並讓使用者能輕觸地圖上的 [我的位置] 按鈕。

本教學課程會提供可讓您要求精確位置存取權的相關程式碼。 詳情請參閱 Android 權限指南。

  1. 將權限新增為 Android 資訊清單中 <manifest> 元素的子項:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.currentplacedetailsonmap">
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    </manifest>
    
  2. 在應用程式中要求執行階段權限,讓使用者有機會允許或拒絕位置存取權。下列程式碼會檢查使用者是否已授予精確位置存取權;如果未授予,程式碼就會要求權限:

    private void getLocationPermission() {
        /*
         * Request location permission, so that we can get the location of the
         * device. The result of the permission request is handled by a callback,
         * onRequestPermissionsResult.
         */
        if (ContextCompat.checkSelfPermission(this.getApplicationContext(),
                android.Manifest.permission.ACCESS_FINE_LOCATION)
                == PackageManager.PERMISSION_GRANTED) {
            mLocationPermissionGranted = true;
        } else {
            ActivityCompat.requestPermissions(this,
                    new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
                    PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
        }
    }
    
  3. 覆寫 onRequestPermissionsResult() 回呼以處理權限要求的結果:

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        mLocationPermissionGranted = false;
        switch (requestCode) {
            case PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    mLocationPermissionGranted = true;
                }
            }
        }
        updateLocationUI();
    }
    

    本教學課程的後續章節會說明 updateLocationUI() 方法。

新增地圖

使用 Maps SDK for Android 顯示地圖。

  1. 在活動的版面配置檔案 activity_maps.xml 中加入 <fragment> 元素。這個元素會將 SupportMapFragment 定義為地圖的容器並提供 GoogleMap 物件的存取權。本教學課程使用 Android 支援資料庫版本的地圖片段,確保與舊版 Android 架構能回溯相容。

    <fragment xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/map"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.example.currentplacedetailsonmap.MapsActivityCurrentPlace" />
    
    
  2. 在活動的 onCreate() 方法中,將版面配置檔案設為內容檢視畫面:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_maps);
    }
    
  3. 導入 OnMapReadyCallback 介面並覆寫 onMapReady() 方法,以便在 GoogleMap 物件可用時設定地圖:

    public void onMapReady(GoogleMap map) {
        mMap = map;
    
        // Do other setup activities here too, as described elsewhere in this tutorial.
    
        // Turn on the My Location layer and the related control on the map.
        updateLocationUI();
    
        // Get the current location of the device and set the position of the map.
        getDeviceLocation();
    }
    
  4. 在活動的 onCreate() 方法中,呼叫 FragmentManager.findFragmentById() 以取得地圖片段的處理常式,然後使用 getMapAsync() 註冊地圖回呼:

    SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
            .findFragmentById(R.id.map);
    mapFragment.getMapAsync(this);
    
  5. 撰寫 updateLocationUI() 方法來設定地圖上的位置控制項。如果使用者已授予位置存取權,請在地圖上啟用「我的位置」圖層和相關控制項,否則請停用圖層和控制項,並將目前位置設為空值:

    private void updateLocationUI() {
        if (mMap == null) {
            return;
        }
        try {
            if (mLocationPermissionGranted) {
                mMap.setMyLocationEnabled(true);
                mMap.getUiSettings().setMyLocationButtonEnabled(true);
            } else {
                mMap.setMyLocationEnabled(false);
                mMap.getUiSettings().setMyLocationButtonEnabled(false);
                mLastKnownLocation = null;
                getLocationPermission();
            }
        } catch (SecurityException e)  {
            Log.e("Exception: %s", e.getMessage());
        }
    }
    

取得 Android 裝置的位置並設定地圖位置

使用整合式位置預測提供工具,找出裝置的最後已知位置,然後使用該位置來定位地圖。本教學課程會提供您需要的程式碼。如要進一步瞭解如何取得裝置的位置資訊,請參閱 Google Play 服務 location API 中的整合式位置預測提供工具指南。

private void getDeviceLocation() {
    /*
     * Get the best and most recent location of the device, which may be null in rare
     * cases when a location is not available.
     */
    try {
        if (mLocationPermissionGranted) {
            Task locationResult = mFusedLocationProviderClient.getLastLocation();
            locationResult.addOnCompleteListener(this, new OnCompleteListener() {
                @Override
                public void onComplete(@NonNull Task task) {
                    if (task.isSuccessful()) {
                        // Set the map's camera position to the current location of the device.
                        mLastKnownLocation = task.getResult();
                        mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(
                                new LatLng(mLastKnownLocation.getLatitude(),
                                        mLastKnownLocation.getLongitude()), DEFAULT_ZOOM));
                    } else {
                        Log.d(TAG, "Current location is null. Using defaults.");
                        Log.e(TAG, "Exception: %s", task.getException());
                        mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(mDefaultLocation, DEFAULT_ZOOM));
                        mMap.getUiSettings().setMyLocationButtonEnabled(false);
                    }
                }
            });
        }
    } catch(SecurityException e)  {
        Log.e("Exception: %s", e.getMessage());
    }
}

取得目前地點

使用 Places SDK for Android 取得裝置目前位置的可能地點清單。在這種情況下,「地點」是指商家或其他搜尋點。

使用者按一下 [取得地點] 按鈕後,本教學課程就會顯示目前地點。系統會提供可能的地點清單,讓使用者從中選擇,並在地圖上為該地點新增位置標記。本教學課程會提供您使用 Places SDK for Android 時需要的程式碼。詳情請參閱取得目前地點指南。

  1. 為選項選單建立版面配置檔案 (current_place_menu.xml),並覆寫 onCreateOptionsMenu() 方法來設定選項選單。請參閱隨附的程式碼範例應用程式。
  2. 使用者按一下 [取得地點] 選項時,會覆寫 onOptionsItemSelected() 方法以取得目前地點:
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.option_get_place) {
            showCurrentPlace();
        }
        return true;
    }
    
  3. 建立 showCurrentPlace() 方法,取得裝置目前位置的可能地點清單:

    private void showCurrentPlace() {
        if (mMap == null) {
            return;
        }
    
        if (mLocationPermissionGranted) {
            // Use fields to define the data types to return.
            List<Place.Field> placeFields = Arrays.asList(Place.Field.NAME, Place.Field.ADDRESS,
                    Place.Field.LAT_LNG);
    
            // Use the builder to create a FindCurrentPlaceRequest.
            FindCurrentPlaceRequest request =
                    FindCurrentPlaceRequest.newInstance(placeFields);
    
            // Get the likely places - that is, the businesses and other points of interest that
            // are the best match for the device's current location.
            @SuppressWarnings("MissingPermission") final
            Task<FindCurrentPlaceResponse> placeResult =
                    mPlacesClient.findCurrentPlace(request);
            placeResult.addOnCompleteListener (new OnCompleteListener<FindCurrentPlaceResponse>() {
                @Override
                public void onComplete(@NonNull Task<FindCurrentPlaceResponse> task) {
                    if (task.isSuccessful() && task.getResult() != null) {
                        FindCurrentPlaceResponse likelyPlaces = task.getResult();
    
                        // Set the count, handling cases where less than 5 entries are returned.
                        int count;
                        if (likelyPlaces.getPlaceLikelihoods().size() < M_MAX_ENTRIES) {
                            count = likelyPlaces.getPlaceLikelihoods().size();
                        } else {
                            count = M_MAX_ENTRIES;
                        }
    
                        int i = 0;
                        mLikelyPlaceNames = new String[count];
                        mLikelyPlaceAddresses = new String[count];
                        mLikelyPlaceAttributions = new List[count];
                        mLikelyPlaceLatLngs = new LatLng[count];
    
                        for (PlaceLikelihood placeLikelihood : likelyPlaces.getPlaceLikelihoods()) {
                            // Build a list of likely places to show the user.
                            mLikelyPlaceNames[i] = placeLikelihood.getPlace().getName();
                            mLikelyPlaceAddresses[i] = placeLikelihood.getPlace().getAddress();
                            mLikelyPlaceAttributions[i] = placeLikelihood.getPlace()
                                    .getAttributions();
                            mLikelyPlaceLatLngs[i] = placeLikelihood.getPlace().getLatLng();
    
                            i++;
                            if (i > (count - 1)) {
                                break;
                            }
                        }
    
                        // Show a dialog offering the user the list of likely places, and add a
                        // marker at the selected place.
                        MapsActivityCurrentPlace.this.openPlacesDialog();
                    }
                    else {
                        Log.e(TAG, "Exception: %s", task.getException());
                    }
                }
            });
        } else {
            // The user has not granted permission.
            Log.i(TAG, "The user did not grant location permission.");
    
            // Add a default marker, because the user hasn't selected a place.
            mMap.addMarker(new MarkerOptions()
                    .title(getString(R.string.default_info_title))
                    .position(mDefaultLocation)
                    .snippet(getString(R.string.default_info_snippet)));
    
            // Prompt the user for permission.
            getLocationPermission();
        }
    }
    
  4. 建立 openPlacesDialog() 方法以顯示表單,讓使用者從可能的地點清單中選取地點。在地圖上為已選取的地點新增標記。標記內容包含地點的名稱和地址,以及 API 提供的任何屬性:

    private void openPlacesDialog() {
        // Ask the user to choose the place where they are now.
        DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                // The "which" argument contains the position of the selected item.
                LatLng markerLatLng = mLikelyPlaceLatLngs[which];
                String markerSnippet = mLikelyPlaceAddresses[which];
                if (mLikelyPlaceAttributions[which] != null) {
                    markerSnippet = markerSnippet + "\n" + mLikelyPlaceAttributions[which];
                }
    
                // Add a marker for the selected place, with an info window
                // showing information about that place.
                mMap.addMarker(new MarkerOptions()
                        .title(mLikelyPlaceNames[which])
                        .position(markerLatLng)
                        .snippet(markerSnippet));
    
                // Position the map's camera at the location of the marker.
                mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(markerLatLng,
                        DEFAULT_ZOOM));
            }
        };
    
        // Display the dialog.
        AlertDialog dialog = new AlertDialog.Builder(this)
                .setTitle(R.string.pick_place)
                .setItems(mLikelyPlaceNames, listener)
                .show();
    }
    
  5. 為資訊視窗內容建立自訂版面配置,這樣就能在資訊視窗中顯示多行內容。首先,請新增 XML 版面配置檔案 custom_info_contents.xml,其中包含資訊視窗標題的文字檢視區塊,以及其他程式碼片段的文字檢視區塊 (即資訊視窗的文字內容):

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layoutDirection="locale"
        android:orientation="vertical">
        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:textColor="#ff000000"
            android:textStyle="bold" />
    
        <TextView
            android:id="@+id/snippet"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#ff7f7f7f" />
    </LinearLayout>
    
    
  6. 導入 InfoWindowAdapter 介面以加載版面配置,並載入資訊視窗內容:

    @Override
    public void onMapReady(GoogleMap map) {
        // Do other setup activities here too, as described elsewhere in this tutorial.
        mMap.setInfoWindowAdapter(new GoogleMap.InfoWindowAdapter() {
    
        @Override
        // Return null here, so that getInfoContents() is called next.
        public View getInfoWindow(Marker arg0) {
            return null;
        }
    
        @Override
        public View getInfoContents(Marker marker) {
            // Inflate the layouts for the info window, title and snippet.
            View infoWindow = getLayoutInflater().inflate(R.layout.custom_info_contents, null);
    
            TextView title = ((TextView) infoWindow.findViewById(R.id.title));
            title.setText(marker.getTitle());
    
            TextView snippet = ((TextView) infoWindow.findViewById(R.id.snippet));
            snippet.setText(marker.getSnippet());
    
            return infoWindow;
          }
        });
    }
    

儲存地圖的狀態

儲存地圖的攝影機位置和裝置位置。當使用者旋轉 Android 裝置或變更設定時,Android 架構會銷毀並重建地圖活動。為確保使用者獲得流暢的使用體驗,建議您儲存相關的應用程式狀態,並在需要時復原。

本教學課程會提供儲存地圖狀態時需要用到的所有程式碼。詳情請參閱 savedInstanceState 軟體包指南。

  1. 在地圖活動中設定存放活動狀態的鍵/值:

    private static final String KEY_CAMERA_POSITION = "camera_position";
    private static final String KEY_LOCATION = "location";
    
  2. 執行 onSaveInstanceState() 回呼以在活動暫停時儲存狀態:

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        if (mMap != null) {
            outState.putParcelable(KEY_CAMERA_POSITION, mMap.getCameraPosition());
            outState.putParcelable(KEY_LOCATION, mLastKnownLocation);
            super.onSaveInstanceState(outState);
        }
    }
    
  3. 在活動的 onCreate() 方法中,擷取裝置位置及地圖攝影機位置 (如果先前已儲存過):

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState != null) {
            mCurrentLocation = savedInstanceState.getParcelable(KEY_LOCATION);
            mCameraPosition = savedInstanceState.getParcelable(KEY_CAMERA_POSITION);
        }
        ...
    }