Auf einer Karte den aktuellen Ort auswählen und Details einblenden

Hier wird beschrieben, wie Sie den aktuellen Standort eines Android-Geräts ermitteln und zu diesem Ort (einem Unternehmen oder POI) Details aufrufen können. In diesem Tutorial erfahren Sie, wie Sie mit dem Maps SDK for Android, dem Places SDK for Android und dem Anbieter für kombinierte Standortbestimmung aus den Standort-APIs der Google Play-Dienste eine Android-App erstellen.

Code abrufen

Klonen Sie das Repository „Google Maps Android API v2 Samples“ oder laden Sie es von GitHub herunter.

So sieht die Java-Version der Aktivität aus:

    // 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.util.TypedValue;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;
import android.widget.Toast;
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 androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
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.CircularBounds;
import com.google.android.libraries.places.api.model.Place;
import com.google.android.libraries.places.api.net.PlacesClient;
import com.google.android.libraries.places.api.net.SearchNearbyRequest;
import com.google.android.libraries.places.api.net.SearchNearbyResponse;
import java.util.ArrayList;
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 or set via long press.
    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 static final double SEARCH_RADIUS_METERS = 100.0;

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

        ViewCompat.setOnApplyWindowInsetsListener(
                getWindow().getDecorView(),
                (v, insets) -> {
                    Insets systemBars =
                            insets.getInsets(
                                    WindowInsetsCompat.Type.systemBars()
                                            | WindowInsetsCompat.Type.displayCutout());
                    View actionBarContainer =
                            getWindow()
                                    .getDecorView()
                                    .findViewById(androidx.appcompat.R.id.action_bar_container);
                    if (actionBarContainer != null) {
                        actionBarContainer.setPadding(
                                actionBarContainer.getPaddingLeft(),
                                systemBars.top,
                                actionBarContainer.getPaddingRight(),
                                actionBarContainer.getPaddingBottom());
                    }
                    TypedValue typedValue = new TypedValue();
                    int actionBarHeight =
                            getTheme()
                                            .resolveAttribute(
                                                    android.R.attr.actionBarSize, typedValue, true)
                                    ? TypedValue.complexToDimensionPixelSize(
                                            typedValue.data, getResources().getDisplayMetrics())
                                    : 0;
                    if (map != null) {
                        map.setPadding(0, systemBars.top + actionBarHeight, 0, systemBars.bottom);
                    }
                    return insets;
                });
        // Construct a PlacesClient
        Places.initializeWithNewPlacesApiEnabled(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;
                    }
                });

        // Allow moving the current location with a long press on the map to easily
        // demo and discover nearby places anywhere in the world.
        this.map.setOnMapLongClickListener(this::setCustomLocation);

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

        WindowInsetsCompat rootInsets = ViewCompat.getRootWindowInsets(getWindow().getDecorView());
        if (rootInsets != null) {
            Insets systemBars =
                    rootInsets.getInsets(
                            WindowInsetsCompat.Type.systemBars()
                                    | WindowInsetsCompat.Type.displayCutout());
            TypedValue typedValue = new TypedValue();
            int actionBarHeight =
                    getTheme().resolveAttribute(android.R.attr.actionBarSize, typedValue, true)
                            ? TypedValue.complexToDimensionPixelSize(
                                    typedValue.data, getResources().getDisplayMetrics())
                            : 0;
            this.map.setPadding(0, systemBars.top + actionBarHeight, 0, systemBars.bottom);
        }

        // 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;
        if (requestCode
                == 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;
            }
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
        updateLocationUI();
    }


    /**
     * UI representation of a place item to display in the selection dialog and map marker.
     *
     * <p>It is good practice to decouple the external data/backend model ({@link Place}) from the
     * UI layer. The UI should be as free of logic as possible: it receives presentation-ready data
     * (non-null coordinates, ready-to-display title and snippet strings) so that dialogs and map
     * overlays remain simple, predictable, and testable.
     */
    static class PlaceItemUiModel {
        final String title;
        final String snippet;
        final LatLng position;

        PlaceItemUiModel(String title, String snippet, LatLng position) {
            this.title = title;
            this.snippet = snippet;
            this.position = position;
        }
    }

    /**
     * Maps an external {@link Place} domain model into a display-ready {@link PlaceItemUiModel}.
     *
     * <p>The mapper encapsulates all data normalization, default value fallbacks (such as fallback
     * titles), and string formatting (such as appending third-party attributions to the address).
     * Keeping this transformation logic in the mapper prevents business and formatting logic from
     * leaking into the UI components. Returns {@code null} if essential coordinates are missing.
     */
    private PlaceItemUiModel toUiModel(Place place) {
        return toUiModel(place, getString(R.string.default_info_title));
    }

    static PlaceItemUiModel toUiModel(Place place, String defaultTitle) {
        if (place.getLocation() == null) {
            return null;
        }
        String title = place.getDisplayName() != null ? place.getDisplayName() : defaultTitle;

        StringBuilder snippet = new StringBuilder();
        if (place.getFormattedAddress() != null) {
            snippet.append(place.getFormattedAddress());
        }
        if (place.getAttributions() != null && !place.getAttributions().isEmpty()) {
            if (snippet.length() > 0) {
                snippet.append("\n");
            }
            snippet.append(String.join("\n", place.getAttributions()));
        }
        return new PlaceItemUiModel(title, snippet.toString(), place.getLocation());
    }


    /**
     * 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 (lastKnownLocation != null && "manual".equals(lastKnownLocation.getProvider())) {
            locationPermissionGranted = true;
        }

        if (locationPermissionGranted) {
            // Use fields to define the data types to return.
            List<Place.Field> placeFields =
                    Arrays.asList(
                            Place.Field.DISPLAY_NAME,
                            Place.Field.FORMATTED_ADDRESS,
                            Place.Field.LOCATION);

            // Search around the device's last-known location, falling back to the center of the
            // map if the location is not available yet.
            LatLng center =
                    lastKnownLocation != null
                            ? new LatLng(
                                    lastKnownLocation.getLatitude(),
                                    lastKnownLocation.getLongitude())
                            : map.getCameraPosition().target;
            CircularBounds searchArea = CircularBounds.newInstance(center, SEARCH_RADIUS_METERS);

            // Use the builder to create a SearchNearbyRequest that returns the closest places
            // first.
            SearchNearbyRequest request =
                    SearchNearbyRequest.builder(searchArea, placeFields)
                            .setMaxResultCount(M_MAX_ENTRIES)
                            .setRankPreference(SearchNearbyRequest.RankPreference.DISTANCE)
                            .build();

            // Get the likely places - that is, the businesses and other points of interest that
            // are closest to the device's current location.
            final Task<SearchNearbyResponse> placeResult = placesClient.searchNearby(request);
            placeResult.addOnCompleteListener(
                    new OnCompleteListener<SearchNearbyResponse>() {
                        @Override
                        public void onComplete(@NonNull Task<SearchNearbyResponse> task) {
                            if (task.isSuccessful() && task.getResult() != null) {
                                List<Place> responsePlaces = task.getResult().getPlaces();
                                int count = Math.min(responsePlaces.size(), M_MAX_ENTRIES);
                                List<PlaceItemUiModel> likelyPlaces = new ArrayList<>();
                                for (int i = 0; i < count; i++) {
                                    PlaceItemUiModel model = toUiModel(responsePlaces.get(i));
                                    if (model != null) {
                                        likelyPlaces.add(model);
                                    }
                                }

                                // Show a dialog offering the user the list of likely places, and
                                // add a marker at the selected place.
                                MapsActivityCurrentPlace.this.openPlacesDialog(likelyPlaces);
                            } 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. */
    void openPlacesDialog(List<PlaceItemUiModel> likelyPlaces) {
        if (likelyPlaces.isEmpty()) {
            Toast.makeText(
                            this,
                            "No places found nearby. Long-press on the map to choose another location.",
                            Toast.LENGTH_LONG)
                    .show();
            return;
        }
        // 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.
                        PlaceItemUiModel selectedPlace = likelyPlaces.get(which);

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

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

        // Display the dialog.
        String[] titles = new String[likelyPlaces.size()];
        for (int i = 0; i < likelyPlaces.size(); i++) {
            titles[i] = likelyPlaces.get(i).title;
        }
        new AlertDialog.Builder(this)
                .setTitle(R.string.pick_place)
                .setItems(titles, 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;
            }
        } catch (SecurityException e) {
            Log.e("Exception: %s", e.getMessage());
        }
    }


    /** Updates the current location and searches for places around the specified coordinate. */
    void setCustomLocation(LatLng latLng) {
        Location location = new Location("manual");
        location.setLatitude(latLng.latitude);
        location.setLongitude(latLng.longitude);
        lastKnownLocation = location;
        if (map != null) {
            map.animateCamera(CameraUpdateFactory.newLatLng(latLng));
        }
        showCurrentPlace();
    }
}

    

