如何使用谷歌地图等自动建议实现地址搜索?



我正在尝试使用HERE库实现按地址的即时搜索,例如在Google或HERE地图中。 当用户依次键入几个单词时,即使用户错过逗号、点等,我也需要显示建议。虽然缺少标点符号的请求有效,但我无法弄清楚如何显示请求返回的建议。

我尝试使用AutoCompleteTextView,但它仅适用于第一个单词,当我再输入一个单词时,它停止工作。

我也尝试使用浮动搜索视图(arimorty(库,但它似乎不适用于androidx。我在焦点上调用了swapSuggestions(suggestions),但它在一个片段中只工作一次,尽管在活动中它工作正常。

按照建议,我通过应用自定义适配器解决了它。但这个适配器类似于谷歌的谷歌地方API版本。感谢您提供本指南

扩展功能

首先,您需要为类添加此扩展函数TextAutoSuggestionRequest以将回调转换为协程以像同步代码一样使用它

suspend fun TextAutoSuggestionRequest.await(): MutableList<AutoSuggest> {
return suspendCoroutine { continuation ->
execute { suggestions, errorCode ->
if (errorCode == ErrorCode.NONE) {
continuation.resume(suggestions)
} else {
continuation.resumeWithException(Exception("Error code: $errorCode"))
}
}
}
}

位置服务地址((

然后添加此转换器。我通过标准位置服务而不是此处地理编码器将地理坐标转换为文本,因为我不喜欢它返回地址的方式。

fun locationServiceAddress(context: Context, coordinate: GeoCoordinate): String {
val googleGeoCoder = Geocoder(context)
val addresses = googleGeoCoder.getFromLocation(coordinate.latitude, coordinate.longitude, 1)
return addresses[0].getAddressLine(0)
}

不过,为了简单起见,您可以将此处地理编码器与另一个扩展函数一起使用:

suspend fun ReverseGeocodeRequest.await(): String {
return suspendCoroutine { continuation ->
execute { location, errorCode ->
if (errorCode == ErrorCode.NONE) {
continuation.resume(location.address.text)
} else {
continuation.resumeWithException(Exception("Error code: $errorCode"))
}
}
}
}

建议适配器.kt

添加此适配器

请注意,如果您尝试在getFilter()中返回object : Filter() {},它将无法正常工作,因为请求将堆叠在该对象中而不是中断(重新创建类(

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.Filter
import android.widget.TextView
import androidx.lifecycle.MutableLiveData
import com.here.android.mpa.common.GeoCoordinate
import com.here.android.mpa.search.AutoSuggestPlace
import com.here.android.mpa.search.TextAutoSuggestionRequest
import kotlinx.coroutines.*
import timber.log.Timber
data class AddressItem(val coordinate: GeoCoordinate, val addressText: String)

class SuggestionsAdapter(context: Context, private val resourceId: Int, private val coordinate: GeoCoordinate) : ArrayAdapter<AddressItem>(context, resourceId, ArrayList<AddressItem>()) {
companion object {
private val _isFetching = MutableLiveData<Boolean>()
val isFetching: LiveData<Boolean>
get() = _isFetching
}
private var suggestions = ArrayList<AddressItem>()
private val customFilter = CustomFilter()
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
var view = convertView
if (view == null) {
view = LayoutInflater.from(parent.context!!).inflate(resourceId, parent, false)
}
val item = getItem(position)
if (item != null) {
val addressText = view!!.findViewById<TextView>(R.id.item_address_text)
addressText.text = item.addressText
}
return view!!
}
override fun getItem(position: Int): AddressItem? {
return try {
suggestions[position]
} catch (e: Exception) {
Timber.d("Item is NULL")
null
}
}
override fun getCount(): Int {
return suggestions.size
}
override fun getItemId(position: Int) = position.toLong()
override fun getFilter(): Filter = customFilter

inner class CustomFilter : Filter() {
override fun convertResultToString(resultValue: Any?): CharSequence {
if (resultValue != null) {
val address = resultValue as AddressItem
return address.addressText
}
return "" // if item is null
}
override fun performFiltering(prefix: CharSequence?): FilterResults {
val results = FilterResults()
val suggestions = ArrayList<AddressItem>()
if (prefix == null || prefix.isEmpty()) {
results.values = ArrayList<AddressItem>()
results.count = 0
} else {
val request = TextAutoSuggestionRequest(prefix.toString()).setSearchCenter(coordinate)
Timber.d("Start perform filtering")
runBlocking {
Timber.d("Blocking coroutine scope started")
withContext(Dispatchers.Main) {
isFetching.value = true
}
// Get places on IO thread
val requestResult = withContext(Dispatchers.IO) {
Timber.d("Getting places on IO thread")
request.await()
}
var i = 0
for (place in requestResult) {
i++
// If there are more than 10 suggestions break the loop because the more addresses found the more time need to process them to a string
if (i == 10) {
break
}
if (place is AutoSuggestPlace) {
val item = withContext(Dispatchers.IO) {
AddressItem(place.position, locationServiceAddress(context, place.position))
}
suggestions.add(item)
}
}
Timber.d("Blocking coroutine scope finished")
withContext(Dispatchers.Main) {
isFetching.value = false
}
results.apply {
values = suggestions
count = suggestions.size
}
Timber.d("Filtered results: ${suggestions}")
}
}
return results
}

override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
try {
if (results?.count!! > 0 && results?.values != null) {
suggestions = results.values as ArrayList<AddressItem>
notifyDataSetChanged()
} else {
suggestions = ArrayList()
notifyDataSetInvalidated()
}
} catch (e: Exception) {
Timber.d("Caught exception: ${e.message}")
}
}
}
}

并将其设置在这里SupporMapFragment.init()回调(如果错误.NONE(中,如下所示

val adapter = SuggestionsAdapter(context!!, R.layout.item_address, map.center)
binding.searchBox.setAdapter(adapter)

然后,您可以观察isFetching以反映加载状态

自动完成文本视图的适配器仅筛选以用户输入的输入开头的元素。您需要修改 ArrayAdapter 类才能使其正常工作。

public class AutoSuggestAdapter extends ArrayAdapter {
private Context context;
private int resource;
private List<String> tempItems;
private List<String> suggestions;

public AutoSuggestAdapter(Context context, int resource, int item, List<String> items) {
super(context, resource, item, items);
this.context = context;
this.resource = resource;
tempItems = new ArrayList<>(items);
suggestions = new ArrayList<>();
}
@Override
public Filter getFilter() {
return nameFilter;
}
Filter nameFilter = new Filter() {
@Override
public CharSequence convertResultToString(Object resultValue) {
String str = (String) resultValue;
return str;
}
@Override
protected FilterResults performFiltering(CharSequence constraint) {
if (constraint != null) {
suggestions.clear();
for (String names : tempItems) {
if (names.toLowerCase().contains(constraint.toString().toLowerCase())) {
suggestions.add(names);
}
}
FilterResults filterResults = new FilterResults();
filterResults.values = suggestions;
filterResults.count = suggestions.size();
return filterResults;
} else {
return new FilterResults();
}
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
List<String> filterList = (ArrayList<String>) results.values;
if (results != null && results.count > 0) {
clear();
for (String item : filterList) {
add(item);
notifyDataSetChanged();
}
}
}
};

请注意,重要的一行在这里:

names.toLowerCase().contains(constraint.toString().toLowerCase())

这样,它将搜索包含输入中的字符串的字符串。默认情况下,它是 startsWith(( intead of contains((

最新更新