지도에서 현재 장소 선택 및 세부정보 표시하기

이 가이드에서는 Android 기기의 현재 위치를 찾고 해당 위치에 있는 장소(비즈니스 또는 기타 관심 장소)의 세부정보를 표시하는 방법을 설명합니다. Maps SDK for Android, Places SDK for Android, Google Play 서비스 Location API의 통합 위치 정보 제공자를 사용하여 Android 앱을 빌드하려면 이 가이드를 따르세요.

코드 가져오기

GitHub에서 Google Maps Android API v2 샘플 저장소를 복제하거나 다운로드하세요.

활동의 자바 버전을 확인하세요.

    // Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.example.currentplacedetailsonmap;

import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Bundle;

import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;

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

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.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.tasks.OnCompleteListener;
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;

/**
 * An activity that displays a map showing the place at the device's current location.
 */
public class MapsActivityCurrentPlace extends AppCompatActivity
        implements OnMapReadyCallback {

    private static final String TAG = MapsActivityCurrentPlace.class.getSimpleName();
    private GoogleMap map;
    private CameraPosition cameraPosition;

    // The entry point to the Places API.
    private PlacesClient placesClient;

    // The entry point to the Fused Location Provider.
    private FusedLocationProviderClient fusedLocationProviderClient;

    // A default location (Sydney, Australia) and default zoom to use when location permission is
    // not granted.
    private final LatLng defaultLocation = 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 locationPermissionGranted;

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

    // Keys for storing activity state.
    private static final String KEY_CAMERA_POSITION = "camera_position";
    private static final String KEY_LOCATION = "location";

    // Used for selecting the current place.
    private static final int M_MAX_ENTRIES = 5;
    private String[] likelyPlaceNames;
    private String[] likelyPlaceAddresses;
    private List[] likelyPlaceAttributions;
    private LatLng[] likelyPlaceLatLngs;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Retrieve location and camera position from saved instance state.
        if (savedInstanceState != null) {
            lastKnownLocation = savedInstanceState.getParcelable(KEY_LOCATION);
            cameraPosition = savedInstanceState.getParcelable(KEY_CAMERA_POSITION);
        }

        // Retrieve the content view that renders the map.
        setContentView(R.layout.activity_maps);

        // Construct a PlacesClient
        Places.initialize(getApplicationContext(), BuildConfig.MAPS_API_KEY);
        placesClient = Places.createClient(this);

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

        // Build the map.
        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);
    }

    /**
     * Saves the state of the map when the activity is paused.
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        if (map != null) {
            outState.putParcelable(KEY_CAMERA_POSITION, map.getCameraPosition());
            outState.putParcelable(KEY_LOCATION, lastKnownLocation);
        }
        super.onSaveInstanceState(outState);
    }

    /**
     * Sets up the options menu.
     * @param menu The options menu.
     * @return Boolean.
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.current_place_menu, menu);
        return true;
    }

    /**
     * Handles a click on the menu option to get a place.
     * @param item The menu item to handle.
     * @return Boolean.
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.option_get_place) {
            showCurrentPlace();
        }
        return true;
    }

    /**
     * Manipulates the map when it's available.
     * This callback is triggered when the map is ready to be used.
     */
    @Override
    public void onMapReady(GoogleMap map) {
        this.map = map;

        // Use a custom info window adapter to handle multiple lines of text in the
        // info window contents.
        this.map.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,
                        (FrameLayout) findViewById(R.id.map), false);

                TextView title = infoWindow.findViewById(R.id.title);
                title.setText(marker.getTitle());

                TextView snippet = infoWindow.findViewById(R.id.snippet);
                snippet.setText(marker.getSnippet());

                return infoWindow;
            }
        });

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

        // 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();
    }

    /**
     * Gets the current location of the device, and positions the map's camera.
     */
    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 (locationPermissionGranted) {
                Task<Location> locationResult = fusedLocationProviderClient.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.
                            lastKnownLocation = task.getResult();
                            if (lastKnownLocation != null) {
                                map.moveCamera(CameraUpdateFactory.newLatLngZoom(
                                        new LatLng(lastKnownLocation.getLatitude(),
                                                lastKnownLocation.getLongitude()), DEFAULT_ZOOM));
                            }
                        } else {
                            Log.d(TAG, "Current location is null. Using defaults.");
                            Log.e(TAG, "Exception: %s", task.getException());
                            map.moveCamera(CameraUpdateFactory
                                    .newLatLngZoom(defaultLocation, DEFAULT_ZOOM));
                            map.getUiSettings().setMyLocationButtonEnabled(false);
                        }
                    }
                });
            }
        } catch (SecurityException e)  {
            Log.e("Exception: %s", e.getMessage(), e);
        }
    }

    /**
     * Prompts the user for permission to use the device location.
     */
    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) {
            locationPermissionGranted = true;
        } else {
            ActivityCompat.requestPermissions(this,
                    new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
                    PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
        }
    }

    /**
     * Handles the result of the request for location permissions.
     */
    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        locationPermissionGranted = 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) {
                    locationPermissionGranted = true;
                }
            }
        }
        updateLocationUI();
    }

    /**
     * Prompts the user to select the current place from a list of likely places, and shows the
     * current place on the map - provided the user has granted location permission.
     */
    private void showCurrentPlace() {
        if (map == null) {
            return;
        }

        if (locationPermissionGranted) {
            // 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 =
                    placesClient.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;
                        likelyPlaceNames = new String[count];
                        likelyPlaceAddresses = new String[count];
                        likelyPlaceAttributions = new List[count];
                        likelyPlaceLatLngs = new LatLng[count];

                        for (PlaceLikelihood placeLikelihood : likelyPlaces.getPlaceLikelihoods()) {
                            // Build a list of likely places to show the user.
                            likelyPlaceNames[i] = placeLikelihood.getPlace().getName();
                            likelyPlaceAddresses[i] = placeLikelihood.getPlace().getAddress();
                            likelyPlaceAttributions[i] = placeLikelihood.getPlace()
                                    .getAttributions();
                            likelyPlaceLatLngs[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.
            map.addMarker(new MarkerOptions()
                    .title(getString(R.string.default_info_title))
                    .position(defaultLocation)
                    .snippet(getString(R.string.default_info_snippet)));

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

    /**
     * Displays a form allowing the user to select a place from a list of likely places.
     */
    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 = likelyPlaceLatLngs[which];
                String markerSnippet = likelyPlaceAddresses[which];
                if (likelyPlaceAttributions[which] != null) {
                    markerSnippet = markerSnippet + "\n" + likelyPlaceAttributions[which];
                }

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

                // Position the map's camera at the location of the marker.
                map.moveCamera(CameraUpdateFactory.newLatLngZoom(markerLatLng,
                        DEFAULT_ZOOM));
            }
        };

        // Display the dialog.
        AlertDialog dialog = new AlertDialog.Builder(this)
                .setTitle(R.string.pick_place)
                .setItems(likelyPlaceNames, listener)
                .show();
    }

    /**
     * Updates the map's UI settings based on whether the user has granted location permission.
     */
    private void updateLocationUI() {
        if (map == null) {
            return;
        }
        try {
            if (locationPermissionGranted) {
                map.setMyLocationEnabled(true);
                map.getUiSettings().setMyLocationButtonEnabled(true);
            } else {
                map.setMyLocationEnabled(false);
                map.getUiSettings().setMyLocationButtonEnabled(false);
                lastKnownLocation = null;
                getLocationPermission();
            }
        } catch (SecurityException e)  {
            Log.e("Exception: %s", e.getMessage());
        }
    }
}

    