Die Kotlin-Version der Aktivität sieht so aus:

    // 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.util.TypedValue
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.FrameLayout
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
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.CircularBounds
import com.google.android.libraries.places.api.model.Place
import com.google.android.libraries.places.api.net.PlacesClient
import com.google.android.libraries.places.api.net.SearchNearbyRequest

/**
 * 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
    internal var selectedMarker: Marker? = 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 or set via long press.
    internal var lastKnownLocation: Location? = null

    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)

        ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { _, insets ->
            val systemBars = insets.getInsets(
                WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout(),
            )
            val actionBarContainer = window.decorView.findViewById<View>(androidx.appcompat.R.id.action_bar_container)
            actionBarContainer?.setPadding(
                actionBarContainer.paddingLeft,
                systemBars.top,
                actionBarContainer.paddingRight,
                actionBarContainer.paddingBottom,
            )
            val typedValue = TypedValue()
            val actionBarHeight = if (theme.resolveAttribute(android.R.attr.actionBarSize, typedValue, true)) {
                TypedValue.complexToDimensionPixelSize(typedValue.data, resources.displayMetrics)
            } else {
                0
            }
            map?.setPadding(0, systemBars.top + actionBarHeight, 0, systemBars.bottom)
            insets
        }
        // Construct a PlacesClient
        Places.initializeWithNewPlacesApiEnabled(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? = 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
            }
        })

        // Allow moving the current location with a long press on the map to easily
        // demo and discover nearby places anywhere in the world.
        this.map?.setOnMapLongClickListener { latLng ->
            setCustomLocation(latLng)
        }

        // Prompt the user for permission.
        getLocationPermission()

        val insets = ViewCompat.getRootWindowInsets(window.decorView)
            ?.getInsets(WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout())
        val topInset = insets?.top ?: 0
        val bottomInset = insets?.bottom ?: 0
        val typedValue = TypedValue()
        val actionBarHeight = if (theme.resolveAttribute(android.R.attr.actionBarSize, typedValue, true)) {
            TypedValue.complexToDimensionPixelSize(typedValue.data, resources.displayMetrics)
        } else {
            0
        }
        this.map?.setPadding(0, topInset + actionBarHeight, 0, bottomInset)

        // 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.
     */
    @SuppressLint("MissingPermission")
    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
                }
            }

            else -> super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        }
        updateLocationUI()
    }

    private fun Place.toUiModel(): PlaceItemUiModel? = toPlaceItemUiModel(this, getString(R.string.default_info_title))

    /**
     * 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 fun showCurrentPlace() {
        val map = map ?: return
        if (lastKnownLocation?.provider == "manual") {
            locationPermissionGranted = true
        }
        if (locationPermissionGranted) {
            // Use fields to define the data types to return.
            val placeFields = listOf(
                Place.Field.DISPLAY_NAME,
                Place.Field.FORMATTED_ADDRESS,
                Place.Field.LOCATION,
            )

            // Search around the device's last-known location, falling back to the center of
            // the map if the location is not available yet.
            val center = lastKnownLocation?.let { LatLng(it.latitude, it.longitude) }
                ?: map.cameraPosition.target
            val searchArea = CircularBounds.newInstance(center, SEARCH_RADIUS_METERS)

            // Use the builder to create a SearchNearbyRequest that returns the closest places
            // first.
            val request = SearchNearbyRequest.builder(searchArea, placeFields)
                .setMaxResultCount(M_MAX_ENTRIES)
                .setRankPreference(SearchNearbyRequest.RankPreference.DISTANCE)
                .build()

            // Get the likely places - that is, the businesses and other points of interest that
            // are closest to the device's current location.
            val placeResult = placesClient.searchNearby(request)
            placeResult.addOnCompleteListener { task ->
                if (task.isSuccessful && task.result != null) {
                    val likelyPlaces = task.result.places
                        .take(M_MAX_ENTRIES)
                        .mapNotNull { it.toUiModel() }

                    // Show a dialog offering the user the list of likely places, and add a
                    // marker at the selected place.
                    openPlacesDialog(likelyPlaces)
                } 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.
     */
    internal fun openPlacesDialog(likelyPlaces: List<PlaceItemUiModel>) {
        if (likelyPlaces.isEmpty()) {
            Toast.makeText(
                this,
                "No places found nearby. Long-press on the map to choose another location.",
                Toast.LENGTH_LONG,
            ).show()
            return
        }
        // Ask the user to choose the place where they are now.
        val listener = DialogInterface.OnClickListener { _, which ->
            // The "which" argument contains the position of the selected item.
            val selectedPlace = likelyPlaces[which]

            // Add a marker for the selected place, with an info window
            // showing information about that place.
            selectedMarker = map?.addMarker(
                MarkerOptions()
                    .title(selectedPlace.title)
                    .position(selectedPlace.position)
                    .snippet(selectedPlace.snippet),
            )

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

        // Display the dialog.
        val titles = likelyPlaces.map { it.title }.toTypedArray()
        AlertDialog.Builder(this)
            .setTitle(R.string.pick_place)
            .setItems(titles, listener)
            .show()
    }

    /**
     * Updates the map's UI settings based on whether the user has granted location permission.
     */
    @SuppressLint("MissingPermission")
    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)
        }
    }

    /**
     * Updates the current location and searches for places around the specified coordinate.
     */
    internal fun setCustomLocation(latLng: LatLng) {
        val location = Location("manual").apply {
            latitude = latLng.latitude
            longitude = latLng.longitude
        }
        lastKnownLocation = location
        map?.animateCamera(CameraUpdateFactory.newLatLng(latLng))
        showCurrentPlace()
    }

    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
        private const val SEARCH_RADIUS_METERS = 100.0
    }
}


