Подтвердить что ты не робот

Android AutocompleteTextView с использованием ArrayAdapter и фильтра

Информация о Backgroud

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

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

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

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

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

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, возвращаемый выполнитьFiltering, заполнен хорошими предложениями, но он не отображается в представлении. Возможно, я пропустил что-то в publishResults...

Вопросы

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

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

4b9b3361

Ответ 1

Geocoder api не предназначен для предложений о частичных строках типа "10 rue Guy Ro". Вы можете попробовать это на картах Google, ввести "10 rue Guy Ro" и оставить пространство. Вы не получите никаких предложений (карты Google используют Geocoder), но при удалении пространства вы получите предложения по частичной строке (Google Maps использует частный API). Вы можете использовать Geocoder api так же, как карты Google, используйте предложение, только после ввода пользовательского пространства в строке, которую он печатает.

Ответ 2

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

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

в это:

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

Ответ 3

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

Итак, вместо

if (filterResults.count > 0) {                     lastSposed = (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;
    }
}