활동의 Kotlin 버전을 확인하세요.

    // Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.example.currentplacedetailsonmap

import android.Manifest
import android.annotation.SuppressLint
import android.content.DialogInterface
import android.content.pm.PackageManager
import android.location.Location
import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.FrameLayout
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
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.GoogleMap.InfoWindowAdapter
import com.google.android.gms.maps.OnMapReadyCallback
import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.model.CameraPosition
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.Marker
import com.google.android.gms.maps.model.MarkerOptions
import com.google.android.libraries.places.api.Places
import com.google.android.libraries.places.api.model.Place
import com.google.android.libraries.places.api.net.FindCurrentPlaceRequest
import com.google.android.libraries.places.api.net.PlacesClient

/**
 * An activity that displays a map showing the place at the device's current location.
 */
class MapsActivityCurrentPlace : AppCompatActivity(), OnMapReadyCallback {
    private var map: GoogleMap? = null
    private var cameraPosition: CameraPosition? = null

    // The entry point to the Places API.
    private lateinit var placesClient: PlacesClient

    // The entry point to the Fused Location Provider.
    private lateinit var fusedLocationProviderClient: FusedLocationProviderClient

    // A default location (Sydney, Australia) and default zoom to use when location permission is
    // not granted.
    private val defaultLocation = LatLng(-33.8523341, 151.2106085)
    private var locationPermissionGranted = false

    // The geographical location where the device is currently located. That is, the last-known
    // location retrieved by the Fused Location Provider.
    private var lastKnownLocation: Location? = null
    private var likelyPlaceNames: Array<String?> = arrayOfNulls(0)
    private var likelyPlaceAddresses: Array<String?> = arrayOfNulls(0)
    private var likelyPlaceAttributions: Array<List<*>?> = arrayOfNulls(0)
    private var likelyPlaceLatLngs: Array<LatLng?> = arrayOfNulls(0)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Retrieve location and camera position from saved instance state.
        if (savedInstanceState != null) {
            lastKnownLocation = savedInstanceState.getParcelable(KEY_LOCATION)
            cameraPosition = savedInstanceState.getParcelable(KEY_CAMERA_POSITION)
        }

        // Retrieve the content view that renders the map.
        setContentView(R.layout.activity_maps)

        // Construct a PlacesClient
        Places.initialize(applicationContext, BuildConfig.MAPS_API_KEY)
        placesClient = Places.createClient(this)