/**
 * UI representation of a place item to display in the selection dialog and map marker.
 *
 * It is good practice to decouple the external data/backend model ([Place]) from the UI layer.
 * The UI should be as free of logic as possible: it receives presentation-ready data (non-null
 * coordinates, ready-to-display title and snippet strings) so that dialogs and map overlays
 * remain simple, predictable, and testable.
 */
internal data class PlaceItemUiModel(val title: String, val snippet: String, val position: LatLng)

/**
 * Maps an external [Place] domain model into a display-ready [PlaceItemUiModel].
 *
 * The mapper encapsulates all data normalization, default value fallbacks (such as fallback
 * titles), and string formatting (such as appending third-party attributions to the address).
 * Keeping this transformation logic in the mapper prevents business and formatting logic
 * from leaking into the UI components. Returns `null` if essential coordinates are missing.
 */
internal fun toPlaceItemUiModel(place: Place, defaultTitle: String): PlaceItemUiModel? {
    val position = place.location ?: return null
    val title = place.displayName ?: defaultTitle
    val snippet = buildString {
        place.formattedAddress?.let { append(it) }
        place.attributions?.takeIf { it.isNotEmpty() }?.let {
            if (isNotEmpty()) append("\n")
            append(it.joinToString("\n"))
        }
    }
    return PlaceItemUiModel(title = title, snippet = snippet, position = position)
}

    

