I have a class class CustomAdapter extends ArrayAdapter<CustomListItem>
, where CustomListItem implements Parcelable
, and has 3 String variables (String a, b, c;
)
Everything is working as intended when loading the ListView. However, now I want to use my SearchView to display only list elements that contain the text that the user inputs. I want the Filter from CustomAdapter to look at a, b, and c for that text, and display any list item that contain that text.
So, for example, if the user types "ar", and a b c are "Rome", "Male", "Arnold"
, no matter which content is in what variable, since one of them has "Ar" (I don't want it to be case sensitive) I want that item to be displayed on the list.
This filter business looks very confusing to me at the moment, and there seem to be lots of answers for custom Filters in stackoverflow, but I can't find one that has the kind of behavior I'm describing. So far this is what I have:
public class CustomAdapter extends ArrayAdapter<CustomListItem> {
public CustomAdapter(Context context, int textViewResourceId) {
super(context, textViewResourceId);
}
public CustomAdapter(Context context, int resource, List<CustomListItem> items) {
super(context, resource, items);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi;
vi = LayoutInflater.from(getContext());
v = vi.inflate(R.layout.row_layout, null);
}
CustomListItem s = getItem(position);
if (s != null) {
TextView a = (TextView) v.findViewById(R.id.a);
TextView b = (TextView) v.findViewById(R.id.b);
TextView c = (TextView) v.findViewById(R.id.c);
if (a != null) {
a.setText(s.getA());
}
if (b != null) {
b.setText(s.getB());
}
if (c != null) {
c.setText(s.getC());
}
}
return v;
}
@Override
public Filter getFilter() {
return new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults result = new FilterResults();
List<CustomListItem> list = new ArrayList<>();
int max = getCount();
for (int cont = 0; cont < max; cont++) {
if (constraint != null) {
CustomListItem item = getItem(cont);
boolean contains =
item.getA().toLowerCase().contains(constraint) ||
item.getB().toLowerCase().contains(constraint) ||
item.getC().toLowerCase().contains(constraint);
if (contains) {
list.add(getItem(cont));
}
} else {
list.add(getItem(cont));
}
}
result.values = list;
result.count = list.size();
return result;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
if (results.count > 0) {
notifyDataSetChanged();
} else {
notifyDataSetInvalidated();
}
}
};
}
}
But this doesn't work, probably because I don't know what I'm doing. This is CustomListItem:
public class CustomListItem implements Parcelable {
private String a, b, c;
public CustomListItem(String a, String b, String c) {
this.a = a;
this.b = b;
this.c = c;
}
private CustomListItem(Parcel in) {
a = in.readString();
b = in.readString();
c = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(a);
dest.writeString(b);
dest.writeString(c);
}
public String getA() {
return a;
}
public String getB() {
return b;
}
public String getC() {
return c;
}
@Override
public int describeContents(){
return 0;
}
public static final Parcelable.Creator<CustomListItem> CREATOR
= new Parcelable.Creator<CustomListItem>() {
public CustomListItem createFromParcel(Parcel in) {
return new CustomListItem(in);
}
public CustomListItem[] newArray(int size) {
return new CustomListItem[size];
}
};
}
And I add the filter like this, on my AppCompatActivity class:
SearchView searchView = (SearchView) findViewById(R.id.searchView);
final CustomAdapter adapter = new CustomAdapter(getApplicationContext(), R.layout.row_layout, item_list); //item_list is my list of custom items, defined elsewhere
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String text) {
adapter.getFilter().filter(text);
return true;
}
@Override
public boolean onQueryTextChange(String text) {
adapter.getFilter().filter(text);
return true;
}
});
I believe that the only thing wrong with my code is the getFilter()
method, so what I'm looking for as an answer is a correct and clean way of achieving what I just said. I'll also be happy with an explanation on what I'm doing wrong, and some examples. Thanks for your time!
SOLUTION:
Based on Submersed's answer I've made the necessary changes to make the code work. As expected, the problem was limited to the filter. However, since I changed other things in the CustomAdapter
class in order to achieve my solution, here's the entire fixed class:
public class CustomAdapter extends ArrayAdapter<CustomListItem> {
private final List<CustomListItem> mList;
public CustomAdapter(Context context, int resource, List<CustomListItem> items) {
super(context, resource, items);
mList = new ArrayList<>(items);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi;
vi = LayoutInflater.from(getContext());
v = vi.inflate(R.layout.row_layout, null);
}
CustomListItem s = getItem(position);
if (s != null) {
TextView a = (TextView) v.findViewById(R.id.a);
TextView b = (TextView) v.findViewById(R.id.b);
TextView c = (TextView) v.findViewById(R.id.c);
if (a != null) {
a.setText(s.getA());
}
if (b != null) {
b.setText(s.getB());
}
if (c != null) {
c.setText(s.getC());
}
}
return v;
}
@Override
public Filter getFilter() {
return new Filter() {
@Override
protected FilterResults performFiltering(CharSequence charSequence) {
FilterResults result = new FilterResults();
String constraint = charSequence.toString().toLowerCase();
if (constraint == null || constraint.isEmpty()) {
result.values = mList;
result.count = mList.size();
} else {
List<CustomListItem> list = new ArrayList<>();
int max = mList.size();
for (int cont = 0; cont < max; cont++) {
CustomListItem item = mList.get(cont);
boolean contains =
item.getA().toLowerCase().contains(constraint) ||
item.getB().toLowerCase().contains(constraint) ||
item.getC().toLowerCase().contains(constraint);
if (contains) {
list.add(mList.get(cont));
}
}
result.values = list;
result.count = list.size();
}
return result;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
clear();
addAll((ArrayList<CustomListItem>) results.values);
notifyDataSetChanged();
}
};
}
}
In your publishResults
method:
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
if (results.count > 0) {
notifyDataSetChanged();
} else {
notifyDataSetInvalidated();
}
}
You're not setting the filtered results on your adapter as the new dataset to display before invaliding the dataset. Also, you should be keeping a copy of your original values as well, so if they empty out the query you can retain and reset the original results.
Edit for Framework example code: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/widget/ArrayAdapter.java#ArrayAdapter.ArrayFilter