Android AutocompleteTextView с использованием ArrayAdapter и Filter

Справочная информация

Приложение, которое я сейчас пишу, имеет дело с местами. Основным способом создания места является ввод адреса. Предоставление удобного способа сделать это очень важно для UX.

Текущее решение

После изучения проблемы некоторое время я пришел к выводу, что лучший способ сделать это — текстовая область с автозаполнением на основе текущего местоположения и пользовательского ввода (как в приложении Google Map). Поскольку эта функция, к сожалению, не предоставляется API Карт Google, я должен реализовать ее самостоятельно.

Текущая реализация

Чтобы получить предложения адресов, я использую Geocoder. Эти предложения предоставляются AutoCompleteTextViewпользовательским ArrayAdaptorпосредством пользовательского Filter.

ArrayAdapter (часть кода была удалена)

public class AddressAutoCompleteAdapter extends ArrayAdapter
{ @Inject private LayoutInflater layoutInflater; private ArrayList
suggested; private ArrayList
lastSuggested; private String lastInput = null; private AddressLoader addrLoader; public AddressAutoCompleteAdapter(Activity activity, AddressLoaderFactory addrLoaderFact, int maxSuggestionsCount) { super(activity,R.layout.autocomplete_address_item); suggested = new ArrayList
(); lastSuggested = new ArrayList
(); addrLoader = addrLoaderFact.create(10); } ... @Override public Filter getFilter() { Filter myFilter = new Filter() { ... @SuppressWarnings("unchecked") @Override protected FilterResults performFiltering(CharSequence constraint) { Log.d("MyPlaces","performFiltring call"); FilterResults filterResults = new FilterResults(); boolean debug = false; if(constraint != null) { // Load address try { suggested = addrLoader.load(constraint.toString()); } catch(IOException e) { e.printStackTrace(); Log.d("MyPlaces","No data conection avilable"); /* ToDo : put something useful here */ } // If the address loader returns some results if (suggested.size() > 0) { filterResults.values = suggested; filterResults.count = suggested.size(); // If there are no result with the given input we check last input and result. // If the new input is more accurate than the previous, display result for the // previous one. } else if (constraint.toString().contains(lastInput)) { debug = true; Log.d("MyPlaces","Keep last suggestion : " + lastSuggested.get(0).toString()); filterResults.values = lastSuggested; filterResults.count = lastSuggested.size(); } else { filterResults.values = new ArrayList
(); filterResults.count = 0; } if ( filterResults.count > 0) { lastSuggested = (ArrayList
) filterResults.values; } lastInput = constraint.toString(); } if (debug) { Log.d("MyPlaces","filter result count :" + filterResults.count); } return filterResults; } @Override protected void publishResults(CharSequence contraint, FilterResults results) { AddressAutoCompleteAdapter.this.notifyDataSetChanged(); } }; return myFilter; } ...

AddressLoader (часть кода была удалена)

public class GeocoderAddressLoader implements AddressLoader {

private Application app;
private int maxSuggestionsCount;
private LocationManager locationManager;

@Inject
public GeocoderAddressLoader(
        Application app,
        LocationManager locationManager,
        @Assisted int maxSuggestionsCount){
    this.maxSuggestionsCount = maxSuggestionsCount;
    this.app = app;
    this.locationManager = locationManager;
}

/**
 * Take a string representing an address or a place and return a list of corresponding
 * addresses
 * @param addrString A String representing an address or place
 * @return List of Address corresponding to the given address description
 * @throws IOException If Geocoder API cannot be called
 */
public ArrayList
load(String addrString) throws IOException { //Try to filter with the current location Location current = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); Geocoder geocoder = new Geocoder(app,Locale.getDefault()); ArrayList
local = new ArrayList
(); if (current != null) { local = (ArrayList
) geocoder.getFromLocationName( addrString, maxSuggestionsCount, current.getLatitude()-0.1, current.getLongitude()-0.1, current.getLatitude()+0.1, current.getLongitude()+0.1); } if (local.size() < maxSuggestionsCount) { ArrayList
global = (ArrayList
) geocoder.getFromLocationName( addrString, maxSuggestionsCount - local.size()); for (Address globalAddr : global) { if (!containsAddress(local,globalAddr)) local.add(globalAddr); } } return local; } ...

Проблемы

Эта реализация работает, но не идеальна.

AddressLoader имеет странное поведение при вводе некоторых данных.
Например, если пользователь хочет ввести этот адрес

10 rue Guy Ropartz 54600 Villers-les-Nancy  

, когда он введет:

10 rue Guy

, есть некоторые предложения, но нет желаемого адреса.
Если он продолжит ввод текста при достижении этого ввода: предложения

10 rue Guy Ro

исчезнут.Желаемый адрес появляется, наконец, когда ввод

10 rue Guy Ropart

. Я думаю, что это беспокоит пользователя. странно, что нет предложений для "10 rue Guy Ro", а есть предложения для "10 rue Guy"и "10 rue Guy Ropart". ..

Возможный обходной путь

Я представляю возможный обходной путь для этой проблемы и пытаюсь реализовать его. Идея состоит в том, чтобы сохранить предыдущее предложение, если после ввода более длинного адреса нет адресов, предложенных AdressLoader. В нашем примере сохраняйте предложения "10 rue Guy"при вводе "10 rue Guy Ro".

К сожалению, мой код не работает. Когда я нахожусь в этой ситуации, достигается хорошая часть кода:

else if (constraint.toString().contains(lastInput)) {
                debug = true;
                Log.d("MyPlaces","Keep last suggestion : " + lastSuggested.get(0).toString());
                filterResults.values = lastSuggested;
                filterResults.count = lastSuggested.size();
            }

и FilterResult, возвращаемый PerformFiltering, заполнен хорошими предложениями, но не отображается в представлении. Может быть, я что-то упустил в publishResults...

Вопросы

  • Геокодер иногда ведет себя странно, может быть, есть лучший способ получить предложения адресов?
  • Можете ли вы предложить мне лучший обходной путь, чем тот, который я описал?
  • В чем проблема с реализацией моего обходного пути?

Обновление
Из соображений совместимости я реализовал новый AddressLoader на основе Google Geocode Http API(поскольку служба геокодирования Android по умолчанию доступна не на всех устройствах). Он воспроизводит точно такое же странное поведение.

25
задан Michael Currie 31 August 2015 в 23:39
поделиться