Entwicklungsprojekt einrichten

Führen Sie die folgenden Schritte aus, um das Tutorial-Projekt in Android Studio zu erstellen.

  1. Laden Sie Android Studio herunter und installieren Sie es.
  2. Fügen Sie das Paket Google Play-Dienste in Android Studio ein.
  3. Klonen Sie das Repository „Google Maps Android API v2 Samples“ oder laden Sie es herunter, sofern Sie das nicht schon zu Beginn dieses Tutorials getan haben.
  4. Importieren Sie das Tutorial-Projekt:

    • Wählen Sie in Android Studio File > New > Import Project aus.
    • Gehen Sie zu dem Speicherort, an dem Sie das Repository „Google Maps Android API v2 Samples“ nach dem Download gespeichert haben.
    • Suchen Sie nach dem Projekt CurrentPlaceDetailsOnMap unter:
      PATH-TO-SAVED-REPO/android-samples/tutorials/java/CurrentPlaceDetailsOnMap (Java) oder
      PATH-TO-SAVED-REPO/android-samples/tutorials/kotlin/CurrentPlaceDetailsOnMap (Kotlin).
    • Wählen Sie das Projektverzeichnis aus und klicken Sie auf Open. Ihr Projekt wird jetzt mit dem Build-Tool Gradle in Android Studio erstellt.

Erforderliche APIs aktivieren und API-Schlüssel anfordern

Als letzten Schritt benötigen Sie noch ein Google Cloud-Projekt mit den erforderlichen, aktivierten APIs und einen API-Schlüssel, der zum Verwenden des Maps SDK for Android berechtigt. Weitere Informationen finden Sie hier:

Wenn Sie die aktivierten APIs sehen möchten, rufen Sie in der Cloud Console die Seite „Google Maps Platform“ auf und wählen Sie Ihr Projekt aus:

