Tạo bộ chọn Địa điểm hiện tại cho riêng bạn trong Android (Java)

1. Trước khi bắt đầu

Tìm hiểu cách sử dụng Nền tảng Google Maps và Places SDK cho Android để hiển thị cho người dùng danh sách Địa điểm nhằm xác định vị trí hiện tại của họ.

bd07a9ad2cb27a06.png

Điều kiện tiên quyết

  • Kỹ năng cơ bản về Java

Bạn sẽ thực hiện

  • Thêm bản đồ vào ứng dụng Android.
  • Sử dụng quyền truy cập thông tin vị trí để xác định vị trí địa lý của người dùng.
  • Tìm nạp các Địa điểm gần vị trí hiện tại của người dùng.
  • Hiển thị những Địa điểm có khả năng cao cho người dùng để xác định vị trí hiện tại của họ.

Sản phẩm bạn sẽ tạo ra

Bạn tạo ứng dụng Android từ đầu, nhưng có thể tải mã mẫu xuống để so sánh khi gỡ lỗi. Tải mã mẫu xuống từ GitHub hoặc nếu bạn đã thiết lập Git để sử dụng dòng lệnh, hãy nhập nội dung sau:

git clone https://github.com/googlecodelabs/current-place-picker-android.git

Nếu bạn gặp vấn đề (lỗi trong đoạn mã, lỗi ngữ pháp, từ ngữ không rõ ràng hoặc vấn đề khác) khi thực hành theo lớp học lập trình này, vui lòng báo cáo vấn đề thông qua đường liên kết Báo cáo lỗi ở góc dưới bên trái lớp học lập trình.

2. Bắt đầu

Trước khi bắt đầu lớp học lập trình này, bạn cần thiết lập những nội dung sau:

Android Studio

Tải Android Studio xuống từ https://developer.android.com/studio.

Nếu bạn đã có Android Studio, hãy đảm bảo bạn có phiên bản mới nhất bằng cách nhấp vào Android Studio > Check for Updates... (Kiểm tra để tìm bản cập nhật...).

1f36bae83b64e33.png

Lớp học này được viết bằng Android Studio 3.4.

SDK Android

Trong Android Studio, bạn có thể định cấu hình các SDK mong muốn bằng Trình quản lý SDK. Lớp học này sử dụng Android Q SDK.

  1. Trên màn hình chào mừng của Android Studio, hãy nhấp vào Configure (Định cấu hình) > SDK Manager (Trình quản lý SDK).

d3fa03c269ec231c.png

  1. Chọn hộp đánh dấu SDK mà bạn muốn, rồi nhấp vào Áp dụng.

Nếu bạn chưa có SDK, thao tác này sẽ bắt đầu tải SDK xuống máy của bạn.

884e0aa1314f70d.png

Dịch vụ Google Play

Bạn cũng cần cài đặt Dịch vụ Google Play thông qua trình quản lý SDK.

  1. Nhấp vào thẻ SDK Tools (Bộ công cụ SDK) rồi chọn hộp đánh dấu Dịch vụ Google Play.

Cập nhật nếu trạng thái là Có bản cập nhật.

ad6211fd78f3b629.png

3. Chuẩn bị trình mô phỏng

Để chạy ứng dụng, bạn có thể kết nối thiết bị của riêng mình hoặc sử dụng Trình mô phỏng Android.

Nếu bạn đang sử dụng thiết bị của riêng mình, hãy chuyển đến phần Hướng dẫn cho thiết bị thực: Cập nhật Dịch vụ Google Play ở cuối trang này.

Thêm trình mô phỏng

  1. Trên màn hình chào mừng của Android Studio, hãy nhấp vào Configure (Định cấu hình) > AVD Manager (Trình quản lý AVD).

5dd2d14c9c56d3f9.png

Thao tác này sẽ mở hộp thoại Android Virtual Device Manager (Trình quản lý thiết bị ảo Android).

  1. Nhấp vào Create Virtual Device... (Tạo thiết bị ảo...) để mở danh sách các thiết bị mà bạn có thể chọn.

2d44eada384f8b35.png

  1. Chọn một thiết bị có biểu tượng Play d5722488d80cd6be.png trong cột Cửa hàng Play rồi nhấp vào Tiếp theo.

e0248f1c6e85ab7c.png

Bạn sẽ thấy một bộ hình ảnh hệ thống mà bạn có thể cài đặt. Nếu Q nhắm đến Android 9 trở lên (Google Play) có từ Tải xuống bên cạnh, hãy nhấp vào Tải xuống.

316d0d1efabd9f24.png

  1. Nhấp vào Next (Tiếp theo) để đặt tên cho thiết bị ảo, sau đó nhấp vào Finish (Hoàn tất).

Bạn sẽ quay lại danh sách Your Virtual Devices (Thiết bị ảo của bạn).

  1. Nhấp vào biểu tượng Bắt đầu ba8adffe56d3b678.png bên cạnh thiết bị mới:

7605864ed27f77ea.png

Sau vài giây, trình mô phỏng sẽ mở ra.

Hướng dẫn về trình mô phỏng – cập nhật Dịch vụ Google Play

  1. Sau khi trình mô phỏng chạy, hãy nhấp vào ... trong thanh điều hướng xuất hiện**.**

2e1156e02643d018.png

