首页 文章

Android AutocompleteTextView使用ArrayAdapter和Filter

提问于
浏览
25

Backgroud信息

我目前正在撰写的应用程序处理地点 . 创建场所的主要方法是输入地址 . 提供便捷的方法对于UX来说非常重要 .

当前的解决方案

在调查问题一段时间后,我得出的结论是,最好的方法是根据当前位置和用户输入自动完成的文本区域(如Google Map应用程序中) . 由于Google Map API不提供此功能,因此我必须自行实施 .

当前实施

要获得地址建议,我使用Geocoder . 这些建议由自定义ArrayAdaptor通过自定义Filter提供给AutoCompleteTextView .

The ArrayAdapter (Some code has been removed)

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

...

The AddressLoader (some code has been removed)

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"和"10 rue Guy Ropart"的建议,但没有对"10 rue Guy Ro"的建议......

可能的解决方法

我想象一下这个问题可能的解决方法并尝试实现它 . 如果在输入更长的地址后没有AdressLoader建议的地址,那么我们的想法是保留先前的建议 . 在输入为"10 rue Guy Ro"时,请保留"10 rue Guy"建议 .

不幸的是我的代码不起作用 . 当我处于这种情况时,达到了良好的代码部分:

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

并且 performFiltering 返回的FilterResult充满了好的建议,但它没有出现在视图上 . 也许我错过了 publishResults 中的一些东西......

问题

  • Geocoder有时会出现一种奇怪的行为,也许有更好的方法来获取地址建议?

  • 你能告诉我一个比我描述的更好的解决方法吗?

  • 我的解决方法的实施有什么问题?

Update
出于兼容性考虑,我实现了一个基于Google Geocode Http API的新AddressLoader(因为默认的Android地理编码器服务并非在所有设备上都可用) . 它重现了完全相同的奇怪行为 .

3 回答

  • 0

    尝试更改这部分代码:

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

    进入这个:

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

    Geocoder api并非旨在提供有关“10 rue Guy Ro”等部分字符串的建议 . 您可以在Google Map 中尝试此操作,输入“10 rue Guy Ro”并留出空格 . 您不会得到任何建议(谷歌 Map 使用地理编码器),但当您删除空格时,您将获得部分字符串的建议(谷歌 Map 使用私有API) . 您可以像使用Google Map 一样使用Geocoder api,只有在他输入的字符串中的用户输入空格后才能获取建议 .

  • 3

    我认为您的问题是您将lastSuggested设置为建议(通过filterResults.values)然后,在下一次传递中,您正在更新建议 - > lastSuggested将设置为建议的相同值,因为Java通过引用传递变量,除非基元 .

    所以,而不是

    if(filterResults.count> 0)

    你应该试试

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

相关问题