هنگام پر کردن آدرس تحویل، اطلاعات صورتحساب یا اطلاعات رویداد، فعال کردن فرمها با تکمیل خودکار مکان به کاربران کمک میکند هنگام وارد کردن اطلاعات آدرس، ضربههای کلید و اشتباهات را کاهش دهند. این آموزش مراحل لازم برای فعال کردن یک فیلد ورودی با تکمیل خودکار مکان و پر کردن فیلدهای فرم آدرس با اجزای آدرس از آدرس انتخاب شده توسط کاربر را طی می کند و آدرس انتخاب شده را روی نقشه برای کمک به تأیید بصری ارائه می دهد.
ویدئوها: فرمهای آدرس را با تکمیل خودکار مکان بهبود دهید
فرم های آدرس
اندروید
iOS
وب
پلتفرم نقشه های گوگل ویجت تکمیل خودکار مکان را برای پلتفرم های موبایل و وب ارائه می دهد. ویجت، که در شکلهای قبلی نشان داده شده است، یک گفتگوی جستجو با قابلیت تکمیل خودکار داخلی ارائه میکند که حتی میتوانید آن را برای جستجوی محدوده مکان بهینه کنید.
کد را دریافت کنید
مخزن Google Places SDK for Android Demos را از GitHub کلون یا دانلود کنید.
مشاهده نسخه جاوا فعالیت:
/* * 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 } }); } }
فعال کردن API ها
برای اجرای این توصیهها، باید APIهای زیر را در Google Cloud Console فعال کنید:
- Maps SDK برای Android (یا API برای پلتفرم انتخابی شما)
- Places API
برای اطلاعات بیشتر درباره راهاندازی، به راهاندازی پروژه Google Cloud خود مراجعه کنید.
افزودن تکمیل خودکار به فیلدهای ورودی
این بخش نحوه افزودن تکمیل خودکار مکان را به فرم آدرس توضیح می دهد.
افزودن ویجت تکمیل خودکار مکان
در Android، میتوانید ویجت تکمیل خودکار را با استفاده از یک هدف تکمیل خودکار اضافه کنید که تکمیل خودکار مکان را از قسمت ورودی خط 1 نشانی راهاندازی میکند، جایی که کاربر شروع به وارد کردن آدرس خود میکند. وقتی شروع به تایپ می کنند، می توانند آدرس خود را از لیست پیش بینی های تکمیل خودکار انتخاب کنند.
ابتدا با استفاده از ActivityResultLauncher
یک راهانداز فعالیت آماده کنید تا به نتیجه فعالیت راهاندازی شده گوش دهد . پاسخ تماس نتیجه شامل یک شی مکان مربوط به آدرسی است که کاربر از پیش بینی های تکمیل خودکار انتخاب می کند.
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"); } });
در مرحله بعد، فیلدها، مکان و خصوصیات نوع هدف Place Autocomplete را تعریف کنید و آن را با Autocomplete.IntentBuilder
بسازید. در نهایت، intent را با استفاده از ActivityResultLauncher
تعریف شده در نمونه کد قبلی اجرا کنید.
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); }
مدیریت آدرس بازگردانده شده توسط Place Autocomplete
تعریف ActivityResultLauncher
قبلاً همچنین مشخص می کند که وقتی نتیجه فعالیت در تماس برگشتی برگردانده می شود چه کاری باید انجام شود. اگر کاربر یک پیشبینی را انتخاب کرد، آن را در هدف موجود در شیء نتیجه تحویل میدهد. از آنجایی که intent توسط Autocomplete.IntentBuilder
ساخته شده است، روش Autocomplete.getPlaceFromIntent()
می تواند شی Place را از آن استخراج کند.
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"); } });
از آنجا، Place.getAddressComponents()
را فراخوانی کنید و هر جزء آدرس را با فیلد ورودی مربوطه در فرم آدرس مطابقت دهید و فیلد را با مقدار مکان انتخابی کاربر پر کنید.
یک نمونه پیاده سازی برای پر کردن فیلدهای فرم آدرس در روش fillInAddress
کد نمونه ارائه شده در بخش دریافت کد این صفحه به اشتراک گذاشته شده است.
گرفتن دادههای آدرس از پیشبینی بهجای یک آدرس وارد شده بهصورت دستی به اطمینان از صحت آدرس کمک میکند، اطمینان میدهد که آدرس شناخته شده است و میتواند به آن تحویل داده شود، و ضربههای کلید کاربر را کاهش میدهد.
ملاحظات هنگام اجرای تکمیل خودکار مکان
تکمیل خودکار مکان تعدادی گزینه دارد که به آن اجازه میدهد در اجرای آن انعطافپذیر باشد، اگر میخواهید بیشتر از ویجت استفاده کنید. می توانید از ترکیبی از خدمات استفاده کنید تا دقیقاً آنچه را که برای مطابقت با یک مکان به روش صحیح نیاز دارید به دست آورید.
برای فرم ADDRESS، پارامتر انواع را روی
address
تنظیم کنید تا مطابقت ها را به آدرس های کامل خیابان محدود کنید. درباره انواع پشتیبانی شده در درخواست های تکمیل خودکار مکان بیشتر بیاموزید.اگر نیازی به جستجو در سراسر جهان ندارید، محدودیت ها و سوگیری های مناسب را تنظیم کنید. تعدادی پارامتر وجود دارد که میتوان از آنها برای تعصب یا محدود کردن هر تطابق فقط به مناطق خاص استفاده کرد.
از
RectangularBounds
برای تنظیم محدودیت های مستطیلی برای یک ناحیه استفاده کنید، ازsetLocationRestriction()
استفاده کنید تا مطمئن شوید که فقط آدرس ها در آن مناطق برگردانده می شوند.از
setCountries()
برای محدود کردن پاسخ ها به مجموعه خاصی از کشورها استفاده کنید.
درصورتیکه فیلدهای خاصی از مسابقه حذف شدند، فیلدها را قابل ویرایش بگذارید و به مشتریان اجازه دهید در صورت لزوم آدرس را بهروزرسانی کنند. از آنجایی که اکثر آدرسهای ارسال شده توسط Place Autocomplete حاوی اعداد فرعی مانند شماره آپارتمان، مجموعه، یا واحد نیستند، میتوانید تمرکز را به خط آدرس ۲ منتقل کنید تا کاربر را تشویق کنید در صورت لزوم آن اطلاعات را پر کند.
ارائه تایید بصری آدرس
به عنوان بخشی از ورود آدرس، تأیید بصری آدرس را روی نقشه به کاربران ارائه دهید. این به کاربران اطمینان بیشتری از درست بودن آدرس می دهد.
شکل زیر نقشه ای را در زیر آدرس با پین در آدرس وارد شده نشان می دهد.
مثال زیر مراحل اولیه اضافه کردن نقشه در اندروید را دنبال می کند. برای جزئیات بیشتر به مستندات مراجعه کنید.
- اضافه کردن
SupportMapFragment
(در این مورد، افزودن یک قطعه به صورت پویا) - گرفتن یک دسته برای قطعه و ثبت پاسخ تماس
- یک ظاهر طراحی و اضافه کردن یک نشانگر به نقشه
- غیرفعال کردن کنترل های نقشه
اضافه کردن SupportMapFragment
ابتدا یک قطعه SupportMapFragment
به فایل XML layout اضافه کنید.
<fragment android:name="com.google.android.gms.maps.SupportMapFragment" android:id="@+id/confirmation_map" android:layout_width="match_parent" android:layout_height="match_parent"/>
سپس، اگر قطعه هنوز وجود ندارد، آن را به صورت برنامهنویسی اضافه کنید.
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); } }
گرفتن یک دسته برای قطعه و ثبت پاسخ تماس
برای بدست آوردن دسته ای از قطعه، متد
FragmentManager.findFragmentById
را فراخوانی کنید و شناسه منبع قطعه را در فایل طرح بندی خود ارسال کنید. اگر قطعه را به صورت پویا اضافه کردید ، از این مرحله بگذرید زیرا قبلاً دسته را بازیابی کرده اید.متد
getMapAsync
را فراخوانی کنید تا پاسخ تماس را روی قطعه تنظیم کنید.
به عنوان مثال، اگر قطعه را به صورت ایستا اضافه کنید:
کاتلین
val mapFragment = supportFragmentManager .findFragmentById(R.id.map) as SupportMapFragment mapFragment.getMapAsync(this)
جاوا
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() .findFragmentById(R.id.map); mapFragment.getMapAsync(this);
یک ظاهر طراحی و اضافه کردن یک نشانگر به نقشه
وقتی نقشه آماده شد، سبک را تنظیم کنید، دوربین را در مرکز قرار دهید و یک نشانگر در مختصات آدرس وارد شده اضافه کنید. کد زیر از استایل تعریف شده در یک شی JSON استفاده می کند یا می توانید یک شناسه نقشه را که با استایل Maps مبتنی بر Cloud تعریف شده است بارگیری کنید.
@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)); }
غیرفعال کردن کنترل های نقشه
برای ساده نگه داشتن نقشه با نشان دادن مکان بدون کنترلهای نقشه اضافی (مانند قطبنما، نوار ابزار یا سایر ویژگیهای داخلی)، کنترلهایی را که لازم نمیدانید غیرفعال کنید. در اندروید، گزینه دیگر فعال کردن حالت Lite برای ارائه تعامل محدود است.