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

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

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

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

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

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

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

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

public class AddressAutoCompleteAdapter extends ArrayAdapter<Address> {

@Inject private LayoutInflater layoutInflater;

private ArrayList<Address> suggested;
private ArrayList<Address> 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<Address>();
    lastSuggested = new ArrayList<Address>();
    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<Address>();
                    filterResults.count = 0;
                }

                if ( filterResults.count > 0) {
                    lastSuggested = (ArrayList<Address>) 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<Address> 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<Address> local = new ArrayList<Address>();
    if (current != null) {
        local = (ArrayList<Address>) 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<Address> global = (ArrayList<Address>) 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 по умолчанию доступна не на всех устройствах). Он воспроизводит точно такое же странное поведение.


person a.b.d    schedule 06.04.2012    source источник
comment
Я хвалю вас за ваш уровень детализации   -  person Jason Robinson    schedule 07.04.2012
comment
можете ли вы помочь мне с этим stackoverflow.com/ вопросы/29046302/   -  person    schedule 16.03.2015


Ответы (3)


API-интерфейс геокодера не предназначен для предоставления рекомендаций по неполным строкам, таким как «10 rue Guy Ro» . Вы можете попробовать это в картах Google, введите «10 rue Guy Ro» и оставьте пробел. Вы не получите никаких предложений (карты Google используют геокодер), но когда вы удалите пробел, вы получите предложения по частичной строке (Карты Google используют частный API). Вы можете использовать API-интерфейс геокодера так же, как карты Google, получать предложение только после пробела ввода пользователя в строке, которую он печатает.

person Harish    schedule 09.10.2012

Попробуйте изменить эту часть кода:

if ( filterResults.count > 0) {
    lastSuggested = (ArrayList<Address>) filterResults.values;
}
lastInput = constraint.toString();

в это:

if ( filterResults.count > 0) {
    lastSuggested = (ArrayList<Address>) filterResults.values;
    lastInput = constraint.toString();
}
person Mohsin Naeem    schedule 26.06.2012

Я думаю, ваша проблема заключается в том, что вы устанавливаете lastSuggested на предлагаемое (через filterResults.values), а затем, на следующем проходе, вы обновляете предложенное --> lastSuggested будет установлено на то же значение, что и предложенное, поскольку Java передает переменные по ссылке, если только не примитивы .

Итак, вместо

если (filterResults.count > 0) { lastSuggested = (ArrayList) filterResults.values; }

Ты должен попытаться

if ( filterResults.count > 0) {
    lastSuggested.clear();
    ArrayList<Address> tempList = (ArrayList<Address>) filterResults.values;
    int i = 0;
    while (i < tempList.size()){
        lastSuggested.add(tempList.get(i));
        i += 1;
    }
}
person FreewheelNat    schedule 15.07.2012
comment
можете ли вы помочь мне с этим stackoverflow.com/ вопросы/29046302/ - person ; 16.03.2015