使用Adapter结合Filter做过滤的时候,在分别继承ArrayAdapter和BaseAdapter时遇到“想修改数据而错误修改了引用”的经典问题。记录遇到的详细情况以免再犯。

继承ArrayAdapter:

privateclassMyAdapterextendsArrayAdapter<String>{privateContextmContext;privateintmResource;privateList<String>mData;privateMyFiltermFilter;publicMyAdapter(@NonNullContextcontext,@LayoutResintresource,@NonNullList<String>objects){//这里会将object赋值给父类的mObjects成员变量,问题的所在super(context,resource,objects);this.mContext=context;this.mResource=resource;this.mData=objects;}@OverridepublicintgetCount(){returnmData.size();}@Nullable@OverridepublicStringgetItem(intposition){returnmData.get(position);}@OverridepubliclonggetItemId(intposition){returnposition;}@NonNull@OverridepublicViewgetView(intposition,@NullableViewconvertView,@NonNullViewGroupparent){Viewview;if(convertView==null){view=LayoutInflater.from(mContext).inflate(mResource,parent,false);}else{view=convertView;}TextViewtext=(TextView)view.findViewById(android.R.id.text1);text.setText(mData.get(position));returnview;}@NonNull@OverridepublicFiltergetFilter(){if(mFilter==null){mFilter=newMyFilter();}returnmFilter;}privateclassMyFilterextendsFilter{@OverrideprotectedFilterResultsperformFiltering(CharSequenceconstraint){StringfilterString=constraint.toString().toLowerCase();FilterResultsresults=newFilterResults();//为null,表示没有赋值过,这里的逻辑是mOriginalValues保存原始数据,而mData保存过滤后的数据if(mOriginalValues==null){mOriginalValues=newArrayList<>(mData);}if(TextUtils.isEmpty(filterString)){results.values=mOriginalValues;results.count=mOriginalValues.size();}else{List<String>values=newArrayList<>(mOriginalValues);List<String>newValues=newArrayList<>();for(inti=0;i<values.size();i++){Stringvalue=values.get(i);if(value.contains(filterString)){newValues.add(value);}}results.values=newValues;results.count=newValues.size();}returnresults;}@OverrideprotectedvoidpublishResults(CharSequenceconstraint,FilterResultsresults){//mData.clear();//mData.addAll((List<String>)results.values);//noinspectionuncheckedmData=(List<String>)results.values;if(results.count>0){notifyDataSetChanged();}else{notifyDataSetInvalidated();}}}}

继承BaseAdapter:

privateclassNewAdapterextendsBaseAdapter{privateContextmContext;privateintmResource;privateList<String>mList;privateArrayFiltermFilter;publicNewAdapter(Contextcontext,intresource,List<String>list){this.mContext=context;this.mList=list;this.mResource=resource;}@OverridepublicintgetCount(){returnmList.size();}@OverridepublicObjectgetItem(intposition){returnmList.get(position);}@OverridepubliclonggetItemId(intposition){returnposition;}@OverridepublicViewgetView(intposition,ViewconvertView,ViewGroupparent){Viewview;if(convertView==null){view=LayoutInflater.from(mContext).inflate(mResource,null,false);}else{view=convertView;}((TextView)view).setText((String)getItem(position));returnview;}publicArrayFiltergetFilter(){if(mFilter==null){mFilter=newArrayFilter();}returnmFilter;}privateclassArrayFilterextendsFilter{@OverrideprotectedFilterResultsperformFiltering(CharSequenceconstraint){FilterResultsresults=newFilterResults();StringfilterString=constraint.toString().toLowerCase();if(mOriginalValues==null){mOriginalValues=newArrayList<>(mList);}if(TextUtils.isEmpty(filterString)){results.values=newArrayList<>(mOriginalValues);results.count=mOriginalValues.size();}else{List<String>values=newArrayList<>(mOriginalValues);List<String>newValues=newArrayList<>();for(inti=0;i<values.size();i++){Stringvalue=values.get(i).toLowerCase();if(value.contains(filterString)){newValues.add(value);}}results.values=newValues;results.count=newValues.size();}returnresults;}@OverrideprotectedvoidpublishResults(CharSequenceconstraint,FilterResultsresults){//noinspectionuncheckedmList=(List<String>)results.values;if(results.count>0){notifyDataSetChanged();}else{notifyDataSetInvalidated();}}}}

问题描述:

继承BaseAdapter的时候必须重写getItem、getCount、getItemId等几个方法,而继承ArrayAdapter的时候只必须有父类相应参数列表的构造方法。我一开始习惯使用BaseAdapter,后来发现直接继承ArrayAdapter代码更简洁。然后在结合使用Filter的时候出现了问题。

问题:修改数据集合后notifyDataSetChanged没有改变。

例如(参照示例片段):

1、重写ArrayAdapter的时候必须实现父类的构造方法(问题所在)。

2、执行new MyAdapter(this,resourceId, datas);

3、那么datas会最终赋值给ArrayAdapter的mObjects成员变量

4、此时MyAdapter中的mData和mObjects指向同一块数据

5、如果没有重写getCount,则getCount=mObjects.size();

6、如果重写了getCount(如下),则getCount=mData.size();

7、在使用Filter之后,修改mData指向过滤后的数据,然而mObjects并没有改变

8、可是这里决定ListView数据集的是mObjects引用,并没有相应更新

解决方法:

1、修改数据引用变量mData的同时,修改mObjects。

2、只使用mData,不使用mObjects(倒不如直接继承BaseAdapter逻辑更清晰)

2、直接修改指向的数据集。mData.clear();mData.addAll()。


示例代码片段:

//重写getCount、getItem等方法,使用mData引用publicintgetCount(){returnmData.size();}publicMyAdapter(@NonNullContextcontext,@LayoutResintresource,@NonNullList<String>objects){//这个父类构造方法会将objects保存到mObjects,作为数据集的真正引用super(context,resource,objects);this.mData=objects;}publicArrayAdapter(@NonNullContextcontext,@LayoutResintresource,@IdResinttextViewResourceId,@NonNullList<T>objects){mContext=context;mInflater=LayoutInflater.from(context);mResource=mDropDownResource=resource;mObjects=objects;//如果要修改数据引用,那么应该修改mObjects,而不是mDatamFieldId=textViewResourceId;}//直接修改数据的方式而不是修改引用变量:@OverrideprotectedvoidpublishResults(CharSequenceconstraint,FilterResultsresults){//noinspectionunchecked//mList=(List<String>)results.values;mData.clear();mData.addAll((List<String>)results.values);//直接修改引用的数据,而非引用本身if(results.count>0){notifyDataSetChanged();}else{notifyDataSetInvalidated();}}

总结:

无搜索功能的ListView

1、继承ArrayAdapter代码更简洁

具备搜索功能的ListView

1、继承BaseAdapter

2、继承ArrayAdapter并重写getItem、getCount等方法使用本类的引用变量

3、继承ArrayAdapter,不改引用变量,直接修改数据集