Filterable Complex ListView
The current implementation of my listview displays data in different categories/sections, depending on a variable of the objects to be displayed. Just to show an example, if the dataset is e.g. {cat,one,red,five,orange,dog}, the resulting listview will be {animals: cat,dog}, {colors: red, orange}, {numbers: one, five}. For this I'm using a variation of a 'SectionerAdapter' I found online, which in my case uses a custom ArrayAdapter<> for each of the sections. The sections provided by this code look like the ones in the Settings app of any Android device.
Now, I'm trying to filter those results. If typing 'O', the list will end up being: {animals: empty},{colors:orange},{numbers:one}. The problem is I don't get it to work with the whole list, but only with one of the sections. That's why I'm trying to use another approach for the whole listing: ExpandableListView.
Does anybody know if filtering within an 开发者_运维知识库ExpandableListView is possible/easy? Do you guys have any example I could use to get an idea of how to do it?
Thanks!
I've done something similar for a project of mine. I'm not at home so unfortunately I don't have a code sample handle. Here's how I accomplished this -
I use a custom ArrayAdapter<T> as a base for my SectionArrayAdapter<SectionedListItem>. The SectionedListItem is used as a base class for every item that I may want to show in the SectionedList. The SectionedListItem defines a couple properties:
boolean isNewSection;
String sectionLabel;
This could also be an interface, doesn't need to be a class. Having it as a class just made sense for my implementation.
I then take the list of items I want to show in a sectioned list and do some custom sorting on them BEFORE I apply them to the adapter. As I sort the list I add in empty SectionedListItems to the indexes where a new sectioned should start, setting the isNewSection property to true. When the SectionedArrayAdapter does it's rendering, it looks to see if the isNewSection property is true. If it's true I render out the section header instead of the default list item.
This will give you a single list to work with during your filtering, not a bunch of different lists. It does pose it's own challenges, however - You need to re-sort your list after filtering and/or you need to ignore the SectionedListItems which are only used to define a new section during your filtering.
I'm not claiming this is the best approach, it is just the approach I came up with :)
Please note that this code was written as part of a prototype so it's not very clean or slick :) It should, however, get you going in the right direction. I should also note that the InventoryListItem extends my SectionedListItem class, which contains the properties outlined above.
/* -------------------------
* Class: InventoryAdapter
* ------------------------- */
private final class InventoryAdapter extends ArrayAdapter<InventoryListItem> implements Filterable {
/* -------------------------
* Fields
* ------------------------- */
private ArrayList<InventoryListItem> items;
private ArrayList<InventoryListItem> staticItems;
private int resource;
private InventoryFilter filter = null;
/* -------------------------
* Constructor
* ------------------------- */
public InventoryAdapter(Context context, int textViewResourceId, ArrayList<InventoryListItem> objects) {
super(context, textViewResourceId, objects);
items = objects;
resource = textViewResourceId;
staticItems = items;
}
/* -------------------------
* Private Methods
* ------------------------- */
private void addCategorySpan(SpannableString span, int startIndex, int endIndex, final String category) {
span.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
String categoryFilter = "cat://" + category;
filterList(categoryFilter, true);
}
}, startIndex, endIndex, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
}
/* -------------------------
* Public Methods
* ------------------------- */
// Properties
@Override
public int getCount() {
return items.size();
}
@Override
public InventoryListItem getItem(int position) {
return items.get(position);
}
@Override
public int getPosition(InventoryListItem item) {
return items.indexOf(item);
}
@Override
public long getItemId(int position) {
return items.get(position).id;
}
@Override
public boolean isEnabled(int position) {
return true;
}
// Methods
public Filter getFilter() {
if ( filter == null ) {
filter = new InventoryFilter();
}
return filter;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = null;
InventoryListItem item = items.get(position);
if ( item.startNewSection ) {
v = inflater.inflate(R.layout.sectioned_list_header, null);
TextView sectionText = (TextView)v.findViewById(R.id.list_header_title);
sectionText.setText(item.sectionName);
} else {
v = inflater.inflate(resource, null);
TextView nameText = (TextView)v.findViewById(R.id.inventory_list_item_name_text);
TextView qtyText = (TextView)v.findViewById(R.id.inventory_list_item_qty_text);
TextView brandText = (TextView)v.findViewById(R.id.inventory_list_item_brand_text);
final TextView categoryText = (TextView)v.findViewById(R.id.inventory_list_item_category_text);
nameText.setText(item.name);
String qty = Float.toString(item.remainingAmount) + " " + item.measurementAbbv;
qtyText.setText(qty);
brandText.setText(item.brand);
// Create our list of categories and patterns
String categories = "";
for ( int i = 0; i <= item.categories.size() - 1; i++ ) {
if ( categories.length() == 0 ) {
categories = item.categories.get(i);
} else {
categories += ", " + item.categories.get(i);
}
}
categoryText.setMovementMethod(LinkMovementMethod.getInstance());
categoryText.setText(categories, BufferType.SPANNABLE);
// Now creat our spannable text
SpannableString span = (SpannableString)categoryText.getText();
// Create our links and set our text
int startIndex = 0;
boolean stillLooking = true;
while ( stillLooking ) {
int commaIndex = categories.indexOf(", ", startIndex);
if ( commaIndex >= 0 ) {
final String spanText = categoryText.getText().toString().substring(startIndex, commaIndex);
addCategorySpan(span, startIndex, commaIndex, spanText);
startIndex = commaIndex + 2;
} else {
final String spanText = categoryText.getText().toString().substring(startIndex, categoryText.getText().toString().length());
addCategorySpan(span, startIndex, categoryText.getText().toString().length(), spanText);
stillLooking = false;
}
}
v.setTag(item.id);
}
return v;
}
/* -------------------------
* Class: InventoryFilter
* ------------------------- */
private class InventoryFilter extends Filter {
private Object lock = new Object();
/* -------------------------
* Protected Methods
* ------------------------- */
@Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
if ( constraint == null || constraint.length() == 0 ) {
synchronized(lock) {
items = staticItems;
results.values = items;
results.count = items.size();
}
} else {
String searchString = constraint.toString();
// Do our category search
if ( searchString.startsWith("cat:") ) {
String trimmedSearch = searchString.substring(searchString.indexOf("://") + 3);
// Do our search
ArrayList<InventoryListItem> newItems = new ArrayList<InventoryListItem>();
for ( int i = 0; i <= items.size() - 1; i++ ) {
InventoryListItem item = items.get(i);
// See if we're a section, and if we have an item under us
if ( item.startNewSection && i < items.size() - 1 ) {
InventoryListItem next = items.get(i + 1);
if ( next.sectionName == item.sectionName ) {
if ( !next.startNewSection && next.categories.contains(trimmedSearch) ) {
newItems.add(item);
}
}
}
else if ( !item.startNewSection && item.categories.contains(trimmedSearch) ) {
newItems.add(item);
}
}
results.values = newItems;
results.count = newItems.size();
}
}
return results;
}
@SuppressWarnings("unchecked")
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
//noinspection unchecked
items = (ArrayList<InventoryListItem>)results.values;
// Let the adapter know about the updated list
if (results.count > 0) {
notifyDataSetChanged();
} else {
notifyDataSetInvalidated();
}
}
}
}
精彩评论