Wenn Sie Formulare mit Place Autocomplete aktivieren, können Nutzer beim Ausfüllen von Lieferadressen, Zahlungsinformationen oder Veranstaltungsinformationen weniger Tastenanschläge ausführen und Fehler vermeiden. In dieser Anleitung wird beschrieben, wie Sie ein Eingabefeld mit Place Autocomplete aktivieren, Adressformularfelder mit Adresskomponenten aus der vom Nutzer ausgewählten Adresse ausfüllen und die ausgewählte Adresse auf einer Karte anzeigen, um eine visuelle Bestätigung zu ermöglichen.
Videos: Adressformulare mit Place Autocomplete optimieren
Adressformulare
Android
iOS
Web
In der Google Maps Platform finden Sie ein „Place Autocomplete“-Widget für mobile Plattformen und das Web. Das Widget, das in den vorherigen Abbildungen gezeigt wurde, umfasst ein Dialogfeld für die Suche mit integrierter automatischer Vervollständigung, das sogar für die standortbezogene Suche optimiert werden kann.
Code abrufen
Klone das Repository „Google Places SDK for Android Demos“ auf GitHub oder lade es herunter.
So sieht die Java-Version der Aktivität aus:
/* * Copyright 2022 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 * * https://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.placesdemo; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Resources; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.ViewStub; import android.widget.Button; import android.widget.CheckBox; import android.widget.Toast; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import com.example.placesdemo.databinding.AutocompleteAddressActivityBinding; 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.GoogleMapOptions; import com.google.android.gms.maps.OnMapReadyCallback; import com.google.android.gms.maps.SupportMapFragment; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.MapStyleOptions; 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.AddressComponent; import com.google.android.libraries.places.api.model.AddressComponents; import com.google.android.libraries.places.api.model.Place; import com.google.android.libraries.places.api.model.TypeFilter; import com.google.android.libraries.places.api.net.PlacesClient; import com.google.android.libraries.places.widget.Autocomplete; import com.google.android.libraries.places.widget.model.AutocompleteActivityMode; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static com.google.maps.android.SphericalUtil.computeDistanceBetween; /** * Activity for using Place Autocomplete to assist filling out an address form. */ @SuppressWarnings("FieldCanBeLocal") public class AutocompleteAddressActivity extends AppCompatActivity implements OnMapReadyCallback { private static final String TAG = "ADDRESS_AUTOCOMPLETE"; private static final String MAP_FRAGMENT_TAG = "MAP"; private LatLng coordinates; private boolean checkProximity = false; private SupportMapFragment mapFragment; private GoogleMap map; private Marker marker; private PlacesClient placesClient; private View mapPanel; private LatLng deviceLocation; private static final double acceptedProximity = 150; private AutocompleteAddressActivityBinding binding; View.OnClickListener startAutocompleteIntentListener = view -> { view.setOnClickListener(null); startAutocompleteIntent(); }; private final ActivityResultLauncher<Intent> startAutocomplete = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == Activity.RESULT_OK) { Intent intent = result.getData(); if (intent != null) { Place place = Autocomplete.getPlaceFromIntent(intent); // Write a method to read the address components from the Place // and populate the form with the address components Log.d(TAG, "Place: " + place.getAddressComponents()); fillInAddress(place); } } else if (result.getResultCode() == Activity.RESULT_CANCELED) { // The user canceled the operation. Log.i(TAG, "User canceled autocomplete"); } }); @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent intent) { super.onActivityResult(requestCode, resultCode, intent); binding.autocompleteAddress1.setOnClickListener(startAutocompleteIntentListener); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = AutocompleteAddressActivityBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); // Retrieve a PlacesClient (previously initialized - see MainActivity) placesClient = Places.createClient(this); // Attach an Autocomplete intent to the Address 1 EditText field binding.autocompleteAddress1.setOnClickListener(startAutocompleteIntentListener); // Update checkProximity when user checks the checkbox CheckBox checkProximityBox = findViewById(R.id.checkbox_proximity); checkProximityBox.setOnCheckedChangeListener((view, isChecked) -> { // Set the boolean to match user preference for when the Submit button is clicked checkProximity = isChecked; }); // Submit and optionally check proximity Button saveButton = findViewById(R.id.autocomplete_save_button); saveButton.setOnClickListener(v -> saveForm()); // Reset the form Button resetButton = findViewById(R.id.autocomplete_reset_button); resetButton.setOnClickListener(v -> clearForm()); } private void startAutocompleteIntent() { // Set the fields to specify which types of place data to // return after the user has made a selection. List<Place.Field> fields = Arrays.asList(Place.Field.ADDRESS_COMPONENTS, Place.Field.LAT_LNG, Place.Field.VIEWPORT); // Build the autocomplete intent with field, country, and type filters applied Intent intent = new Autocomplete.IntentBuilder(AutocompleteActivityMode.OVERLAY, fields) .setCountries(Arrays.asList("US")) .setTypesFilter(new ArrayList<String>() {{ add(TypeFilter.ADDRESS.toString().toLowerCase()); }}) .build(this); startAutocomplete.launch(intent); } @Override public void onMapReady(@NonNull GoogleMap googleMap) { map = googleMap; try { // Customise the styling of the base map using a JSON object defined // in a string resource. boolean success = map.setMapStyle( MapStyleOptions.loadRawResourceStyle(this, R.raw.style_json)); if (!success) { Log.e(TAG, "Style parsing failed."); } } catch (Resources.NotFoundException e) { Log.e(TAG, "Can't find style. Error: ", e); } map.moveCamera(CameraUpdateFactory.newLatLngZoom(coordinates, 15f)); marker = map.addMarker(new MarkerOptions().position(coordinates)); } private void fillInAddress(Place place) { AddressComponents components = place.getAddressComponents(); StringBuilder address1 = new StringBuilder(); StringBuilder postcode = new StringBuilder(); // Get each component of the address from the place details, // and then fill-in the corresponding field on the form. // Possible AddressComponent types are documented at https://goo.gle/32SJPM1 if (components != null) { for (AddressComponent component : components.asList()) { String type = component.getTypes().get(0); switch (type) { case "street_number": { address1.insert(0, component.getName()); break; } case "route": { address1.append(" "); address1.append(component.getShortName()); break; } case "postal_code": { postcode.insert(0, component.getName()); break; } case "postal_code_suffix": { postcode.append("-").append(component.getName()); break; } case "locality": binding.autocompleteCity.setText(component.getName()); break; case "administrative_area_level_1": { binding.autocompleteState.setText(component.getShortName()); break; } case "country": binding.autocompleteCountry.setText(component.getName()); break; } } } binding.autocompleteAddress1.setText(address1.toString()); binding.autocompletePostal.setText(postcode.toString()); // After filling the form with address components from the Autocomplete // prediction, set cursor focus on the second address line to encourage // entry of sub-premise information such as apartment, unit, or floor number. binding.autocompleteAddress2.requestFocus(); // Add a map for visual confirmation of the address showMap(place); } private void showMap(Place place) { coordinates = place.getLatLng(); // It isn't possible to set a fragment's id programmatically so we set a tag instead and // search for it using that. mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentByTag(MAP_FRAGMENT_TAG); // We only create a fragment if it doesn't already exist. if (mapFragment == null) { mapPanel = ((ViewStub) findViewById(R.id.stub_map)).inflate(); GoogleMapOptions mapOptions = new GoogleMapOptions(); mapOptions.mapToolbarEnabled(false); // To programmatically add the map, we first create a SupportMapFragment. mapFragment = SupportMapFragment.newInstance(mapOptions); // Then we add it using a FragmentTransaction. getSupportFragmentManager() .beginTransaction() .add(R.id.confirmation_map, mapFragment, MAP_FRAGMENT_TAG) .commit(); mapFragment.getMapAsync(this); } else { updateMap(coordinates); } } private void updateMap(LatLng latLng) { marker.setPosition(latLng); map.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 15f)); if (mapPanel.getVisibility() == View.GONE) { mapPanel.setVisibility(View.VISIBLE); } } private void saveForm() { Log.d(TAG, "checkProximity = " + checkProximity); if (checkProximity) { checkLocationPermissions(); } else { Toast.makeText( this, R.string.autocomplete_skipped_message, Toast.LENGTH_SHORT) .show(); } } private void clearForm() { binding.autocompleteAddress1.setText(""); binding.autocompleteAddress2.getText().clear(); binding.autocompleteCity.getText().clear(); binding.autocompleteState.getText().clear(); binding.autocompletePostal.getText().clear(); binding.autocompleteCountry.getText().clear(); if (mapPanel != null) { mapPanel.setVisibility(View.GONE); } binding.autocompleteAddress1.requestFocus(); } // Register the permissions callback, which handles the user's response to the // system permissions dialog. Save the return value, an instance of // ActivityResultLauncher, as an instance variable. private final ActivityResultLauncher<String> requestPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { if (isGranted) { // Since ACCESS_FINE_LOCATION is the only permission in this sample, // run the location comparison task once permission is granted. // Otherwise, check which permission is granted. getAndCompareLocations(); } else { // Fallback behavior if user denies permission Log.d(TAG, "User denied permission"); } }); private void checkLocationPermissions() { if (ContextCompat.checkSelfPermission(this, ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { getAndCompareLocations(); } else { requestPermissionLauncher.launch( ACCESS_FINE_LOCATION); } } @SuppressLint("MissingPermission") private void getAndCompareLocations() { // TODO: Detect and handle if user has entered or modified the address manually and update // the coordinates variable to the Lat/Lng of the manually entered address. May use // Geocoding API to convert the manually entered address to a Lat/Lng. LatLng enteredLocation = coordinates; map.setMyLocationEnabled(true); FusedLocationProviderClient fusedLocationClient = LocationServices.getFusedLocationProviderClient(this); fusedLocationClient.getLastLocation() .addOnSuccessListener(this, location -> { // Got last known location. In some rare situations this can be null. if (location == null) { return; } deviceLocation = new LatLng(location.getLatitude(), location.getLongitude()); Log.d(TAG, "device location = " + deviceLocation); Log.d(TAG, "entered location = " + enteredLocation.toString()); // Use the computeDistanceBetween function in the Maps SDK for Android Utility Library // to use spherical geometry to compute the distance between two Lat/Lng points. double distanceInMeters = computeDistanceBetween(deviceLocation, enteredLocation); if (distanceInMeters <= acceptedProximity) { Log.d(TAG, "location matched"); // TODO: Display UI based on the locations matching } else { Log.d(TAG, "location not matched"); // TODO: Display UI based on the locations not matching } }); } }
APIs aktivieren
Für diese Implementierungen müssen Sie folgende APIs in der Google Cloud Console aktivieren:
- Maps SDK for Android oder die API für die Plattform Ihrer Wahl
- Places API
Weitere Informationen zur Einrichtung finden Sie unter Google Cloud-Projekt einrichten.
Automatische Vervollständigung in Eingabefeldern
In diesem Abschnitt wird beschrieben, wie Sie einem Adressformular Place Autocomplete hinzufügen.
Das „Place Autocomplete“-Widget hinzufügen
In Android können Sie das Autocomplete-Widget über einen Autocomplete-Intent hinzufügen. Damit wird Place Autocomplete über das Eingabefeld für Adresszeile 1 gestartet, wo der Nutzer mit der Adresseingabe beginnt. Wenn er anfängt zu tippen, wird eine Liste mit automatischen Vervollständigungen angezeigt, aus der er seine Adresse auswählen kann.
Erstellen Sie zuerst einen Aktivitäts-Launcher mit ActivityResultLauncher
, der auf ein Ergebnis der gestarteten Aktivität wartet. Der Ergebnis-Callback enthält ein Place-Objekt, das der Adresse entspricht, die der Nutzer aus den automatischen Vervollständigungen auswählt.
private final ActivityResultLauncher<Intent> startAutocomplete = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == Activity.RESULT_OK) { Intent intent = result.getData(); if (intent != null) { Place place = Autocomplete.getPlaceFromIntent(intent); // Write a method to read the address components from the Place // and populate the form with the address components Log.d(TAG, "Place: " + place.getAddressComponents()); fillInAddress(place); } } else if (result.getResultCode() == Activity.RESULT_CANCELED) { // The user canceled the operation. Log.i(TAG, "User canceled autocomplete"); } });
Legen Sie als Nächstes die Felder, den Ort und die Typenattribute des „Place Autocomplete“-Intents fest und erstellen Sie ihn mit Autocomplete.IntentBuilder
.
Starten Sie den Intent mit dem ActivityResultLauncher
, der im vorherigen Codebeispiel definiert wurde.
private void startAutocompleteIntent() { // Set the fields to specify which types of place data to // return after the user has made a selection. List<Place.Field> fields = Arrays.asList(Place.Field.ADDRESS_COMPONENTS, Place.Field.LAT_LNG, Place.Field.VIEWPORT); // Build the autocomplete intent with field, country, and type filters applied Intent intent = new Autocomplete.IntentBuilder(AutocompleteActivityMode.OVERLAY, fields) .setCountries(Arrays.asList("US")) .setTypesFilter(new ArrayList<String>() {{ add(TypeFilter.ADDRESS.toString().toLowerCase()); }}) .build(this); startAutocomplete.launch(intent); }
Von Place Autocomplete zurückgegebene Adresse verarbeiten
Als Sie zuvor den ActivityResultLauncher
definiert haben, wurde auch festgelegt, was passieren soll, wenn das Aktivitätsergebnis im Callback zurückgegeben wird. Wenn der Nutzer einen Vorschlag ausgewählt hat, wird dieser in dem Intent zurückgegeben, der im Ergebnisobjekt enthalten ist. Da der Intent über Autocomplete.IntentBuilder
erstellt wurde, kann das Place-Objekt mit der Methode Autocomplete.getPlaceFromIntent()
extrahiert werden.
private final ActivityResultLauncher<Intent> startAutocomplete = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == Activity.RESULT_OK) { Intent intent = result.getData(); if (intent != null) { Place place = Autocomplete.getPlaceFromIntent(intent); // Write a method to read the address components from the Place // and populate the form with the address components Log.d(TAG, "Place: " + place.getAddressComponents()); fillInAddress(place); } } else if (result.getResultCode() == Activity.RESULT_CANCELED) { // The user canceled the operation. Log.i(TAG, "User canceled autocomplete"); } });
Von dort rufen Sie Place.getAddressComponents()
auf und weisen jede Adresskomponente dem entsprechenden Eingabefeld im Adressformular zu. Das Feld wird dabei mit dem Wert des Orts ausgefüllt, den der Nutzer ausgewählt hat.
Eine Beispielimplementierung zum Ausfüllen der Felder des Adressformulars finden Sie in der fillInAddress
-Methode des Beispielcodes im Abschnitt Code abrufen dieser Seite.
Wenn die Adressdaten aus Vorschlägen übernommen und nicht manuell eingegeben werden, lassen sich Fehler bei der Eingabe verhindern. Außerdem ist so direkt klar, dass die Adresse existiert und beliefert werden kann. Auch der Eingabeaufwand für den Nutzer wird gesenkt.
Überlegungen bei der Implementierung von Place Autocomplete
Place Autocomplete bietet eine Reihe von Optionen, mit denen Sie die Implementierung flexibel gestalten können, wenn Sie nicht nur das Widget nutzen möchten. Sie können Dienste so miteinander kombinieren, dass sich der jeweilige Ort korrekt abgleichen lässt.
Für ein Adressformular sollten Sie den Parameter „types“ auf
address
festlegen, damit die Treffer auf vollständige Adressen beschränkt werden. Weitere Informationen zu unterstützten Typen in „Place Autocomplete“-AnfragenWenn kein weltweiter Suchvorgang erforderlich ist, legen Sie die entsprechenden Einschränkungen oder Gewichtungen fest. Es gibt eine Reihe von Parametern, mit denen sich Treffer so auf bestimmte Regionen beschränken lassen.
Mit
RectangularBounds
werden die rechteckigen Begrenzungen für ein Gebiet festgelegt. MitsetLocationRestriction()
werden nur Adressen in den jeweiligen Regionen zurückgegeben.Wenn die Ergebnisse auf eine bestimmte Gruppe von Ländern beschränkt werden sollen, ist
setCountries()
die richtige Wahl.
Kunden sollten die Felder immer bearbeiten können. Falls bei einigen die Zuordnung nicht funktioniert, können sie die Adresse dann manuell ändern. Die meisten Daten, die über Place Autocomplete zurückgegeben werden, enthalten keine Adresszusätze, wie etwa die Nummer des Apartments, der Suite oder der Gebäudeeinheit. Sie können die Aufmerksamkeit aber auf Adresszeile 2 lenken, damit Nutzer diese Informationen gegebenenfalls eingeben.
Visuelle Bestätigung der Adresse
Sie können die Adresse während der Adresseingabe auf einer Karte anzeigen lassen, damit Nutzer sich davon überzeugen können, dass sie wirklich stimmt.
In der folgenden Abbildung wird unter den Eingabefeldern eine Karte eingeblendet, auf der die Adresse markiert ist.
Das nächste Beispiel folgt den grundlegenden Schritten zum Hinzufügen einer Karte in Android. Weitere Informationen finden Sie in der Dokumentation.
SupportMapFragment
hinzufügen (in diesem Fall wird ein Fragment dynamisch hinzugefügt)- Handle zum Fragment abrufen und Callback registrieren
- Stil festlegen und eine Markierung auf der Karte einfügen
- Kartensteuerelemente deaktivieren
SupportMapFragment
hinzufügen
Zuerst müssen Sie ein SupportMapFragment
in die Layoutdatei (XML) einfügen.
<fragment android:name="com.google.android.gms.maps.SupportMapFragment" android:id="@+id/confirmation_map" android:layout_width="match_parent" android:layout_height="match_parent"/>
Fügen Sie das Fragment dann programmatisch hinzu, falls es noch nicht vorhanden ist.
private void showMap(Place place) { coordinates = place.getLatLng(); // It isn't possible to set a fragment's id programmatically so we set a tag instead and // search for it using that. mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentByTag(MAP_FRAGMENT_TAG); // We only create a fragment if it doesn't already exist. if (mapFragment == null) { mapPanel = ((ViewStub) findViewById(R.id.stub_map)).inflate(); GoogleMapOptions mapOptions = new GoogleMapOptions(); mapOptions.mapToolbarEnabled(false); // To programmatically add the map, we first create a SupportMapFragment. mapFragment = SupportMapFragment.newInstance(mapOptions); // Then we add it using a FragmentTransaction. getSupportFragmentManager() .beginTransaction() .add(R.id.confirmation_map, mapFragment, MAP_FRAGMENT_TAG) .commit(); mapFragment.getMapAsync(this); } else { updateMap(coordinates); } }
Handle zum Fragment abrufen und Callback registrieren
Um ein Handle zum Fragment zu erhalten, müssen Sie die Methode
FragmentManager.findFragmentById
aufrufen und dabei die Ressourcen-ID des Fragments in der Layoutdatei übergeben. Wenn Sie das Fragment dynamisch hinzugefügt haben, überspringen Sie diesen Schritt, da Sie den Handle bereits abgerufen haben.Rufen Sie die
getMapAsync
-Methode auf, um den Callback für das Fragment festzulegen.
In diesem Beispiel wird angenommen, dass das Fragment statisch hinzugefügt wurde:
Kotlin
val mapFragment = supportFragmentManager .findFragmentById(R.id.map) as SupportMapFragment mapFragment.getMapAsync(this)
Java
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() .findFragmentById(R.id.map); mapFragment.getMapAsync(this);
Stil festlegen und eine Markierung auf der Karte einfügen
Wenn die Karte fertig ist, legen Sie den Stil fest, zentrieren Sie die Kamera und setzen Sie eine Markierung an den Koordinaten der eingegebenen Adresse. Im folgenden Code wird der Stil aus einem JSON-Objekt verwendet. Alternativ können Sie auch eine Karten-ID laden, die mithilfe von Funktionen für das cloudbasierte Gestalten von Karteninhalten definiert wurde.
@Override public void onMapReady(@NonNull GoogleMap googleMap) { map = googleMap; try { // Customise the styling of the base map using a JSON object defined // in a string resource. boolean success = map.setMapStyle( MapStyleOptions.loadRawResourceStyle(this, R.raw.style_json)); if (!success) { Log.e(TAG, "Style parsing failed."); } } catch (Resources.NotFoundException e) { Log.e(TAG, "Can't find style. Error: ", e); } map.moveCamera(CameraUpdateFactory.newLatLngZoom(coordinates, 15f)); marker = map.addMarker(new MarkerOptions().position(coordinates)); }
Kartensteuerelemente deaktivieren
Wenn Sie die Karte einfach gestalten möchten, können Sie den Ort ohne zusätzliche Steuerelemente wie Kompass, Symbolleiste oder andere integrierte Funktionen anzeigen lassen. Deaktivieren Sie dazu einfach alle Steuerelemente, die nicht gebraucht werden. Unter Android können Sie auch den Lite-Modus aktivieren. Interaktionen sind dann nur eingeschränkt möglich.