Thao tác này sẽ mở hộp thoại Extended controls (Chế độ điều khiển mở rộng).

  1. Nhấp vào Google Play trong trình đơn.

Nếu có bản cập nhật, hãy nhấp vào Cập nhật.

5afd2686c5cad0e5.png

  1. Đăng nhập vào trình mô phỏng bằng Tài khoản Google.

Bạn có thể sử dụng tài khoản của riêng mình hoặc tạo một tài khoản mới miễn phí để tách biệt hoạt động kiểm thử với thông tin cá nhân.

Sau đó, Google Play sẽ mở Dịch vụ Google Play.

  1. Nhấp vào Cập nhật để tải phiên bản mới nhất của Dịch vụ Google Play.

f4bc067e80630b9c.png

Nếu được yêu cầu hoàn tất quy trình thiết lập tài khoản và thêm phương thức thanh toán, hãy nhấp vào Bỏ qua.

Đặt vị trí trong trình mô phỏng

  1. Sau khi trình mô phỏng khởi chạy, hãy nhập "maps" vào thanh tìm kiếm trên màn hình chính để kéo biểu tượng ứng dụng Google Maps lên.

2d996aadd53685a6.png

  1. Nhấp vào biểu tượng để chạy.

Bạn sẽ thấy một bản đồ mặc định.

  1. Ở dưới cùng bên phải của bản đồ, hãy nhấp vào Vị trí của bạn c5b4e2fda57a7e71.png.

Bạn được yêu cầu cấp cho điện thoại quyền sử dụng thông tin vị trí.

f2b68044eabca151.png

  1. Nhấp vào biểu tượng ... để mở trình đơn Chế độ điều khiển mở rộng.
  2. Nhấp vào tab Vị trí.
  3. Nhập vĩ độ và kinh độ.

Bạn có thể nhập bất cứ nội dung nào mình muốn ở đây, nhưng hãy nhớ nhập nội dung ở một khu vực có nhiều địa điểm.

(Sử dụng Vĩ độ 20.7818 và Kinh độ -156.4624 cho thị trấn Kihei trên đảo Maui ở Hawaii để sao chép kết quả từ lớp học lập trình này.)

  1. Nhấp vào Gửi và bản đồ sẽ cập nhật vị trí này.

f9576b35218f4187.png

Bạn đã sẵn sàng chạy ứng dụng và kiểm thử ứng dụng bằng vị trí.

Hướng dẫn về thiết bị thực – cập nhật Dịch vụ Google Play

Nếu bạn đang sử dụng một thiết bị Android thực, hãy làm như sau:

  1. Sử dụng thanh tìm kiếm trên màn hình chính để tìm và mở Dịch vụ Google Play.
  2. Nhấp vào Chi tiết khác.

Nếu có, hãy nhấp vào Cập nhật.

ad16cdb975b5c3f7.png baf0379ef8a9c88c.png

4. Tạo cấu trúc cơ bản của ứng dụng bằng một hoạt động trên Google Maps

  1. Trên màn hình chào mừng của Android Studio, hãy chọn Start a new Android Studio project (Bắt đầu một dự án Android Studio mới).
  2. Trên thẻ Điện thoại và máy tính bảng, hãy chọn Hoạt động trên Google Maps.

c9c80aa8211a8761.png

Hộp thoại Configure your project (Định cấu hình dự án của bạn) sẽ mở ra. Đây là nơi bạn đặt tên cho ứng dụng và tạo gói dựa trên miền của bạn.

Sau đây là các chế độ cài đặt cho một ứng dụng có tên là Current Place (Vị trí hiện tại), tương ứng với gói com.google.codelab.currentplace.

37f5b93b94ee118c.png

  1. Chọn Java làm ngôn ngữ và chọn Sử dụng androidx. artifacts*.

Giữ nguyên các chế độ cài đặt mặc định cho các chế độ cài đặt còn lại.

  1. Nhấp vào Hoàn tất.

5. Thêm các phần phụ thuộc của Dịch vụ Google vào tệp bản dựng Gradle

Để truy cập vào các quyền truy cập vị trí trong Android, bạn cần có API Nhận dạng vị trí và hoạt động của Google trong Dịch vụ Google Play. Để biết thêm thông tin về cách thêm API này và các API khác của Dịch vụ Google Play, hãy xem phần Thiết lập Dịch vụ Google Play.

Các dự án Android Studio thường có 2 tệp build.gradle. Một tệp dành cho dự án tổng thể và một tệp dành cho ứng dụng. Nếu có Trình khám phá dự án Android Studio ở chế độ xem Android, bạn sẽ thấy cả hai tệp này trong thư mục Gradle Scripts. Bạn cần chỉnh sửa tệp build.gradle (Module: app) để thêm các dịch vụ của Google.

f3043429cf719c47.png

  1. Thêm hai dòng vào phần dependencies để thêm các dịch vụ của Google cho vị trí và Places API ( mã mẫu trong ngữ cảnh).

build.gradle (Mô-đun: app)

plugins {
  id 'com.android.application'
}

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.google.codelab.currentplace"
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'com.google.android.gms:play-services-maps:16.1.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'

    implementation 'com.google.android.gms:play-services-location:16.0.0'
    implementation 'com.google.android.libraries.places:places:1.1.0'
}