        // Construct a FusedLocationProviderClient.
        fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)

        // Build the map.
        val mapFragment = supportFragmentManager
            .findFragmentById(R.id.map) as SupportMapFragment?
        mapFragment?.getMapAsync(this)
    }

    /**
     * Saves the state of the map when the activity is paused.
     */
    override fun onSaveInstanceState(outState: Bundle) {
        map?.let { map ->
            outState.putParcelable(KEY_CAMERA_POSITION, map.cameraPosition)
            outState.putParcelable(KEY_LOCATION, lastKnownLocation)
        }
        super.onSaveInstanceState(outState)
    }

    /**
     * Sets up the options menu.
     * @param menu The options menu.
     * @return Boolean.
     */
    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menuInflater.inflate(R.menu.current_place_menu, menu)
        return true
    }

    /**
     * Handles a click on the menu option to get a place.
     * @param item The menu item to handle.
     * @return Boolean.
     */
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        if (item.itemId == R.id.option_get_place) {
            showCurrentPlace()
        }
        return true
    }

    /**
     * Manipulates the map when it's available.
     * This callback is triggered when the map is ready to be used.
     */
    override fun onMapReady(map: GoogleMap) {
        this.map = map

        // Use a custom info window adapter to handle multiple lines of text in the
        // info window contents.
        this.map?.setInfoWindowAdapter(object : InfoWindowAdapter {
            // Return null here, so that getInfoContents() is called next.
            override fun getInfoWindow(arg0: Marker): View? {
                return null
            }

            override fun getInfoContents(marker: Marker): View {
                // Inflate the layouts for the info window, title and snippet.
                val infoWindow = layoutInflater.inflate(R.layout.custom_info_contents,
                    findViewById<FrameLayout>(R.id.map), false)
                val title = infoWindow.findViewById<TextView>(R.id.title)
                title.text = marker.title
                val snippet = infoWindow.findViewById<TextView>(R.id.snippet)
                snippet.text = marker.snippet
                return infoWindow
            }
        })

        // Prompt the user for permission.
        getLocationPermission()

        // 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()
    }

    /**
     * Gets the current location of the device, and positions the map's camera.
     */
    private fun 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 (locationPermissionGranted) {
                val locationResult = fusedLocationProviderClient.lastLocation
                locationResult.addOnCompleteListener(this) { task ->
                    if (task.isSuccessful) {
                        // Set the map's camera position to the current location of the device.
                        lastKnownLocation = task.result
                        if (lastKnownLocation != null) {
                            map?.moveCamera(CameraUpdateFactory.newLatLngZoom(
                                LatLng(lastKnownLocation!!.latitude,
                                    lastKnownLocation!!.longitude), DEFAULT_ZOOM.toFloat()))
                        }
                    } else {
                        Log.d(TAG, "Current location is null. Using defaults.")
                        Log.e(TAG, "Exception: %s", task.exception)
                        map?.moveCamera(CameraUpdateFactory
                            .newLatLngZoom(defaultLocation, DEFAULT_ZOOM.toFloat()))
                        map?.uiSettings?.isMyLocationButtonEnabled = false
                    }
                }
            }
        } catch (e: SecurityException) {
            Log.e("Exception: %s", e.message, e)
        }
    }

    /**
     * Prompts the user for permission to use the device location.
     */
    private fun 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.applicationContext,
                Manifest.permission.ACCESS_FINE_LOCATION)
            == PackageManager.PERMISSION_GRANTED) {
            locationPermissionGranted = true
        } else {
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION)
        }
    }

    /**
     * Handles the result of the request for location permissions.
     */
    override fun onRequestPermissionsResult(requestCode: Int,
                                            permissions: Array<String>,
                                            grantResults: IntArray) {
        locationPermissionGranted = false
        when (requestCode) {
            PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION -> {

                // If request is cancelled, the result arrays are empty.
                if (grantResults.isNotEmpty() &&
                    grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    locationPermissionGranted = true
                }
            }
        }
        updateLocationUI()
    }

    /**
     * Prompts the user to select the current place from a list of likely places, and shows the
     * current place on the map - provided the user has granted location permission.
     */
    @SuppressLint("MissingPermission")
    private fun showCurrentPlace() {
        if (map == null) {
            return
        }
        if (locationPermissionGranted) {
            // Use fields to define the data types to return.
            val placeFields = listOf(Place.Field.NAME, Place.Field.ADDRESS, Place.Field.LAT_LNG)

            // Use the builder to create a FindCurrentPlaceRequest.
            val 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.
            val placeResult = placesClient.findCurrentPlace(request)
            placeResult.addOnCompleteListener { task ->
                if (task.isSuccessful && task.result != null) {
                    val likelyPlaces = task.result

                    // Set the count, handling cases where less than 5 entries are returned.
                    val count = if (likelyPlaces != null && likelyPlaces.placeLikelihoods.size < M_MAX_ENTRIES) {
                        likelyPlaces.placeLikelihoods.size
                    } else {
                        M_MAX_ENTRIES
                    }
                    var i = 0
                    likelyPlaceNames = arrayOfNulls(count)
                    likelyPlaceAddresses = arrayOfNulls(count)
                    likelyPlaceAttributions = arrayOfNulls<List<*>?>(count)
                    likelyPlaceLatLngs = arrayOfNulls(count)
                    for (placeLikelihood in likelyPlaces?.placeLikelihoods ?: emptyList()) {
                        // Build a list of likely places to show the user.
                        likelyPlaceNames[i] = placeLikelihood.place.name
                        likelyPlaceAddresses[i] = placeLikelihood.place.address
                        likelyPlaceAttributions[i] = placeLikelihood.place.attributions
                        likelyPlaceLatLngs[i] = placeLikelihood.place.latLng
                        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.
                    openPlacesDialog()
                } else {
                    Log.e(TAG, "Exception: %s", task.exception)
                }
            }
        } 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.
            map?.addMarker(MarkerOptions()
                .title(getString(R.string.default_info_title))
                .position(defaultLocation)
                .snippet(getString(R.string.default_info_snippet)))

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

    /**
     * Displays a form allowing the user to select a place from a list of likely places.
     */
    private fun openPlacesDialog() {
        // Ask the user to choose the place where they are now.
        val listener = DialogInterface.OnClickListener { dialog, which -> // The "which" argument contains the position of the selected item.
            val markerLatLng = likelyPlaceLatLngs[which]
            var markerSnippet = likelyPlaceAddresses[which]
            if (likelyPlaceAttributions[which] != null) {
                markerSnippet = """
                    $markerSnippet
                    ${likelyPlaceAttributions[which]}
                    """.trimIndent()
            }

            // Add a marker for the selected place, with an info window
            // showing information about that place.
            map?.addMarker(MarkerOptions()
                .title(likelyPlaceNames[which])
                .position(markerLatLng!!)
                .snippet(markerSnippet))

            // Position the map's camera at the location of the marker.
            map?.moveCamera(CameraUpdateFactory.newLatLngZoom(markerLatLng,
                DEFAULT_ZOOM.toFloat()))
        }

        // Display the dialog.
        AlertDialog.Builder(this)
            .setTitle(R.string.pick_place)
            .setItems(likelyPlaceNames, listener)
            .show()
    }

    /**
     * Updates the map's UI settings based on whether the user has granted location permission.
     */
    private fun updateLocationUI() {
        if (map == null) {
            return
        }
        try {
            if (locationPermissionGranted) {
                map?.isMyLocationEnabled = true
                map?.uiSettings?.isMyLocationButtonEnabled = true
            } else {
                map?.isMyLocationEnabled = false
                map?.uiSettings?.isMyLocationButtonEnabled = false
                lastKnownLocation = null
                getLocationPermission()
            }
        } catch (e: SecurityException) {
            Log.e("Exception: %s", e.message, e)
        }
    }

    companion object {
        private val TAG = MapsActivityCurrentPlace::class.java.simpleName
        private const val DEFAULT_ZOOM = 15
        private const val PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 1

        // Keys for storing activity state.
        private const val KEY_CAMERA_POSITION = "camera_position"
        private const val KEY_LOCATION = "location"

        // Used for selecting the current place.
        private const val M_MAX_ENTRIES = 5
    }
}

    

개발 프로젝트 설정

다음 단계에 따라 Android 스튜디오에서 가이드 프로젝트를 만듭니다.

  1. Android 스튜디오를 다운로드하여 설치합니다.
  2. Android 스튜디오에 Google Play 서비스 패키지를 추가합니다.
  3. 이 가이드를 읽기 전에 미리 하지 않은 경우 Google Maps Android API v2 샘플 저장소를 복제하거나 다운로드합니다.
  4. 가이드 프로젝트를 가져옵니다.

    • Android 스튜디오에서 File > New > Import Project를 선택합니다.
    • Google Maps Android API v2 샘플 저장소를 다운로드한 후 저장한 위치로 이동합니다.
    • 다음 위치에서 CurrentPlaceDetailsOnMap 프로젝트를 찾습니다.
      PATH-TO-SAVED-REPO/android-samples/tutorials/java/CurrentPlaceDetailsOnMap(자바) 또는
      PATH-TO-SAVED-REPO/android-samples/tutorials/kotlin/CurrentPlaceDetailsOnMap(Kotlin)
    • 프로젝트 디렉터리를 선택한 다음 OK를 클릭합니다. 이제 Android 스튜디오에서 Gradle 빌드 도구를 사용하여 프로젝트를 빌드합니다.

API 키 가져오기 및 필요한 API 사용 설정

이 가이드의 내용을 완료하려면 Android용 Maps SDK 및 Android용 Places SDK를 사용할 수 있도록 승인된 Google API 키가 필요합니다.

다음 버튼을 클릭하여 키를 가져오고 API를 활성화하세요.

시작하기

자세한 내용은 API 키 가져오기에 관한 전체 가이드를 참고하세요.

앱에 API 키 추가

  1. 프로젝트의 local.properties 파일을 엽니다.
  2. 다음 문자열을 추가한 다음 YOUR_API_KEY를 API 키 값으로 대체합니다.

    MAPS_API_KEY=YOUR_API_KEY

    앱을 빌드할 때 Android용 Secrets Gradle Plugin에서 API 키를 복사하여 이를 Android 매니페스트 빌드 변수로 제공합니다.

    앱 빌드 및 실행

    1. 컴퓨터에 Android 기기를 연결합니다. 안내에 따라 Android 기기에서 개발자 옵션을 사용 설정하고 기기를 감지하도록 시스템을 구성합니다. (또는 Android Virtual DeviceAndroid(AVD) Manager를 사용하여 가상 기기를 구성할 수 있습니다. 에뮬레이터를 선택할 때 Google API가 포함된 이미지를 선택해야 합니다. 자세한 내용은 시작 가이드를 참고하세요.)
    2. Android 스튜디오에서 Run 메뉴 옵션(또는 재생 버튼 아이콘)을 클릭합니다. 표시되는 메시지에 따라 기기를 선택합니다.

    Android 스튜디오에서 Gradle을 호출하여 앱을 빌드한 다음 기기 또는 에뮬레이터에서 앱을 실행합니다. 이 페이지의 이미지와 비슷하게, 내 현재 위치를 중심으로 주변에 여러 개의 아이콘이 있는 지도가 표시됩니다.

    문제 해결:

    • 지도가 표시되지 않으면 위에서 설명한 대로 API 키를 가져와 앱에 추가했는지 확인하세요. Android 스튜디오의 Android Monitor에 있는 로그에 API 키 관련 오류 메시지가 있는지 확인하세요.
    • 지도에서 시드니 하버 브리지(앱에서 지정된 기본 위치)에 마커가 하나만 표시되면 앱에 위치 정보 액세스 권한을 부여했는지 확인하세요. Android 권한 가이드에 설명된 패턴에 따라, 런타임 시 위치 정보 액세스 권한을 요청하는 메시지가 앱에 표시됩니다. Settings > Apps > app name > Permissions > Location을 선택하여 기기에서 직접 권한을 설정할 수도 있습니다. 코드에서 권한을 처리하는 방법에 대해 자세히 알아보려면 아래에 나온 앱에서 위치 정보 액세스 권한 요청 관련 가이드를 읽어보세요.
    • Android 스튜디오 디버깅 도구를 사용하여 로그를 확인하고 앱을 디버깅하세요.

    코드 이해하기

    여기에서는 유사한 앱을 빌드하는 방법을 이해할 수 있도록 CurrentPlaceDetailsOnMap 앱의 가장 중요한 부분을 설명합니다.

    Places API 클라이언트 인스턴스화

    다음 객체는 Android용 Places SDK의 기본 진입점입니다.

    • Places 클래스는 Android용 Places SDK의 클라이언트를 만들고 관리합니다.
    • PlaceDetectionClient 인터페이스는 기기의 현재 위치와 위치 근처의 장소를 검색합니다.

    LocationServices 인터페이스는 Android 위치 서비스의 기본 진입점입니다.

    API를 사용하려면 프래그먼트 또는 활동의 onCreate() 메서드에서 다음을 실행하세요.

    1. Places 객체를 초기화합니다.
    2. PlaceDetectionClient 객체를 만듭니다.
    3. FusedLocationProviderClient 객체를 만듭니다.

    예:

    자바

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        // ...
    
        // Retrieve the content view that renders the map.
        setContentView(R.layout.activity_maps);
    
        // Construct a PlacesClient
        Places.initialize(getApplicationContext(), getString(R.string.maps_api_key));
        placesClient = Places.createClient(this);
    
        // Construct a FusedLocationProviderClient.
        fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
    }
    

    Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    
        // ...
    
        // Retrieve the content view that renders the map.
        setContentView(R.layout.activity_maps)
    
        // Construct a PlacesClient
        Places.initialize(applicationContext, getString(R.string.maps_api_key))
        placesClient = Places.createClient(this)
    
        // Construct a FusedLocationProviderClient.
        fusedLocationProviderClient = 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) {
              locationPermissionGranted = true;
          } else {
              ActivityCompat.requestPermissions(this,
                      new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
                      PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
          }
      }
      

      Kotlin

      private fun 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.applicationContext,
                  Manifest.permission.ACCESS_FINE_LOCATION)
              == PackageManager.PERMISSION_GRANTED) {
              locationPermissionGranted = true
          } else {
              ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                  PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION)
          }
      }
      
    3. 권한 요청의 결과를 처리하도록 onRequestPermissionsResult() 콜백을 재정의합니다.

      자바

      @Override
      public void onRequestPermissionsResult(int requestCode,
                                             @NonNull String[] permissions,
                                             @NonNull int[] grantResults) {
          locationPermissionGranted = 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) {
                      locationPermissionGranted = true;
                  }
              }
          }
          updateLocationUI();
      }
      

      Kotlin

      override fun onRequestPermissionsResult(requestCode: Int,
                                              permissions: Array<String>,
                                              grantResults: IntArray) {
          locationPermissionGranted = false
          when (requestCode) {
              PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION -> {
      
                  // If request is cancelled, the result arrays are empty.
                  if (grantResults.isNotEmpty() &&
                      grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                      locationPermissionGranted = true
                  }
              }
          }
          updateLocationUI()
      }
      

      이 가이드의 후반 섹션에서 updateLocationUI() 메서드에 대해 설명합니다.

    지도 추가

    Android용 Maps SDK를 사용하여 지도를 표시합니다.

    1. 활동의 레이아웃 파일인 activity_maps.xml<fragment> 요소를 추가합니다. 이 요소는 지도의 컨테이너 역할을 하고 GoogleMap 객체에 대한 액세스 권한을 제공하도록 SupportMapFragment를 정의합니다. 이 가이드에서는 이전 버전 Android 프레임워크와의 호환성을 보장하기 위해 지도 프래그먼트의 Android 지원 라이브러리 버전을 사용합니다.

      <!--
       Copyright 2020 Google LLC
      
       Licensed under the Apache License, Version 2.0 (the "License");
       you may not use this file except in compliance with the License.
       You may obtain a copy of the License at
      
            http://www.apache.org/licenses/LICENSE-2.0
      
       Unless required by applicable law or agreed to in writing, software
       distributed under the License is distributed on an "AS IS" BASIS,
       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       See the License for the specific language governing permissions and
       limitations under the License.
      -->
      
      <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);
      
          // Retrieve the content view that renders the map.
          setContentView(R.layout.activity_maps);
      
      }
      

      Kotlin

      override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
      
          // Retrieve the content view that renders the map.
          setContentView(R.layout.activity_maps)
      
      }
      
    3. OnMapReadyCallback 인터페이스를 구현하고, GoogleMap 객체를 사용할 수 있을 때 지도를 설정하도록 onMapReady() 메서드를 재정의합니다.

      자바

      @Override
      public void onMapReady(GoogleMap map) {
          this.map = map;
      
          // ...
      
          // 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();
      }
      

      Kotlin

      override fun onMapReady(map: GoogleMap) {
          this.map = map
      
          // ...
      
          // 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);
      

      Kotlin

      val mapFragment = supportFragmentManager
          .findFragmentById(R.id.map) as SupportMapFragment?
      mapFragment?.getMapAsync(this)
      
    5. updateLocationUI() 메서드를 작성하여 지도에 위치 컨트롤을 설정합니다. 사용자가 위치 정보 액세스 권한을 부여한 경우 지도의 내 위치 레이어 및 관련 컨트롤을 사용 설정하고 그렇지 않은 경우 레이어와 컨트롤을 사용 중지하고 현재 위치를 null로 설정합니다.

      자바

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

      Kotlin

      private fun updateLocationUI() {
          if (map == null) {
              return
          }
          try {
              if (locationPermissionGranted) {
                  map?.isMyLocationEnabled = true
                  map?.uiSettings?.isMyLocationButtonEnabled = true
              } else {
                  map?.isMyLocationEnabled = false
                  map?.uiSettings?.isMyLocationButtonEnabled = false
                  lastKnownLocation = null
                  getLocationPermission()
              }
          } catch (e: SecurityException) {
              Log.e("Exception: %s", e.message, e)
          }
      }
      

    Android 기기의 위치 가져오기 및 지도에 위치 지정

    통합 위치 정보 제공자를 사용하여 마지막으로 알려진 기기 위치를 찾은 다음 해당 위치를 지도에 지정합니다. 이 가이드에서 필요한 코드를 제공합니다. 기기 위치 가져오기에 대한 자세한 내용은 Google Play 서비스 위치 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 (locationPermissionGranted) {
                Task<Location> locationResult = fusedLocationProviderClient.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.
                            lastKnownLocation = task.getResult();
                            if (lastKnownLocation != null) {
                                map.moveCamera(CameraUpdateFactory.newLatLngZoom(
                                        new LatLng(lastKnownLocation.getLatitude(),
                                                lastKnownLocation.getLongitude()), DEFAULT_ZOOM));
                            }
                        } else {
                            Log.d(TAG, "Current location is null. Using defaults.");
                            Log.e(TAG, "Exception: %s", task.getException());
                            map.moveCamera(CameraUpdateFactory
                                    .newLatLngZoom(defaultLocation, DEFAULT_ZOOM));
                            map.getUiSettings().setMyLocationButtonEnabled(false);
                        }
                    }
                });
            }
        } catch (SecurityException e)  {
            Log.e("Exception: %s", e.getMessage(), e);
        }
    }
    

    Kotlin

    private fun 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 (locationPermissionGranted) {
                val locationResult = fusedLocationProviderClient.lastLocation
                locationResult.addOnCompleteListener(this) { task ->
                    if (task.isSuccessful) {
                        // Set the map's camera position to the current location of the device.
                        lastKnownLocation = task.result
                        if (lastKnownLocation != null) {
                            map?.moveCamera(CameraUpdateFactory.newLatLngZoom(
                                LatLng(lastKnownLocation!!.latitude,
                                    lastKnownLocation!!.longitude), DEFAULT_ZOOM.toFloat()))
                        }
                    } else {
                        Log.d(TAG, "Current location is null. Using defaults.")
                        Log.e(TAG, "Exception: %s", task.exception)
                        map?.moveCamera(CameraUpdateFactory
                            .newLatLngZoom(defaultLocation, DEFAULT_ZOOM.toFloat()))
                        map?.uiSettings?.isMyLocationButtonEnabled = false
                    }
                }
            }
        } catch (e: SecurityException) {
            Log.e("Exception: %s", e.message, e)
        }
    }
    

    현재 장소 가져오기

    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;
      }
      

      Kotlin

      override fun onOptionsItemSelected(item: MenuItem): Boolean {
          if (item.itemId == R.id.option_get_place) {
              showCurrentPlace()
          }
          return true
      }
      
    3. showCurrentPlace() 메서드를 작성하여 현재 기기 위치의 예상 장소 목록을 가져옵니다.

      자바

      private void showCurrentPlace() {
          if (map == null) {
              return;
          }
      
          if (locationPermissionGranted) {
              // 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 =
                      placesClient.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;
                          likelyPlaceNames = new String[count];
                          likelyPlaceAddresses = new String[count];
                          likelyPlaceAttributions = new List[count];
                          likelyPlaceLatLngs = new LatLng[count];
      
                          for (PlaceLikelihood placeLikelihood : likelyPlaces.getPlaceLikelihoods()) {
                              // Build a list of likely places to show the user.
                              likelyPlaceNames[i] = placeLikelihood.getPlace().getName();
                              likelyPlaceAddresses[i] = placeLikelihood.getPlace().getAddress();
                              likelyPlaceAttributions[i] = placeLikelihood.getPlace()
                                      .getAttributions();
                              likelyPlaceLatLngs[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.
              map.addMarker(new MarkerOptions()
                      .title(getString(R.string.default_info_title))
                      .position(defaultLocation)
                      .snippet(getString(R.string.default_info_snippet)));
      
              // Prompt the user for permission.
              getLocationPermission();
          }
      }
      

      Kotlin

      @SuppressLint("MissingPermission")
      private fun showCurrentPlace() {
          if (map == null) {
              return
          }
          if (locationPermissionGranted) {
              // Use fields to define the data types to return.
              val placeFields = listOf(Place.Field.NAME, Place.Field.ADDRESS, Place.Field.LAT_LNG)
      
              // Use the builder to create a FindCurrentPlaceRequest.
              val 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.
              val placeResult = placesClient.findCurrentPlace(request)
              placeResult.addOnCompleteListener { task ->
                  if (task.isSuccessful && task.result != null) {
                      val likelyPlaces = task.result
      
                      // Set the count, handling cases where less than 5 entries are returned.
                      val count = if (likelyPlaces != null && likelyPlaces.placeLikelihoods.size < M_MAX_ENTRIES) {
                          likelyPlaces.placeLikelihoods.size
                      } else {
                          M_MAX_ENTRIES
                      }
                      var i = 0
                      likelyPlaceNames = arrayOfNulls(count)
                      likelyPlaceAddresses = arrayOfNulls(count)
                      likelyPlaceAttributions = arrayOfNulls<List<*>?>(count)
                      likelyPlaceLatLngs = arrayOfNulls(count)
                      for (placeLikelihood in likelyPlaces?.placeLikelihoods ?: emptyList()) {
                          // Build a list of likely places to show the user.
                          likelyPlaceNames[i] = placeLikelihood.place.name
                          likelyPlaceAddresses[i] = placeLikelihood.place.address
                          likelyPlaceAttributions[i] = placeLikelihood.place.attributions
                          likelyPlaceLatLngs[i] = placeLikelihood.place.latLng
                          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.
                      openPlacesDialog()
                  } else {
                      Log.e(TAG, "Exception: %s", task.exception)
                  }
              }
          } 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.
              map?.addMarker(MarkerOptions()
                  .title(getString(R.string.default_info_title))
                  .position(defaultLocation)
                  .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 = likelyPlaceLatLngs[which];
                  String markerSnippet = likelyPlaceAddresses[which];
                  if (likelyPlaceAttributions[which] != null) {
                      markerSnippet = markerSnippet + "\n" + likelyPlaceAttributions[which];
                  }
      
                  // Add a marker for the selected place, with an info window
                  // showing information about that place.
                  map.addMarker(new MarkerOptions()
                          .title(likelyPlaceNames[which])
                          .position(markerLatLng)
                          .snippet(markerSnippet));
      
                  // Position the map's camera at the location of the marker.
                  map.moveCamera(CameraUpdateFactory.newLatLngZoom(markerLatLng,
                          DEFAULT_ZOOM));
              }
          };
      
          // Display the dialog.
          AlertDialog dialog = new AlertDialog.Builder(this)
                  .setTitle(R.string.pick_place)
                  .setItems(likelyPlaceNames, listener)
                  .show();
      }
      

      Kotlin

      private fun openPlacesDialog() {
          // Ask the user to choose the place where they are now.
          val listener = DialogInterface.OnClickListener { dialog, which -> // The "which" argument contains the position of the selected item.
              val markerLatLng = likelyPlaceLatLngs[which]
              var markerSnippet = likelyPlaceAddresses[which]
              if (likelyPlaceAttributions[which] != null) {
                  markerSnippet = """
                      $markerSnippet
                      ${likelyPlaceAttributions[which]}
                      """.trimIndent()
              }
      
              // Add a marker for the selected place, with an info window
              // showing information about that place.
              map?.addMarker(MarkerOptions()
                  .title(likelyPlaceNames[which])
                  .position(markerLatLng!!)
                  .snippet(markerSnippet))
      
              // Position the map's camera at the location of the marker.
              map?.moveCamera(CameraUpdateFactory.newLatLngZoom(markerLatLng,
                  DEFAULT_ZOOM.toFloat()))
          }
      
          // Display the dialog.
          AlertDialog.Builder(this)
              .setTitle(R.string.pick_place)
              .setItems(likelyPlaceNames, listener)
              .show()
      }
      
    5. 정보 창 콘텐츠의 맞춤 레이아웃을 생성합니다. 이렇게 하면 정보 창에 여러 줄의 콘텐츠를 표시할 수 있습니다. 먼저, 정보 창 제목의 텍스트 뷰, 스니펫의 다른 텍스트 뷰(정보 창의 텍스트 콘텐츠)가 포함된 XML 레이아웃 파일인 custom_info_contents.xml을 추가합니다.

      <?xml version="1.0" encoding="utf-8"?>
      <!--
       Copyright 2020 Google LLC
      
       Licensed under the Apache License, Version 2.0 (the "License");
       you may not use this file except in compliance with the License.
       You may obtain a copy of the License at
      
            http://www.apache.org/licenses/LICENSE-2.0
      
       Unless required by applicable law or agreed to in writing, software
       distributed under the License is distributed on an "AS IS" BASIS,
       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       See the License for the specific language governing permissions and
       limitations under the License.
      -->
      
      <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 인터페이스를 구현하여 레이아웃을 확장하고 정보 창 콘텐츠를 로드합니다.

      자바

      // Use a custom info window adapter to handle multiple lines of text in the
      // info window contents.
      this.map.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,
                      (FrameLayout) findViewById(R.id.map), false);
      
              TextView title = infoWindow.findViewById(R.id.title);
              title.setText(marker.getTitle());
      
              TextView snippet = infoWindow.findViewById(R.id.snippet);
              snippet.setText(marker.getSnippet());
      
              return infoWindow;
          }
      });
      

      Kotlin

      // Use a custom info window adapter to handle multiple lines of text in the
      // info window contents.
      this.map?.setInfoWindowAdapter(object : InfoWindowAdapter {
          // Return null here, so that getInfoContents() is called next.
          override fun getInfoWindow(arg0: Marker): View? {
              return null
          }
      
          override fun getInfoContents(marker: Marker): View {
              // Inflate the layouts for the info window, title and snippet.
              val infoWindow = layoutInflater.inflate(R.layout.custom_info_contents,
                  findViewById<FrameLayout>(R.id.map), false)
              val title = infoWindow.findViewById<TextView>(R.id.title)
              title.text = marker.title
              val snippet = infoWindow.findViewById<TextView>(R.id.snippet)
              snippet.text = marker.snippet
              return infoWindow
          }
      })
      

    지도의 상태 저장

    지도의 카메라 위치와 기기 위치를 저장합니다. 사용자가 Android 기기를 회전시키거나 구성을 변경하면 Android 프레임워크에서 지도 활동을 삭제하고 재구성합니다. 원활한 사용자 환경을 유지하려면 관련 애플리케이션 상태를 저장했다가 필요할 때 복원하는 것이 좋습니다.

    이 가이드에서는 지도의 상태를 저장하는 데 필요한 모든 코드를 제공합니다. 자세한 내용은 savedInstanceState 번들 가이드를 참고하세요.

    1. 지도 활동에서 활동 상태를 저장하기 위한 키 값을 설정합니다.

      자바

      private static final String KEY_CAMERA_POSITION = "camera_position";
      private static final String KEY_LOCATION = "location";
      

      Kotlin

      private const val KEY_CAMERA_POSITION = "camera_position"
      private const val KEY_LOCATION = "location"
      
    2. 활동이 일시중지될 때 상태를 저장하도록 onSaveInstanceState() 콜백을 구현합니다.

      자바

      @Override
      protected void onSaveInstanceState(Bundle outState) {
          if (map != null) {
              outState.putParcelable(KEY_CAMERA_POSITION, map.getCameraPosition());
              outState.putParcelable(KEY_LOCATION, lastKnownLocation);
          }
          super.onSaveInstanceState(outState);
      }
      

      Kotlin

      override fun onSaveInstanceState(outState: Bundle) {
          map?.let { map ->
              outState.putParcelable(KEY_CAMERA_POSITION, map.cameraPosition)
              outState.putParcelable(KEY_LOCATION, lastKnownLocation)
          }
          super.onSaveInstanceState(outState)
      }
      
    3. 활동의 onCreate() 메서드에서 기기의 위치와 지도의 카메라 위치(이전에 저장한 경우)를 가져옵니다.

      자바

      // Retrieve location and camera position from saved instance state.
      if (savedInstanceState != null) {
          lastKnownLocation = savedInstanceState.getParcelable(KEY_LOCATION);
          cameraPosition = savedInstanceState.getParcelable(KEY_CAMERA_POSITION);
      }
      

      Kotlin

      if (savedInstanceState != null) {
          lastKnownLocation = savedInstanceState.getParcelable(KEY_LOCATION)
          cameraPosition = savedInstanceState.getParcelable(KEY_CAMERA_POSITION)
      }