Zur Seite „Google Maps Platform“

Wenn die Places API in Ihrem Projekt nicht aktiviert ist, holen Sie diesen Schritt nach:

Places API aktivieren

Falls Sie Einschränkungen für Ihren API-Schlüssel festlegen, müssen Sie ihm die Places API hinzufügen. Weitere Informationen finden Sie unter API-Schlüssel verwenden.

API-Schlüssel in die App einfügen

  1. Öffnen Sie die local.properties-Datei Ihres Projekts.
  2. Fügen Sie den folgenden String ein und ersetzen Sie dabei YOUR_API_KEY durch den Wert Ihres API-Schlüssels:

    MAPS_API_KEY=YOUR_API_KEY

    Wenn Sie Ihre App erstellen, wird der API-Schlüssel über das Secrets Gradle-Plug-in für Android kopiert und dann als Build-Variable im Android-Manifest zur Verfügung gestellt. Dies wird weiter unten genauer beschrieben.

App erstellen und ausführen

  1. Verbinden Sie ein Android-Gerät mit Ihrem Computer. Folgen Sie dieser Anleitung, um Entwickleroptionen auf Ihrem Android-Gerät zu aktivieren und Ihr System so zu konfigurieren, dass das Gerät erkannt wird.

    Alternativ können Sie mit Android Virtual Device (AVD) Manager ein virtuelles Gerät konfigurieren. Beim Auswählen eines Emulators sollten Sie ein Image angeben, das die Google-APIs enthält. Weitere Informationen finden Sie unter Android Studio-Projekt einrichten.

  2. Klicken Sie in Android Studio auf die Menüoption Run (oder das Wiedergabesymbol). Wählen Sie ein Gerät aus, wenn Sie dazu aufgefordert werden.

In Android Studio wird Gradle aufgerufen, um die App zu erstellen. Dann wird die App auf dem Gerät oder im Emulator ausgeführt. Nun sollte eine Karte mit einer Reihe von Markierungen zu sehen sein, die um Ihren aktuellen Standort herum angeordnet sind – ähnlich wie auf dem Bild auf dieser Seite.

  1. Wählen Sie Get Place aus, um eine Liste mit Orten (Unternehmen oder anderen POIs) in der Nähe Ihres aktuellen Standorts zu öffnen. Damit Sie die Liste der Orte sehen können, müssen Sie die Berechtigung zur Standortermittlung erteilen, wenn Sie dazu aufgefordert werden.
  2. Wählen Sie in der Liste einen Ort aus. In der Karte wird für den ausgewählten Ort eine Markierung eingefügt.

Fehlerbehebung:

  • Wenn Sie keine Karte sehen, prüfen Sie, ob Sie einen API-Schlüssel erhalten und ihn wie oben beschrieben in die App eingefügt haben. Kontrollieren Sie, ob es im Log im Android Monitor von Android Studio Fehlermeldungen zum API-Schlüssel gibt.
  • Wenn auf der Karte nur eine einzelne Markierung auf der Sydney Harbour Bridge (dem in der App angegebenen standardmäßigen Standort) angezeigt wird, prüfen Sie, ob Sie der App eine Berechtigung zur Standortermittlung zugewiesen haben. Die App fordert diese Berechtigung zur Laufzeit an. Dabei wird das im Leitfaden zu Android-Berechtigungen beschriebene Muster befolgt. Sie können Berechtigungen auch direkt auf dem Gerät festlegen, indem Sie Einstellungen > Apps > App-Name > Berechtigungen > Standort auswählen. Ausführliche Informationen zum Umgang mit Berechtigungen in Ihrem Code finden Sie unten im Abschnitt Berechtigung zur Standortermittlung anfordern.
  • Wenn Sie Logs aufrufen und Fehler in der App beheben möchten, können Sie dazu die Debugging-Tools in Android Studio verwenden.

Code verstehen

In diesem Teil des Tutorials werden die wichtigsten Teile der App CurrentPlaceDetailsOnMap erläutert, damit Sie besser verstehen, wie Sie eine ähnliche App erstellen können.

Places API-Client instanziieren

Die folgenden Objekte sind die primären Einstiegspunkte für das Places SDK for Android:

  • Mit der Klasse Places werden Clients für das Places SDK for Android erstellt und verwaltet.
  • Über die Schnittstelle PlacesClient werden der aktuelle Standort des Geräts sowie die Orte in der Nähe des Standorts abgerufen.

Die Schnittstelle LocationServices ist der Haupteinstiegspunkt für Android-Standortdienste.