6. Bật API Nền tảng Google Maps và lấy khoá API

Đối với bước bật sau đây , bạn cần bật Maps SDK for AndroidPlaces API.

Thiết lập Nền tảng Google Maps

Nếu bạn chưa có tài khoản Google Cloud Platform và dự án có bật tính năng thanh toán, vui lòng xem hướng dẫn Bắt đầu sử dụng Google Maps Platform để tạo tài khoản thanh toán và dự án.

  1. Trong Cloud Console, hãy nhấp vào trình đơn thả xuống dự án rồi chọn dự án mà bạn muốn sử dụng cho lớp học lập trình này.

  1. Bật các API và SDK của Google Maps Platform cần thiết cho lớp học lập trình này trong Google Cloud Marketplace. Để làm như vậy, hãy làm theo các bước trong video này hoặc tài liệu này.
  2. Tạo khoá API trong trang Thông tin xác thực của Cloud Console. Bạn có thể làm theo các bước trong video này hoặc tài liệu này. Tất cả các yêu cầu gửi đến Nền tảng Google Maps đều cần có khoá API.

Sao chép khoá API mà bạn vừa tạo. Chuyển về Android Studio rồi tìm tệp google_maps_api.xml trong Android > app > res > values.

Thay thế YOUR_KEY_HERE bằng khoá API mà bạn đã sao chép.

aa576e551a7a1009.png

Ứng dụng của bạn hiện đã được định cấu hình.

7. Chỉnh sửa tệp bố cục

  1. Trong trình khám phá dự án, hãy mở tệp activity_maps.xml trong Android > app > res > layout.

4e0d986480c57efa.png

  1. Bạn sẽ thấy giao diện người dùng cơ bản mở ở bên phải màn hình, với các thẻ ở dưới cùng cho phép bạn chọn Design Editor hoặc Text Editor cho bố cục của mình. Chọn Text (Văn bản) rồi thay thế toàn bộ nội dung của tệp bố cục bằng nội dung sau:

activity_maps.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout 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"
    android:orientation="vertical">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:minHeight="?attr/actionBarSize"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:titleTextColor="@android:color/white"
        android:background="@color/colorPrimary" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <fragment
            android:id="@+id/map"
            android:name="com.google.android.gms.maps.SupportMapFragment"
            android:layout_width="match_parent"
            android:layout_height="349dp"
            tools:context=".MapsActivity" />

        <ListView
            android:id="@+id/listPlaces"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>

</LinearLayout>

Thao tác này sẽ cung cấp cho bạn một giao diện người dùng có dạng như sau:

1bf786808a4697ce.png

8. Thiết lập thanh ứng dụng

Để cung cấp cho người dùng một nút để nhấp vào khi họ muốn chọn vị trí hiện tại, hãy thêm một thanh ứng dụng có biểu tượng tìm vị trí hiện tại của người dùng và hiển thị các vị trí có khả năng ở gần đó. Giao diện đó sẽ giống như sau:

3a17c92b613a26c5.png

Trên điện thoại, chỉ biểu tượng mới xuất hiện. Trên máy tính bảng có nhiều không gian hơn, văn bản cũng được đưa vào.

Tạo biểu tượng

  1. Trong trình khám phá dự án, hãy nhấp vào Android > app, sau đó nhấp chuột phải vào thư mục res rồi chọn New > Image Asset (Mới > Thành phần hình ảnh).

Asset Studio sẽ mở ra.

  1. Trong trình đơn Loại biểu tượng, hãy nhấp vào Biểu tượng thẻ và thanh thao tác.
  2. Đặt tên cho thành phần của bạn ic_geolocate.
  3. Chọn Hình mẫu làm loại thành phần**.**
  4. Nhấp vào hình ảnh bên cạnh Clip Art (Hình mẫu).

Thao tác này sẽ mở cửa sổ Chọn biểu tượng.

  1. Chọn một biểu tượng.

Bạn có thể sử dụng thanh tìm kiếm để tìm các biểu tượng liên quan đến ý định của mình.

  1. Tìm location rồi chọn một biểu tượng liên quan đến vị trí.

Biểu tượng vị trí của tôi giống với biểu tượng được dùng trong ứng dụng Google Maps khi người dùng muốn di chuyển camera đến vị trí hiện tại của họ.

  1. Nhấp vào OK > Next > Finish (OK > Tiếp theo > Hoàn tất) rồi xác nhận rằng có một thư mục mới tên là drawable chứa các tệp biểu tượng mới của bạn.

b9e0196137ed18ae.png

Thêm tài nguyên chuỗi

  1. Trong trình khám phá dự án, hãy nhấp vào Android > app > res > values rồi mở tệp strings.xml.
  2. Thêm các dòng sau sau <string name="title_activity_maps">Map</string>:

strings.xml

    <string name="action_geolocate">Pick Place</string>
    <string name="default_info_title">Default Location</string>
    <string name="default_info_snippet">No places found, because location permission is disabled.</string>

Dòng đầu tiên được dùng trong thanh ứng dụng khi có đủ không gian để thêm nhãn văn bản bên cạnh biểu tượng. Các lớp còn lại được dùng cho những điểm đánh dấu mà bạn thêm vào bản đồ.

Bây giờ, mã trong tệp sẽ có dạng như sau:

<resources>
    <string name="app_name">Current Place</string>
    <string name="title_activity_maps">Map</string>
    <string name="action_geolocate">Pick Place</string>
    <string name="default_info_title">Default Location</string>
    <string name="default_info_snippet">No places found, because location permission is disabled.</string>
</resources>

Thêm thanh ứng dụng

  1. Trong trình khám phá dự án, hãy nhấp vào Android > app, sau đó nhấp chuột phải vào thư mục res rồi chọn New > Directory (Mới > Thư mục) để tạo một thư mục con mới trong app/src/main/res.
  2. Đặt tên cho thư mục là menu.
  3. Nhấp chuột phải vào thư mục menu rồi chọn New > File (Tệp).
  4. Đặt tên menu.xml cho tệp.
  5. Dán mã sau:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- "Locate me", should appear as action button if possible -->
    <item
        android:id="@+id/action_geolocate"
        android:icon="@drawable/ic_geolocate"
        android:title="@string/action_geolocate"
        app:showAsAction="always|withText" />

</menu>

Cập nhật kiểu thanh ứng dụng

  1. Trong trình khám phá dự án, hãy mở rộng Android > app > res > values rồi mở tệp styles.xml bên trong.
  2. Trong thẻ <style>, hãy chỉnh sửa thuộc tính mẹ thành "Theme.AppCompat.NoActionBar".
  3. Lưu ý thuộc tính name mà bạn sẽ dùng trong bước tiếp theo.

styles.xml

<style name="AppTheme" parent="Theme.AppCompat.NoActionBar">

Cập nhật giao diện của ứng dụng trong AndroidManifest.xml

  1. Nhấp vào Android > app > manifests rồi mở tệp AndroidManifest.xml.
  2. Tìm dòng android:theme rồi chỉnh sửa hoặc xác nhận giá trị là @style/AppTheme.

AndroidManifest.xml

   <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

Giờ bạn đã sẵn sàng bắt đầu lập trình!

9. Khởi chạy ứng dụng

  1. Trong trình khám phá dự án, hãy tìm tệp MapsActivity.java.

Thư mục này nằm trong thư mục tương ứng với gói mà bạn đã tạo cho ứng dụng ở bước 1.

8b0fa27d417f5f55.png

  1. Mở tệp này và bạn sẽ thấy trình chỉnh sửa mã Java.

Nhập Places SDK và các phần phụ thuộc khác

Thêm các dòng này vào đầu MapsActivity.java, thay thế các câu lệnh nhập hiện có.

Các tệp này bao gồm những tệp nhập hiện có và thêm nhiều tệp khác được dùng trong mã của lớp học lập trình này.

MapsActivity.java

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationServices;
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;

import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.google.android.libraries.places.api.Places;
import com.google.android.libraries.places.api.model.Place;
import com.google.android.libraries.places.api.model.PlaceLikelihood;
import com.google.android.libraries.places.api.net.FindCurrentPlaceRequest;
import com.google.android.libraries.places.api.net.FindCurrentPlaceResponse;
import com.google.android.libraries.places.api.net.PlacesClient;

import java.util.Arrays;
import java.util.List;

Cập nhật chữ ký lớp

Places API sử dụng các thành phần AndroidX để hỗ trợ khả năng tương thích ngược, vì vậy, bạn cần xác định thành phần này để mở rộng AppCompatActivity. Thao tác này sẽ thay thế tiện ích FragmentActivity được xác định theo mặc định cho một hoạt động trên bản đồ.

public class MapsActivity extends AppCompatActivity implements OnMapReadyCallback {

Thêm biến lớp

Tiếp theo, hãy khai báo các biến lớp được dùng trong nhiều phương thức lớp. Những thông tin này bao gồm các phần tử trên giao diện người dùng và mã trạng thái. Các dòng này phải nằm ngay bên dưới phần khai báo biến cho GoogleMap mMap.

    // New variables for Current Place picker
    private static final String TAG = "MapsActivity";
    ListView lstPlaces;
    private PlacesClient mPlacesClient;
    private FusedLocationProviderClient mFusedLocationProviderClient;

    // The geographical location where the device is currently located. That is, the last-known
    // location retrieved by the Fused Location Provider.
    private Location mLastKnownLocation;

    // A default location (Sydney, Australia) and default zoom to use when location permission is
    // not granted.
    private final LatLng mDefaultLocation = new LatLng(-33.8523341, 151.2106085);
    private static final int DEFAULT_ZOOM = 15;
    private static final int PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 1;
    private boolean mLocationPermissionGranted;

    // Used for selecting the Current Place.
    private static final int M_MAX_ENTRIES = 5;
    private String[] mLikelyPlaceNames;
    private String[] mLikelyPlaceAddresses;
    private String[] mLikelyPlaceAttributions;
    private LatLng[] mLikelyPlaceLatLngs;

Cập nhật phương thức onCreate

Bạn cần cập nhật phương thức onCreate để xử lý quyền của người dùng trong thời gian chạy cho dịch vụ vị trí, thiết lập các phần tử giao diện người dùng và tạo ứng dụng Places API.

Thêm các dòng mã sau đây liên quan đến thanh công cụ thao tác, chế độ thiết lập khung hiển thị và ứng dụng Places vào cuối phương thức onCreate() hiện có.

MapsActivity.java onCreate()

