我有一个AutoCompleteTextView,其中包含使用ArrayList<Product>
的自定义ArrayAdapter。
我得出的结论是,AutoCompleteTextView的自定义ArrayAdapter必须implements Filterable
,您必须进行自己的过滤。
从this SO-question & accepted answer和this example,我制作了以下ArrayAdapter:
public class AutoCompleteAdapter extends ArrayAdapter<Product> implements Filterable
{
// Logcat tag
private static final String TAG = "AutoCompleteAdapter";
// The OrderedProductItem we need to get the Filtered ProductNames
OrderedProductItem orderedProductItem;
private Context context;
private ArrayList<Product> productsShown, productsAll;
// Default Constructor for an ArrayAdapter
public AutoCompleteAdapter(Context c, int layoutId, ArrayList<Product> objects, OrderedProductItem opi){
// Though we don't use the Layout-ResourceID , we still need it for the super
super(c, layoutId, objects);
L.Log(TAG, "AutoCompleteAdapter Constructor", LogType.VERBOSE);
// ArrayAdapter's setNotifyOnChange is true by default,
// but I set it nonetheless, just in case
setNotifyOnChange(true);
context = c;
replaceList(objects, true);
orderedProductItem = opi;
}
// Setup the ListItem's UI-elements
@Override
public View getView(int position, View convertView, ViewGroup parent){
return createTextViewAsItem(position);
}
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent){
return createTextViewAsItem(position);
}
// To prevent repetition, we have this private method
private TextView createTextViewAsItem(int position){
TextView label = new TextView(context);
String name = "";
if(productsShown != null && productsShown.size() > 0 && position >= 0 && position < productsShown.size() - 1)
name = productsShown.get(position).getName();
label.setText(name);
return label;
}
// Replace the List
// When the boolean is set, we replace this ArrayAdapter's List entirely,
// instead of just the filtering
@SuppressWarnings("unchecked")
public void replaceList(ArrayList<Product> p, boolean replaceInitialList){
if(p != null && p.size() > 0){
productsShown = p;
if(replaceInitialList)
productsAll = (ArrayList<Product>)productsShown.clone();
notifyDataSetChanged();
}
}
// Since we are using an AutoCompleteTextView, the Filtering has been reset and we need to apply this ourselves..
Filter filter = new Filter(){
@Override
public String convertResultToString(Object resultValue){
return ((Product)resultValue).getName();
}
@Override
protected FilterResults performFiltering(CharSequence constraint){
FilterResults filterResults = new FilterResults();
if(productsAll != null){
// If no constraint is given, return the whole list
if(constraint == null){
filterResults.values = productsAll;
filterResults.count = productsAll.size();
}
else if(V.notNull(constraint.toString(), true)){
L.Log(TAG, "performFiltering: " + constraint.toString(), LogType.VERBOSE);
ArrayList<Product> suggestions = new ArrayList<Product>();
if(p.size() > 0)
for(Product p : productsAll)
if(p.getName().toLowerCase(Locale.ENGLISH).contains(constraint.toString().toLowerCase(Locale.ENGLISH)))
suggestions.add(p);
filterResults.values = suggestions;
filterResults.count = suggestions.size();
}
}
return filterResults;
}
@SuppressWarnings("unchecked")
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
if(results != null && results.count > 0)
replaceList((ArrayList<Product>)results.values, false);
}
};
@Override
public Filter getFilter(){
return filter;
}
}
一切都很完美。但是,由于我有大约1250个产品的列表,并且每次用户在AutoCompleteTextView中更改其输入时都会循环,包括创建两个新的实例化(FilterResults和ArrayList),我想知道是否有更好的解决方案这不需要循环每个用户输入的所有内容都会改变。
如果还没有我保留这个。我只是想知道,因为AutoCompleteTextView包含大约1250个对象,具有自定义ArrayAdapter(包括自定义过滤)和自定义TextWatcher,所以它对性能没有好处。特别是因为这个AutoCompleteTextView用在ListView的项目中。这意味着每个项目都有一个AutoCompleteTextView(可能从5到50,平均大约15)。
答案 0 :(得分:2)
这已经相当晚了,但我认为我会考虑你的问题...主要是因为你的实施中出现了相当恐慌的事情。要回答您的直接问题,在过滤时,您可以轻松避免完全ArrayList
次迭代。如果您需要更快的内容,则需要考虑将数据预处理为具有更快搜索时间的内容。 AutoComplete Algorithm?
我有一个通用的经验法则来定制ArrayAdapter
过滤逻辑。不要这样做。每当遇到这种情况时,正确的解决方案是推送自己的适配器解决方案(使用BaseAdapter
)...或找到允许您使用的3rd party solution。部分问题是ArrayAdapter
内部有自己的两个过滤列表,它有自己的内部同步锁。你的AutoCompleteAdapter
暴露了大量的mutator,所有mutator都在你无法同步的对象上同步。这意味着如果在进行过滤时适配器发生变异,则会冒并发问题。
与您的代码一致,ArrayAdapter
与您的productsAll
列表相关联。任何突变,访问器,方法等都将始终引用该列表。起初,我很惊讶您的解决方案有效!然后我意识到你没有使用getItem
作为常态。我想你完全忽略了所有其他ArrayAdapter
方法,否则你会看到相当奇怪的行为。如果是这样的话,那么ArrayAdapter
并没有真正为你做任何事情而你正在装载这个庞大的课程。使用BaseAdapter
将其切换出来是微不足道的。
事实上,我很惊讶你没有看到其他奇怪的问题。例如,无论您的过滤列表显示什么,您的适配器始终注册productsAll
列表计数而不是productsShown
计数。这可能就是为什么你有所有这些索引超出界限检查?通常不需要。
我也很惊讶您的过滤操作会更新列表,因为您在完成后无法调用notifyDataSetChanged
。
下一个大问题,你永远不应该嵌套适配器。我通常提倡这个,因为人们嵌入了ListViews
......这是另一个不可靠的。这是我第一次听说有关AutoCompleteTextView
的嵌套。情况略有不同,但我仍然说这是一个坏主意。为什么?无法保证为给定位置调用getView
多少次。它可以称之为...它可以称之为4次......或更多。因此,想象每个项目重新创建适配器4次。即使一次只显示10个项目,您也会查看自定义适配器的40个实例!我当然希望你找到一种方法来回收这些适配器来降低这个数字。
但是考虑到你没有使用ViewHolder
我假设你甚至不知道回收行为?任何适配器都必须ViewHolder
。它单一的轻松将提供巨大的表现夸耀。现在,您正在使用每个getView
调用创建一个新视图,并忽略所提供的任何回收视图。有一百万个在线示例显示和解释ViewHolder
。这是一个link。
旁注,ArrayAdapter
已实施Filterable
。不需要在自定义适配器中重新添加工具。
总结一下:
BaseAdapter
而不是AutoCompleteTextViews
内的多个ListView
。必须在Google I / O上观看有关ListView和适配器的video。
以下是关于ArrayAdapter
的更多readings。