Wenn Sie die APIs nutzen möchten, führen Sie in der onCreate()-Methode des Fragments oder der Aktivität folgende Schritte aus:

  1. Initialisieren Sie ein Places-Objekt.
  2. Erstellen Sie ein PlacesClient-Objekt.
  3. Erstellen Sie ein FusedLocationProviderClient-Objekt.

Beispiel:

Java

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

}

Berechtigung zur Standortermittlung anfordern

Um den Gerätestandort bestimmen und die Funktion zum Antippen des Buttons Mein Standort auf der Karte für Nutzer freigeben zu können, muss über Ihre App eine Berechtigung zur Standortermittlung angefordert werden.

Dieses Tutorial enthält den Code, den Sie zum Anfordern einer detaillierten Berechtigung zur Standortermittlung benötigen. Weitere Informationen finden Sie im Leitfaden zu Android-Berechtigungen.

  1. Fügen Sie die Berechtigung als untergeordnetes Element des Elements <manifest> in Ihrem Android-Manifest hinzu:

    <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. Fordern Sie in Ihrer App Laufzeitberechtigungen an und geben Sie dem Nutzer die Möglichkeit, die Berechtigung zur Standortermittlung zu erteilen oder abzulehnen. Mit dem folgenden Code wird überprüft, ob der Nutzer eine gültige Berechtigung zur Standortermittlung erteilt hat. Falls nicht, wird die Berechtigung angefordert:

    Java

    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. Überschreiben Sie den Callback onRequestPermissionsResult(), um das Ergebnis der Berechtigungsanfrage zu verarbeiten:

    Java

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

    In einem späteren Abschnitt dieses Tutorials wird die updateLocationUI()-Methode beschrieben.

Karte hinzufügen

Rufen Sie mit dem Maps SDK for Android eine Karte auf.

  1. Fügen Sie der Layoutdatei activity_maps.xml Ihrer Aktivität ein <fragment>-Element hinzu. Mit diesem Element wird ein SupportMapFragment definiert, das als Container für die Karte fungiert und Zugriff auf das GoogleMap-Objekt bietet. In diesem Tutorial wird die Version der Android Support Library des Kartenfragments verwendet, um die Abwärtskompatibilität mit früheren Versionen des Android-Frameworks sicherzustellen.

    <!--
     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. Legen Sie in der onCreate()-Methode die Layoutdatei als Inhaltsansicht fest:

    Java

    @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. Implementieren Sie die Schnittstelle OnMapReadyCallback und überschreiben Sie die onMapReady()-Methode, um die Karte einzurichten, wenn das GoogleMap-Objekt verfügbar ist:

    Java

    @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. Rufen Sie in der onCreate()-Methode Ihrer Aktivität einen Handle zum Kartenfragment ab, indem Sie FragmentManager.findFragmentById() aufrufen. Registrieren Sie sich dann mit getMapAsync() für den Karten-Callback:

    Java

    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. Schreiben Sie eine updateLocationUI()-Methode, um die Standortsteuerelemente auf der Karte festzulegen. Wenn der Nutzer die Berechtigung zur Standortermittlung erteilt hat, aktivieren Sie die Ebene „Mein Standort“ und das entsprechende Steuerelement auf der Karte. Deaktivieren Sie andernfalls die Ebene und das Steuerelement und setzen Sie den aktuellen Standort auf „null“:

    Java

    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;
            }
        } catch (SecurityException e) {
            Log.e("Exception: %s", e.getMessage());
        }
    }
    

    Kotlin

    @SuppressLint("MissingPermission")
    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)
        }
    }

Standort des Android-Geräts abrufen und Karte positionieren

Verwenden Sie den Anbieter für kombinierte Standortbestimmung, um den zuletzt bekannten Standort des Geräts zu finden, und nutzen Sie den Standort dann, um die Karte zu positionieren. Dieses Tutorial enthält den Code, den Sie benötigen. Weitere Informationen zum Abrufen des Gerätestandorts finden Sie in der Referenz zur FusedLocationProviderApi in den Standort-APIs der Google Play-Dienste.

Java

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

@SuppressLint("MissingPermission")
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)
    }
}

Aktuellen Ort abrufen

Verwenden Sie das Places SDK for Android, um eine Liste von Orten abzurufen, die sich möglicherweise am aktuellen Standort befinden. In diesem Zusammenhang ist ein Ort ein Unternehmen oder ein anderer POI.