   @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);

        //
        // PASTE THE LINES BELOW THIS COMMENT
        //
        
        // Set up the action toolbar
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        // Set up the views
        lstPlaces = (ListView) findViewById(R.id.listPlaces);

        // Initialize the Places client
        String apiKey = getString(R.string.google_maps_key);
        Places.initialize(getApplicationContext(), apiKey);
        mPlacesClient = Places.createClient(this);
        mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
    }

Thêm mã cho trình đơn thanh ứng dụng

Hai phương thức này thêm trình đơn thanh ứng dụng (chỉ có một mục là biểu tượng Chọn địa điểm) và xử lý lượt nhấp của người dùng vào biểu tượng.

Sao chép 2 phương thức này vào tệp sau phương thức onCreate.

MapsActivity.java onCreateOptionsMenu() và onOptionsItemSelected()

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu, menu);

        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
           case R.id.action_geolocate:
                
                // COMMENTED OUT UNTIL WE DEFINE THE METHOD
                // Present the current place picker
                // pickCurrentPlace();
                return true;

            default:
                // If we got here, the user's action was not recognized.
                // Invoke the superclass to handle it.
                return super.onOptionsItemSelected(item);

        }
    }

Thử nghiệm

  1. Trong Android Studio, hãy nhấp vào Run (Chạy) hoặc Run menu > Run 'app' (Trình đơn Chạy > Chạy "ứng dụng").

28bea91c68c36fb2.png

  1. Bạn sẽ được yêu cầu chọn mục tiêu triển khai. Trình mô phỏng đang chạy sẽ xuất hiện trong danh sách này. Chọn mục này và Android Studio sẽ triển khai ứng dụng vào trình mô phỏng cho bạn.

f44658ca91f6f41a.png

Sau vài giây, ứng dụng sẽ khởi chạy. Bạn sẽ thấy bản đồ có tâm là Sydney, Úc, với một nút duy nhất và danh sách địa điểm chưa có thông tin.

68eb8c70f4748350.png

Tiêu điểm của bản đồ không di chuyển đến vị trí của người dùng, trừ phi bạn yêu cầu quyền truy cập vào vị trí của thiết bị.

10. Yêu cầu và xử lý quyền truy cập thông tin vị trí

Yêu cầu cấp quyền truy cập thông tin vị trí sau khi bản đồ đã sẵn sàng

  1. Xác định một phương thức có tên là getLocationPermission để yêu cầu quyền của người dùng.

Dán đoạn mã này bên dưới phương thức onOptionsSelected mà bạn vừa tạo.

MapsActivity.java getLocationPermission()

    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.
         */
        mLocationPermissionGranted = false;
        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);
        }
    }
  1. Thêm 2 dòng vào cuối phương thức onMapReady hiện có để bật các chế độ thu phóng và yêu cầu người dùng cấp quyền truy cập vào vị trí.

MapsActivity.java onMapReady()

   @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));

        //
        // PASTE THE LINES BELOW THIS COMMENT
        //

        // Enable the zoom controls for the map
        mMap.getUiSettings().setZoomControlsEnabled(true);

        // Prompt the user for permission.
        getLocationPermission();

    }

Xử lý kết quả từ các quyền được yêu cầu

Khi người dùng phản hồi hộp thoại yêu cầu cấp quyền, lệnh gọi lại này sẽ được Android gọi.

Dán mã này sau phương thức getLocationPermission():

MapsActivity.java onRequestPermissionsResult()

   /**
     * Handles the result of the request for location permissions
     */
    @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;
                }
            }
        }
    }

11. Lấy vị trí hiện tại và tìm nạp các địa điểm có thể

Khi người dùng nhấp vào Chọn địa điểm trong thanh ứng dụng, ứng dụng sẽ gọi phương thức pickCurrentPlace(), phương thức này sẽ gọi phương thức getDeviceLocation() mà bạn đã xác định trước đó. Phương thức getDeviceLocation gọi một phương thức khác, getCurrentPlaceLikelihoods, sau khi truy xuất vị trí mới nhất của thiết bị.

Gọi API findCurrentPlace và xử lý phản hồi

getCurrentPlaceLikelihoods tạo một findCurrentPlaceRequest và gọi tác vụ Places API findCurrentPlace. Nếu thành công, tác vụ này sẽ trả về một findCurrentPlaceResponse chứa danh sách các đối tượng placeLikelihood. Mỗi đối tượng trong danh sách này đều có một số thuộc tính, bao gồm tên, địa chỉ của địa điểm và xác suất bạn ở địa điểm đó (giá trị gấp đôi từ 0 đến 1). Phương thức này xử lý phản hồi bằng cách tạo danh sách thông tin chi tiết về địa điểm từ placeLikelihoods.

Đoạn mã này lặp lại qua 5 địa điểm có khả năng cao nhất và thêm những địa điểm có khả năng lớn hơn 0 vào một danh sách mà sau đó đoạn mã này sẽ hiển thị. Nếu bạn muốn hiển thị nhiều hơn hoặc ít hơn 5, hãy chỉnh sửa hằng số M_MAX_ENTRIES.

Dán mã này sau phương thức onMapReady.

