使用ArrayAdapter和Filter的Android自动完成文本视图

25

背景信息

我目前正在编写的应用程序涉及地点。创建地点的主要方法是输入地址。提供方便的方式对于用户体验非常重要。

当前解决方案

经过一段时间的调查研究,我得出结论,最好的方法是使用文本区域,并基于当前位置和用户输入提供自动完成功能(就像在 Google 地图应用程序中一样)。 由于 Google 地图 API 不提供此功能,因此我必须自己实现它。

当前实现

为了获得地址建议,我使用 Geocoder。 这些建议通过自定义 ArrayAdaptor 和自定义 Filter 提供给 AutoCompleteTextView

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

...

地址加载器(部分代码已被删除)
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中漏掉了什么...

问题

  • 地理编码器有时表现奇怪,也许有更好的方法来获取地址建议吗?
  • 你能建议一个比我描述的更好的解决方法吗?
  • 我的解决方法的实现有什么问题吗?

更新
为了兼容性考虑,我基于Google Geocode Http API实现了一个新的AddressLoader(因为默认的Android geocoder服务并不适用于所有设备)。它复制了完全相同的奇怪行为。


2
我赞赏你的细节水平。 - Jason Robinson
你能帮我解决这个问题吗?https://dev59.com/HIjca4cB1Zd3GeqP07xf - user4050065
3个回答

3

Geocoder API并不适用于像“10 rue Guy Ro”这样的部分字符串提供建议。您可以在Google地图中尝试输入“10 rue Guy Ro”并留下一个空格。您将不会得到任何建议(Google地图正在使用Geocoder),但是当您删除空格时,您将获得部分字符串的建议(Google地图正在使用私有API)。您可以像Google地图一样使用Geocoder API,在用户在他正在输入的字符串中输入空格后仅获取建议。


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

我认为你的问题在于你通过filterResults.values将lastSuggested设置为suggested,然后在下一次传递中更新了suggested。因为Java通过引用传递变量(除了原始类型),所以lastSuggested将被设置为与suggested相同的值。

因此,你应该尝试:

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

你能帮我解决这个问题吗?https://dev59.com/HIjca4cB1Zd3GeqP07xf - user4050065

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接