【问题标题】:How to implement address search with autosuggestions like google maps?如何使用谷歌地图等自动建议实现地址搜索?
【发布时间】:2019-12-30 09:44:59
【问题描述】:

我正在尝试使用 HERE 库在 Google 或 HERE 地图中按地址实现即时搜索。 我需要在用户按顺序键入多个单词时显示建议,即使用户错过了逗号、点等。虽然缺少标点符号的请求有效,但我不知道如何显示请求返回的建议。

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

我也尝试使用floatingsearchview (arimorty) library,但它似乎不适用于androidx。我在焦点上调用了swapSuggestions(suggestions),但它在片段中只工作一次,但在活动中它工作正常。

【问题讨论】:

    标签: android kotlin autocomplete here-api autocompletetextview


    【解决方案1】:

    按照建议,我通过应用自定义适配器解决了这个问题。但是这个适配器类似于 Google 的 Google Place API 版本。感谢this guide

    扩展功能

    首先你需要为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"))
                }
            }
        }
    }
    

    locationServicesAddress()

    然后添加这个转换器。我通过标准定位服务而不是 Here GeoCoder 将地理坐标转换为文本,因为我不喜欢它返回地址的方式。

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

    虽然为了简单起见,您可以将 Here GeoCoder 与另一个扩展功能一起使用:

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

    SuggestionsAdapter.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() 回调(如果 Error.NONE)像这样

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

    然后你可以观察isFetching来反映加载状态

    【讨论】:

      【解决方案2】:

      AutoCompleteTextView 的适配器仅过滤以用户输入的输入开头的元素。您需要修改 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() 而不是 contains()

      【讨论】:

      • 问题是请求以“地区、城市、街道、房屋”的格式返回字符串地址。我的意思是,它包括标点符号,但我需要忽略它。我应该每次都修改源字符串以进行匹配吗?
      • 是的,您可以做的是在将字符串添加到适配器之前从字符串中删除标点符号。
      • 还有另一种方法可以做到这一点,即构建另一个带有标点符号的数组,然后在 for 循环的 publishResults() 中获取项目的索引,然后搜索带有标点符号的“项目”第二个数组并修改行:add(item) to add(string with punctuation),这样用户搜索没有标点,但建议是标点的
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-10-22
      • 2016-02-16
      • 2014-08-28
      • 1970-01-01
      • 2012-11-10
      • 1970-01-01
      • 2021-04-12
      相关资源
      最近更新 更多