MapsActivity.java getCurrentPlaceLikelihoods()

   private void getCurrentPlaceLikelihoods() {
        // 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);

        // 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 FindCurrentPlaceRequest request =
                FindCurrentPlaceRequest.builder(placeFields).build();
        Task<FindCurrentPlaceResponse> placeResponse = mPlacesClient.findCurrentPlace(request);
        placeResponse.addOnCompleteListener(this,
                new OnCompleteListener<FindCurrentPlaceResponse>() {
                    @Override
                    public void onComplete(@NonNull Task<FindCurrentPlaceResponse> task) {
                        if (task.isSuccessful()) {
                            FindCurrentPlaceResponse response = task.getResult();
                            // Set the count, handling cases where less than 5 entries are returned.
                            int count;
                            if (response.getPlaceLikelihoods().size() < M_MAX_ENTRIES) {
                                count = response.getPlaceLikelihoods().size();
                            } else {
                                count = M_MAX_ENTRIES;
                            }

                            int i = 0;
                            mLikelyPlaceNames = new String[count];
                            mLikelyPlaceAddresses = new String[count];
                            mLikelyPlaceAttributions = new String[count];
                            mLikelyPlaceLatLngs = new LatLng[count];

                            for (PlaceLikelihood placeLikelihood : response.getPlaceLikelihoods()) {
                                Place currPlace = placeLikelihood.getPlace();
                                mLikelyPlaceNames[i] = currPlace.getName();
                                mLikelyPlaceAddresses[i] = currPlace.getAddress();
                                mLikelyPlaceAttributions[i] = (currPlace.getAttributions() == null) ?
                                        null : TextUtils.join(" ", currPlace.getAttributions());
                                mLikelyPlaceLatLngs[i] = currPlace.getLatLng();

                                String currLatLng = (mLikelyPlaceLatLngs[i] == null) ?
                                        "" : mLikelyPlaceLatLngs[i].toString();

                                Log.i(TAG, String.format("Place " + currPlace.getName()
                                        + " has likelihood: " + placeLikelihood.getLikelihood()
                                        + " at " + currLatLng));

                                i++;
                                if (i > (count - 1)) {
                                    break;
                                }
                            }


                            // COMMENTED OUT UNTIL WE DEFINE THE METHOD
                            // Populate the ListView
                            // fillPlacesList();
                        } else {
                            Exception exception = task.getException();
                            if (exception instanceof ApiException) {
                                ApiException apiException = (ApiException) exception;
                                Log.e(TAG, "Place not found: " + apiException.getStatusCode());
                            }
                        }
                    }
                });
    }

Di chuyển camera trên bản đồ đến vị trí hiện tại của thiết bị

Nếu người dùng cấp quyền, ứng dụng sẽ tìm nạp vị trí mới nhất của người dùng và di chuyển camera để tập trung vào vị trí đó.

Nếu người dùng từ chối cấp quyền, ứng dụng sẽ chỉ di chuyển camera đến vị trí mặc định được xác định trong số các hằng số ở đầu trang này (trong mã mẫu, đó là Sydney, Úc).

Dán mã này sau phương thức getPlaceLikelihoods():

MapsActivity.java getDeviceLocation()

    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<Location> locationResult = mFusedLocationProviderClient.getLastLocation();
                locationResult.addOnCompleteListener(this, new OnCompleteListener<Location>() {
                    @Override
                    public void onComplete(@NonNull Task<Location> task) {
                        if (task.isSuccessful()) {
                            // Set the map's camera position to the current location of the device.
                            mLastKnownLocation = task.getResult();
                            Log.d(TAG, "Latitude: " + mLastKnownLocation.getLatitude());
                            Log.d(TAG, "Longitude: " + mLastKnownLocation.getLongitude());
                            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));
                        }

                       getCurrentPlaceLikelihoods();
                    }
                });
            }
        } catch (SecurityException e)  {
            Log.e("Exception: %s", e.getMessage());
        }
    }

Kiểm tra quyền truy cập thông tin vị trí khi người dùng nhấp vào Chọn địa điểm

Khi người dùng nhấn vào Chọn địa điểm, phương thức này sẽ kiểm tra các quyền truy cập vị trí và nhắc người dùng cấp quyền nếu họ chưa cấp.

Nếu người dùng đã cấp quyền, thì phương thức này sẽ gọi getDeviceLocation để bắt đầu quy trình lấy các địa điểm có khả năng là địa điểm hiện tại.

  1. Thêm phương thức này sau getDeviceLocation():

MapsActivity.java pickCurrentPlace()

   private void pickCurrentPlace() {
        if (mMap == null) {
            return;
        }

        if (mLocationPermissionGranted) {
            getDeviceLocation();
        } 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();
        }
    }
  1. Giờ đây, khi pickCurrentPlace được xác định, hãy tìm dòng trong onOptionsItemSelected() gọi pickCurrentPlace và bỏ chú thích dòng đó.

MapsActivity.java onOptionItemSelected()

           case R.id.action_geolocate:

                // COMMENTED OUT UNTIL WE DEFINE THE METHOD
                // Present the Current Place picker
                pickCurrentPlace();
                return true;

Thử nghiệm