In diesem Tutorial wird der aktuelle Ort abgerufen, wenn der Nutzer auf den Button zum Abrufen des Ortes klickt. Dem Nutzer wird daraufhin eine Liste mit möglichen Orten zur Auswahl angezeigt. Wenn er einen Ort ausgewählt hat, wird an dessen Position eine Markierung erstellt. Das Tutorial enthält den Code, den Sie für die Interaktion mit dem Places SDK for Android benötigen. Weitere Informationen finden Sie in der Anleitung zum Abrufen des aktuellen Orts.

  1. Erstellen Sie eine Layoutdatei (current_place_menu.xml) für das Optionsmenü und überschreiben Sie die onCreateOptionsMenu()-Methode, um das Optionsmenü einzurichten. Entsprechenden Code finden Sie in der zugehörigen Beispiel-App.
  2. Überschreiben Sie die onOptionsItemSelected()-Methode, um den aktuellen Ort abzurufen, wenn der Nutzer auf die Option zum Abrufen des Standorts klickt:

    Java

    @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. Erstellen Sie eine showCurrentPlace()-Methode, um eine Liste mit Orten abzurufen, die sich möglicherweise am aktuellen Standort befinden:

    Java

    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.DISPLAY_NAME,
                            Place.Field.FORMATTED_ADDRESS,
                            Place.Field.LOCATION);
    
            // Search around the device's last-known location, falling back to the center of the
            // map if the location is not available yet.
            LatLng center =
                    lastKnownLocation != null
                            ? new LatLng(
                                    lastKnownLocation.getLatitude(),
                                    lastKnownLocation.getLongitude())
                            : map.getCameraPosition().target;
            CircularBounds searchArea = CircularBounds.newInstance(center, SEARCH_RADIUS_METERS);
    
            // Use the builder to create a SearchNearbyRequest that returns the closest places
            // first.
            SearchNearbyRequest request =
                    SearchNearbyRequest.builder(searchArea, placeFields)
                            .setMaxResultCount(M_MAX_ENTRIES)
                            .setRankPreference(SearchNearbyRequest.RankPreference.DISTANCE)
                            .build();
    
            // Get the likely places - that is, the businesses and other points of interest that
            // are closest to the device's current location.
            final Task<SearchNearbyResponse> placeResult = placesClient.searchNearby(request);
            placeResult.addOnCompleteListener(
                    new OnCompleteListener<SearchNearbyResponse>() {
                        @Override
                        public void onComplete(@NonNull Task<SearchNearbyResponse> task) {
                            if (task.isSuccessful() && task.getResult() != null) {
                                List<Place> responsePlaces = task.getResult().getPlaces();
                                int count = Math.min(responsePlaces.size(), M_MAX_ENTRIES);
                                List<PlaceItemUiModel> likelyPlaces = new ArrayList<>();
                                for (int i = 0; i < count; i++) {
                                    PlaceItemUiModel model = toUiModel(responsePlaces.get(i));
                                    if (model != null) {
                                        likelyPlaces.add(model);
                                    }
                                }
    
                                // Show a dialog offering the user the list of likely places, and
                                // add a marker at the selected place.
                                MapsActivityCurrentPlace.this.openPlacesDialog(likelyPlaces);
                            } 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

    private fun showCurrentPlace() {
        val map = map ?: return
        // ...
        if (locationPermissionGranted) {
            // Use fields to define the data types to return.
            val placeFields = listOf(
                Place.Field.DISPLAY_NAME,
                Place.Field.FORMATTED_ADDRESS,
                Place.Field.LOCATION,
            )
    
            // Search around the device's last-known location, falling back to the center of
            // the map if the location is not available yet.
            val center = lastKnownLocation?.let { LatLng(it.latitude, it.longitude) }
                ?: map.cameraPosition.target
            val searchArea = CircularBounds.newInstance(center, SEARCH_RADIUS_METERS)
    
            // Use the builder to create a SearchNearbyRequest that returns the closest places
            // first.
            val request = SearchNearbyRequest.builder(searchArea, placeFields)
                .setMaxResultCount(M_MAX_ENTRIES)
                .setRankPreference(SearchNearbyRequest.RankPreference.DISTANCE)
                .build()
    
            // Get the likely places - that is, the businesses and other points of interest that
            // are closest to the device's current location.
            val placeResult = placesClient.searchNearby(request)
            placeResult.addOnCompleteListener { task ->
                if (task.isSuccessful && task.result != null) {
                    val likelyPlaces = task.result.places
                        .take(M_MAX_ENTRIES)
                        .mapNotNull { it.toUiModel() }
    
                    // Show a dialog offering the user the list of likely places, and add a
                    // marker at the selected place.
                    openPlacesDialog(likelyPlaces)
                } 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. Erstellen Sie eine openPlacesDialog()-Methode, um ein Formular einzublenden, über das der Nutzer einen Ort aus einer Liste möglicher Orte auswählen kann. Erstellen Sie auf der Karte eine Markierung für den ausgewählten Ort. Die Markierung enthält den Namen und die Adresse des Orts sowie alle Quellenangaben, die von der API zur Verfügung gestellt werden:

    Java

    void openPlacesDialog(List<PlaceItemUiModel> likelyPlaces) {
        // ...
        // 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.
                        PlaceItemUiModel selectedPlace = likelyPlaces.get(which);
    
                        // Add a marker for the selected place, with an info window
                        // showing information about that place.
                        map.addMarker(
                                new MarkerOptions()
                                        .title(selectedPlace.title)
                                        .position(selectedPlace.position)
                                        .snippet(selectedPlace.snippet));
    
                        // Position the map's camera at the location of the marker.
                        map.moveCamera(
                                CameraUpdateFactory.newLatLngZoom(
                                        selectedPlace.position, DEFAULT_ZOOM));
                    }
                };
    
        // Display the dialog.
        String[] titles = new String[likelyPlaces.size()];
        for (int i = 0; i < likelyPlaces.size(); i++) {
            titles[i] = likelyPlaces.get(i).title;
        }
        new AlertDialog.Builder(this)
                .setTitle(R.string.pick_place)
                .setItems(titles, listener)
                .show();
    }
    

    Kotlin

    internal fun openPlacesDialog(likelyPlaces: List<PlaceItemUiModel>) {
        // ...
        // Ask the user to choose the place where they are now.
        val listener = DialogInterface.OnClickListener { _, which ->
            // The "which" argument contains the position of the selected item.
            val selectedPlace = likelyPlaces[which]
    
            // Add a marker for the selected place, with an info window
            // showing information about that place.
            selectedMarker = map?.addMarker(
                MarkerOptions()
                    .title(selectedPlace.title)
                    .position(selectedPlace.position)
                    .snippet(selectedPlace.snippet),
            )
    
            // Position the map's camera at the location of the marker.
            map?.moveCamera(
                CameraUpdateFactory.newLatLngZoom(
                    selectedPlace.position,
                    DEFAULT_ZOOM.toFloat(),
                ),
            )
        }
    
        // Display the dialog.
        val titles = likelyPlaces.map { it.title }.toTypedArray()
        AlertDialog.Builder(this)
            .setTitle(R.string.pick_place)
            .setItems(titles, listener)
            .show()
    }
  5. Erstellen Sie ein benutzerdefiniertes Layout für den Inhalt des Infofensters. Dadurch erhalten Sie in diesem Fenster mehrere Inhaltszeilen. Fügen Sie zuerst die XML-Layoutdatei custom_info_contents.xml hinzu, die eine Textansicht für den Titel des Infofensters enthält, und dann eine weitere Textansicht für das Snippet, also den Textinhalt dieses Fensters:

    <?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. Implementieren Sie die Schnittstelle InfoWindowAdapter, um eine Layout-Inflation durchzuführen und den Inhalt des Infofensters zu laden.

    Java

    // 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? = 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
        }
    })

Status der Karte speichern

Speichern Sie die Kameraposition und den Gerätestandort. Wenn ein Nutzer ein Android-Gerät dreht oder die Konfiguration ändert, wird die Kartenaktivität durch das Android-Framework zerstört und wieder neu erstellt. Damit dieser Ablauf flüssiger wird, sollten Sie den Status der relevanten App speichern und bei Bedarf wiederherstellen.

Im vorliegenden Tutorial finden Sie den gesamten Code, der erforderlich ist, um den Status der Karte zu speichern. Weitere Informationen finden Sie in der Anleitung zum savedInstanceState-Paket.

  1. Richten Sie in Ihrer Kartenaktivität Schlüssel/Wert-Paare ein, um den Status der Aktivität zu speichern:

    Java

    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. Implementieren Sie den onSaveInstanceState()-Callback, um den Status zu speichern, wenn die Aktivität pausiert:

    Java

    @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. Rufen Sie in der onCreate()-Methode Ihrer Aktivität den Standort des Geräts und die Kameraposition der Karte ab, falls sie zuvor gespeichert wurden:

    Java

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