هنگام تکمیل نشانی ارسال، اطلاعات صورتحساب، یا اطلاعات رویداد، فعال کردن فرمهای دارای تکمیل خودکار جا به کاربران کمک میکند هنگام وارد کردن اطلاعات نشانی، تعداد ضربههای کلید و اشتباهات را کاهش دهند. این آموزش مراحل لازم برای فعال کردن فیلد ورودی با «تکمیل خودکار جا» و پر کردن فیلدهای فرم نشانی با مؤلفههای نشانی از نشانی انتخابشده توسط کاربر را شرح میدهد و نشانی انتخابشده را روی نقشه ارائه میدهد تا به تأیید بصری کمک کند.

ویدیوها: بهبود دادن فرمهای نشانی با «تکمیل خودکار جا»
فرمهای نشانی
Android
iOS
وب
«پلاتفرم Google Maps» ابزاره «تکمیل خودکار جا» را برای پلاتفرمهای تلفن همراه و وب ارائه میدهد. ابزارک، که در شکلهای قبلی نشان داده شده است، کادر گفتگوی جستجویی با عملکرد داخلی تکمیل خودکار ارائه میدهد که حتی میتوانید آن را برای جستجوی محدود به مکان بهینهسازی کنید.
دریافت کد
مخزن «نمونههای اولیه کیت توسعه نرمافزار مکانهای Google ویژه Android» را از GitHub همسانهسازی یا بارگیری کنید.
نسخه Java فعالیت را مشاهده کنید:
/* * 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.PlaceTypes; 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.Arrays; import java.util.List; import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static com.google.maps.android.SphericalUtil.computeDistanceBetween; import androidx.activity.EdgeToEdge; /** * 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) { // Enable edge-to-edge display. This must be called before calling super.onCreate(). EdgeToEdge.enable(this); 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.LOCATION, Place.Field.VIEWPORT); // Build the autocomplete intent with field, country, and type filters applied Intent intent = new Autocomplete.IntentBuilder(AutocompleteActivityMode.OVERLAY, fields) .setCountries(List.of("US")) // IMPORTANT: the widget uses the legacy api .setTypesFilter(List.of(PlaceTypes.ADDRESS)) .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.getLocation(); // 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 } }); } }
درحال فعال کردن میاناهای برنامهسازی کاربردی
برای پیادهسازی این توصیهها، باید میاناهای برنامهسازی کاربردی زیر را در کنسول Google Cloud فعال کنید:
- کیت توسعه نرمافزار Maps برای Android (یا API برای پلاتفرم انتخابی شما)
- Places API
برای اطلاعات بیشتر درباره راهاندازی، به راهاندازی پروژه Google Cloud مراجعه کنید.
افزودن تکمیل خودکار به فیلدهای ورودی
این بخش نحوه افزودن «تکمیل خودکار جا» به فرم نشانی را شرح میدهد.
درحال افزودن ابزاره «تکمیل خودکار جا»
در Android، میتوانید بااستفاده از هدف تکمیل خودکار، ابزاره تکمیل خودکار را اضافه کنید که «تکمیل خودکار جا» را از فیلد ورودی «خط نشانی ۱» راهاندازی میکند، جایی که کاربر شروع به وارد کردن نشانی خود میکند. وقتی شروع به تایپ کردن میکنند، میتوانند نشانیشان را از فهرست پیشبینیهای «تکمیل خودکار» انتخاب کنند.
ابتدا، بااستفاده از
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"); } });
سپس، فیلدها، مکان، و ویژگیهای نوع هدف «تکمیل خودکار جا» را تعریف کنید و آن را با
Autocomplete.IntentBuilder بسازید.
درنهایت، هدف را بااستفاده از 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.LOCATION, Place.Field.VIEWPORT); // Build the autocomplete intent with field, country, and type filters applied Intent intent = new Autocomplete.IntentBuilder(AutocompleteActivityMode.OVERLAY, fields) .setCountries(List.of("US")) // IMPORTANT: the widget uses the legacy api .setTypesFilter(List.of(PlaceTypes.ADDRESS)) .build(this); startAutocomplete.launch(intent); }
مدیریت نشانی برگرداندهشده توسط «تکمیل خودکار جا»
تعریف کردن ActivityResultLauncher در مراحل قبلی همچنین مشخص کرد که وقتی نتیجه فعالیت در فراخوانی برگشتی برگردانده میشود چه کاری باید انجام شود. اگر کاربر
پیشبینیای را انتخاب کرده باشد، این پیشبینی در هدف موجود در
شیء نتیجه ارائه خواهد شد. ازآنجاییکه هدف توسط Autocomplete.IntentBuilder ساخته شده است،
روش Autocomplete.getPlaceFromIntent() میتواند شیء «مکان» را از آن استخراج کند.
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 کد نمونه ارائهشده در بخش دریافت کد این صفحه همرسانی شده است.
گرفتن دادههای نشانی از پیشبینی بهجای نشانی واردشده بهصورت دستی به اطمینان از دقت نشانی کمک میکند، اطمینان میدهد که نشانی شناختهشده است و میتوان به آن تحویل داد، و تعداد کلیدهای فشردهشده توسط کاربر را کاهش میدهد.
ملاحظات هنگام پیادهسازی «تکمیل خودکار جا»
«تکمیل خودکار جا» گزینههایی دارد که به آن اجازه میدهد در پیادهسازیاش انعطافپذیر باشد، اگر بخواهید از چیزی بیشتر از ابزاره استفاده کنید. میتوانید از ترکیبی از سرویسها استفاده کنید تا دقیقاً آنچه را نیاز دارید برای مطابقت دادن مکان به روش صحیح دریافت کنید.
برای فرم نشانی، پارامتر types را روی
addressتنظیم کنید تا مطابقتها به نشانیهای کامل خیابان محدود شود. درباره انواع پشتیبانیشده در درخواستهای «تکمیل خودکار جا» بیشتر بدانید.اگر نیازی به جستجو در سراسر جهان ندارید، محدودیتها و گرایشهای مناسب را تنظیم کنید. پارامترهای متعددی وجود دارد که میتوان از آنها برای سوگیری یا محدود کردن هرگونه مطابقت فقط به مناطق خاص استفاده کرد.
از
RectangularBoundsبرای تنظیم محدودههای مستطیلی برای محدود کردن یک منطقه استفاده کنید، ازsetLocationRestriction()استفاده کنید تا مطمئن شوید فقط نشانیهای آن مناطق برگردانده میشوند.از
setCountries()برای محدود کردن پاسخها به مجموعه خاصی از کشورها استفاده کنید.
فیلدها را قابلویرایش بگذارید تا درصورت عدم وجود فیلدهای خاص در مطابقت، مشتریان بتوانند نشانی را درصورت نیاز بهروز کنند. ازآنجاییکه بیشتر نشانیهای برگرداندهشده توسط «تکمیل خودکار مکان» حاوی شمارههای فرعی مثل شماره آپارتمان، سوئیت، یا واحد نیستند، میتوانید تمرکز را به «خط نشانی ۲» منتقل کنید تا کاربر را تشویق کنید درصورت لزوم این اطلاعات را تکمیل کند.
ارائه تأیید تصویری نشانی
بهعنوان بخشی از ورودی نشانی، تأیید دیداری نشانی روی نقشه را به کاربران ارائه دهید. این کار به کاربران اطمینان بیشتری میدهد که نشانی درست است.
شکل زیر نقشهای را در زیر نشانی نشان میدهد که سنجاقی روی نشانی واردشده قرار دارد.

مثال زیر از مراحل اصلی برای افزودن نقشه در Android پیروی میکند. برای جزئیات بیشتر به اسناد مراجعه کنید.
- افزودن
SupportMapFragment(در این مورد، افزودن قطعه بهصورت پویا) - دریافت دستهای از قطعه و ثبت کردن تماس برگشتی
- سبکبندی و افزودن نشانگر به نقشه
- غیرفعال کردن کنترلهای نقشه
درحال افزودن SupportMapFragment
ابتدا، یک قطعه SupportMapFragment به
فایل XML چیدمان اضافه کنید.
<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.getLocation();
// 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را فراخوانی کنید.
برای مثال، اگر قطعه را بهصورت ایستا اضافه کرده باشید:
Kotlin
val mapFragment = supportFragmentManager .findFragmentById(R.id.map) as SupportMapFragment mapFragment.getMapAsync(this)
جاوا
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() .findFragmentById(R.id.map); mapFragment.getMapAsync(this);
درحال سبکدهی و افزودن نشانگر به نقشه
وقتی نقشه آماده شد، سبک را تنظیم کنید، دوربین را در مرکز قرار دهید، و در مختصات نشانی واردشده نشانگر اضافه کنید. کد زیر از سبکپردازی تعریفشده در شیء JSON استفاده میکند یا میتوانید بهجای آن شناسه نقشهای را بار کنید که با سبکپردازی نقشه بااستفاده از فضای ابری تعریف شده است.
@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)); }
غیرفعال کردن کنترلهای نقشه
برای ساده نگه داشتن نقشه با نشان دادن مکان بدون کنترلهای اضافی نقشه (مانند قطبنما، نوار ابزار، یا سایر ویژگیهای داخلی)، غیرفعال کردن کنترلهایی را که ضروری نمیدانید درنظر بگیرید. در Android، گزینه دیگر فعال کردن حالت ساده برای ارائه تعامل محدود است.