Nếu bạn chạy ứng dụng ngay bây giờ và nhấn vào Pick Place (Chọn địa điểm), ứng dụng sẽ nhắc bạn cấp quyền truy cập thông tin vị trí.

  • Nếu bạn cho phép, lựa chọn ưu tiên đó sẽ được lưu và bạn sẽ không nhận được lời nhắc. Nếu từ chối cấp quyền, bạn sẽ nhận được lời nhắc vào lần tiếp theo nhấn vào nút này.
  • Mặc dù getPlaceLikelihoods đã tìm nạp được những địa điểm có khả năng là địa điểm hiện tại, nhưng ListView vẫn chưa hiển thị những địa điểm đó. Trong Android Studio, bạn có thể nhấp vào ⌘6 để kiểm tra nhật ký trong Logcat đối với các câu lệnh được gắn thẻ MapsActivity nhằm xác minh rằng các phương thức mới của bạn đang hoạt động đúng cách.
  • Nếu bạn đã cấp quyền, nhật ký sẽ có một câu lệnh cho Latitude: và một câu lệnh cho Longitude: cho biết vị trí được phát hiện của thiết bị. Nếu trước đây bạn đã sử dụng Google Maps và trình đơn mở rộng của trình mô phỏng để chỉ định một vị trí cho trình mô phỏng, thì các câu lệnh này sẽ cho biết vị trí đó.
  • Nếu lệnh gọi đến findCurrentPlace thành công, nhật ký sẽ bao gồm 5 câu lệnh in tên và vị trí của 5 địa điểm có khả năng cao nhất.

d9896a245b81bf3.png

12. Điền sẵn thông tin vào bộ chọn Địa điểm hiện tại

Thiết lập trình xử lý cho các địa điểm được chọn

Hãy nghĩ về điều mà chúng ta muốn xảy ra khi người dùng nhấp vào một mục trong ListView. Để xác nhận lựa chọn của người dùng về địa điểm mà họ đang ở, bạn có thể thêm một điểm đánh dấu vào bản đồ tại địa điểm đó. Nếu người dùng nhấp vào điểm đánh dấu đó, một cửa sổ thông tin sẽ bật lên, hiển thị tên và địa chỉ của địa điểm.

Dán trình xử lý lượt nhấp này sau phương thức pickCurrentPlace.

MapsActivity.java listClickedHandler

    private AdapterView.OnItemClickListener listClickedHandler = new AdapterView.OnItemClickListener() {
        public void onItemClick(AdapterView parent, View v, int position, long id) {
            // position will give us the index of which place was selected in the array
            LatLng markerLatLng = mLikelyPlaceLatLngs[position];
            String markerSnippet = mLikelyPlaceAddresses[position];
            if (mLikelyPlaceAttributions[position] != null) {
                markerSnippet = markerSnippet + "\n" + mLikelyPlaceAttributions[position];
            }

            // Add a marker for the selected place, with an info window
            // showing information about that place.
            mMap.addMarker(new MarkerOptions()
                    .title(mLikelyPlaceNames[position])
                    .position(markerLatLng)
                    .snippet(markerSnippet));

           // Position the map's camera at the location of the marker.
            mMap.moveCamera(CameraUpdateFactory.newLatLng(markerLatLng));
        }
    };

Điền dữ liệu vào ListView

Giờ đây, khi đã có danh sách những địa điểm mà người dùng có khả năng đang ghé thăm nhất, bạn có thể trình bày những lựa chọn đó cho người dùng trong ListView. bạn cũng có thể đặt trình nghe lượt nhấp ListView để sử dụng trình xử lý lượt nhấp mà bạn vừa xác định.

Dán phương thức này sau trình xử lý lượt nhấp:

MapsActivity.java fillPlacesList()

    private void fillPlacesList() {
        // Set up an ArrayAdapter to convert likely places into TextViews to populate the ListView
        ArrayAdapter<String> placesAdapter =
                new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mLikelyPlaceNames);
        lstPlaces.setAdapter(placesAdapter);
        lstPlaces.setOnItemClickListener(listClickedHandler);
    }

Giờ đây, khi fillPlacesList được xác định, hãy tìm dòng gần cuối findPlaceLikelihoods gọi fillPlacesList và bỏ chú thích dòng đó.

MapsActivity.java fillPlaceLikelihoods()

               // COMMENTED OUT UNTIL WE DEFINE THE METHOD
                // Populate the ListView
                fillPlacesList();

Đó là tất cả mã cần thiết cho bộ chọn Địa điểm hiện tại!

13. Chạy ứng dụng

Thử chọn một địa điểm

  1. Chạy lại ứng dụng.

Lần này, khi bạn nhấn vào Chọn địa điểm, ứng dụng sẽ điền sẵn danh sách các địa điểm có tên gần với vị trí đó. Gần địa điểm này ở Maui có những nơi như Ululani's Hawaiian Shave Ice và Sugar Beach Bake Shop. Vì một số địa điểm rất gần với toạ độ vị trí, nên đây là danh sách những địa điểm có khả năng bạn đang ở.

  1. Nhấp vào tên của một địa điểm trong ListView.

Bạn sẽ thấy một điểm đánh dấu được thêm vào bản đồ.

  1. Nhấn vào điểm đánh dấu.

Bạn có thể xem Chi tiết về địa điểm.

e52303cc0de6a513.png 864c74342fb52a01.png

Thử một vị trí khác

Nếu bạn muốn thay đổi vị trí và đang sử dụng trình mô phỏng, thì vị trí thiết bị sẽ không tự động cập nhật khi bạn cập nhật toạ độ vị trí trong trình đơn mở rộng của trình mô phỏng.

Để khắc phục vấn đề này, hãy làm theo các bước sau để sử dụng ứng dụng Google Maps gốc nhằm buộc cập nhật vị trí của trình mô phỏng:

  1. Mở Google Maps.
  2. Nhấn vào biểu tượng ... > Vị trí để thay đổi vĩ độ và kinh độ thành toạ độ mới, rồi nhấn vào Gửi.
  3. Ví dụ: bạn có thể sử dụng Vĩ độ: 49.2768 và Kinh độ: -123.1142 để đặt vị trí ở trung tâm thành phố Vancouver, Canada.
  4. Xác minh rằng Google Maps đã đặt lại tâm bản đồ theo toạ độ mới của bạn. Bạn có thể cần nhấn vào nút Vị trí của tôi trong ứng dụng Google Maps để yêu cầu căn lại chính giữa.
  5. Quay lại ứng dụng Current Place rồi nhấn vào Pick Place (Chọn địa điểm) để xem bản đồ ở toạ độ mới và danh sách mới gồm các địa điểm có khả năng là địa điểm hiện tại.

9adb99d1ce25c184.png

Chỉ vậy thôi! Bạn đã tạo một ứng dụng đơn giản để kiểm tra các địa điểm tại vị trí hiện tại và cho bạn biết khả năng bạn đang ở địa điểm nào. Chúc bạn học vui!

Bây giờ, hãy chạy ứng dụng với những nội dung sửa đổi mà bạn đã thực hiện để hoàn tất bước thưởng này!

14. Các bước tiếp theo

Để tránh bị đánh cắp khoá API, bạn cần bảo mật khoá để chỉ ứng dụng Android của bạn mới dùng được khoá đó. Nếu bạn không hạn chế truy cập, bất kỳ ai có khoá của bạn đều có thể dùng khoá đó để gọi API trên Nền tảng Google Maps và khiến bạn bị tính phí.

Nhận chứng chỉ SHA-1

Bạn sẽ cần thông tin này sau này khi hạn chế khoá API. Sau đây là một bộ hướng dẫn để lấy chứng chỉ gỡ lỗi.

Đối với Linux hoặc macOS, hãy mở cửa sổ dòng lệnh rồi nhập nội dung sau:

keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android

Đối với Windows Vista và Windows 7, hãy chạy lệnh sau:

keytool -list -v -keystore "%USERPROFILE%\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android

Bạn sẽ thấy kết quả xuất ra có dạng như sau:

Alias name: androiddebugkey
Creation date: Jan 01, 2013
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=Android Debug, O=Android, C=US
Issuer: CN=Android Debug, O=Android, C=US
Serial number: 4aa9b300
Valid from: Mon Jan 01 08:04:04 UTC 2013 until: Mon Jan 01 18:04:04 PST 2033
Certificate fingerprints:
     MD5:  AE:9F:95:D0:A6:86:89:BC:A8:70:BA:34:FF:6A:AC:F9
     SHA1: BB:0D:AC:74:D3:21:E1:43:07:71:9B:62:90:AF:A1:66:6E:44:5D:75
     Signature algorithm name: SHA1withRSA
     Version: 3

Dòng bắt đầu bằng SHA1 chứa dấu vân tay SHA-1 của chứng chỉ. Dấu vân tay là chuỗi gồm 20 số thập lục phân có hai chữ số, được phân tách bằng dấu hai chấm.

Khi bạn đã sẵn sàng phát hành một ứng dụng, hãy sử dụng hướng dẫn trong tài liệu này để truy xuất chứng chỉ phát hành.

Thêm các quy tắc hạn chế cho khoá API

  1. Trong Cloud Console, hãy chuyển đến phần API và Dịch vụ > Thông tin xác thực.

Khoá bạn đã dùng cho ứng dụng này sẽ xuất hiện trong phần Khoá API.

  1. Nhấp vào biểu tượng 6454a04865d551e6.png để chỉnh sửa chế độ cài đặt khoá.

316b052c621ee91c.png

  1. Trên trang khoá API, sau mục Key restrictions (Hạn chế cho khoá), hãy đặt Application restrictions (Hạn chế cho ứng dụng) bằng cách làm như sau:
  2. Chọn Ứng dụng Android rồi làm theo hướng dẫn.
  3. Nhấp vào Thêm một mục.
  4. Nhập tên gói và dấu vân tay chứng chỉ SHA-1 (đã truy xuất trong phần trước).

Ví dụ:

com.google.codelab.currentplace
BB:0D:AC:74:D3:21:E1:43:07:71:9B:62:90:AF:A1:66:6E:44:5D:75s
  1. Để tăng cường bảo vệ, hãy đặt API restrictions (Hạn chế cho API) bằng cách làm như sau.
  2. Sau khi chọn API restrictions (Hạn chế cho API), hãy chọn Restrict key (Hạn chế cho khoá).
  3. Chọn Maps SDK cho Android và Places API.
  4. Nhấp vào XongLưu.

15. Xin chúc mừng

Bạn đã tạo một ứng dụng đơn giản để kiểm tra những địa điểm có khả năng xuất hiện nhất tại vị trí hiện tại và thêm một điểm đánh dấu vào bản đồ cho địa điểm mà người dùng chọn.

Tìm hiểu thêm