Filtering ListView with custom (object) adapter

72,719

Solution 1

You need to do a few things:

1) In your activity, register for a text change listener on your EditText that contains the value the user enters:

mSearchValue.addTextChangedListener(searchTextWatcher);

2) Create your searchTextWatcher and have it do something:

private TextWatcher searchTextWatcher = new TextWatcher() {
    @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            // ignore
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            // ignore
        }

        @Override
        public void afterTextChanged(Editable s) {
            Log.d(Constants.TAG, "*** Search value changed: " + s.toString());
            adapter.getFilter().filter(s.toString());
        }
    };

3) Override getFilter() in your custom adapter and have it filter the results and notify the listview that the dataset has changed.

    @Override
    public Filter getFilter() {
        return new Filter() {
            @SuppressWarnings("unchecked")
            @Override
            protected void publishResults(CharSequence constraint, FilterResults results) {
                Log.d(Constants.TAG, "**** PUBLISHING RESULTS for: " + constraint);
                myData = (List<MyDataType>) results.values;
                MyCustomAdapter.this.notifyDataSetChanged();
            }

            @Override
            protected FilterResults performFiltering(CharSequence constraint) {
                Log.d(Constants.TAG, "**** PERFORM FILTERING for: " + constraint);
                List<MyDataType> filteredResults = getFilteredResults(constraint);

                FilterResults results = new FilterResults();
                results.values = filteredResults;

                return results;
            }
        };
    }

Solution 2

Here an interesting example

public Filter getFilter() {
    return new Filter() {

        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            final FilterResults oReturn = new FilterResults();
            final ArrayList<station> results = new ArrayList<station>();
            if (orig == null)
                orig = items;
            if (constraint != null) {
                if (orig != null && orig.size() > 0) {
                    for (final station g : orig) {
                        if (g.getName().toLowerCase()
                                .contains(constraint.toString()))
                            results.add(g);
                    }
                }
                oReturn.values = results;
            }
            return oReturn;
        }

        @SuppressWarnings("unchecked")
        @Override
        protected void publishResults(CharSequence constraint,
                FilterResults results) {
            items = (ArrayList<station>) results.values;
            notifyDataSetChanged();
        }
    };
}

public void notifyDataSetChanged() {
    super.notifyDataSetChanged();
    notifyChanged = true;
}

Solution 3

Add toString override on your base class. For example

@Override
public String toString() {
    return this.name;
}

Above makes your List as string list. So you can use:

your_edit_text.addTextChangedListener(new TextWatcher() {

    @Override
    public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
        YourActivity.this.YourAdapter.getFilter().filter(arg0);
    }

    @Override
    public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
    }

    @Override
    public void afterTextChanged(Editable arg0) {    
    }
});

Solution 4

For those who don't need the Filterable interface, there is a much simpler solution. This also handles notifyDataSetChanged() correctly where the other solutions fail. Note that you need to add a getArray() function to the BaseAdapter that just returns the array object that was passed to the constructor.

public abstract class BaseFilterAdapter<T> extends BaseAdapter<T> {

    private List<T> original;
    private String lastFilter;

    public BaseFilterAdapter(Context context, List<T> array) {
        super(context, new LinkedList<T>());
        original = array;
        filter("");
    }

    protected abstract Boolean predicate(T element, String filter);

    public void filter(String filter) {
        lastFilter = filter;
        super.getArray().clear();
        for (T element : original)
            if (predicate(element, filter))
                super.getArray().add(element);
        super.notifyDataSetChanged();
    }

    @Override
    public List<T> getArray() {
        return original;
    }

    @Override
    public void notifyDataSetChanged() {
        filter(lastFilter);
    }
}
Share:
72,719
Admin
Author by

Admin

Updated on July 08, 2022

Comments

  • Admin
    Admin almost 2 years

    I'm trying to implement filtering of a ListView which is uses a custom object adapter, but I can't find any useful samples. The included code is very simplified, so no- keep in mind I can't use an regular ArrayAdapter. I have a EditText above the ListView, and when the user enters text in the EditText widget I would like to filter the ListView by the text written in the EditText. Any suggestions would be much appreciated!

    Here is the snippet from the activity class:

    public class management_objects extends Activity {
    
    private static List<User> UserList;
    private EfficientAdapter adapter = null;
    private ListView objectListView = null;
    private EditText SearchText = null;
    
    private static class EfficientAdapter extends BaseAdapter implements Filterable{
        private LayoutInflater mInflater;   
    
        public EfficientAdapter(Context context) {
            mInflater = LayoutInflater.from(context);
        }
    
        public int getCount() {
            return UserList.size();
        }
    
        public Object getItem(int position) {
            return position;
        }
    
        public long getItemId(int position) {
            return position;
        }
    
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder; 
            if (convertView == null) { 
                convertView = mInflater.inflate(R.layout.imagelayout_2lines, null);
                holder = new ViewHolder();
                holder.text = (TextView) convertView.findViewById(R.id.managementObjectText);
                holder.subtext = (TextView) convertView.findViewById(R.id.managementObjectSubText);
                holder.icon = (ImageView) convertView.findViewById(R.id.managementObjectIcon);
                convertView.setTag(holder);
            }
            else {
                holder = (ViewHolder) convertView.getTag();
            }
    
            holder.text.setText(UserList.get(position).getFirstName());
            holder.subtext.setText(UserList.get(position).getLastName());
            holder.icon.setImageResource(R.drawable.user);
    
            return convertView;
        }
    
        static class ViewHolder { 
            TextView text;
            TextView subtext;
            ImageView icon;
        }
    
        @Override
        public Filter getFilter() {
            return null;
        }
    }
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.adobjectlist);
        Bundle extras = getIntent().getExtras();
    
        SearchText = (EditText) findViewById(R.id.SearchBox);    
        SearchText.addTextChangedListener(filterTextWatcher);
    
        objectListView = (ListView) findViewById(R.id.ObjectList);
        objectListView.setOnItemClickListener(Item_Click);
        adapter = new EfficientAdapter(this);
        ComputerName = extras.getString("COMPUTER_NAME");
    
        //Get User list from webservice
        ShowUsers();
    }
    

    Here is The User class:

     public class User {
      private int UserId;
      private String FirstName;
      private String LastName;
    
        public int getUserId() {
            return UserId;
        }
        public void setUserId(int UserId) {
            this.UserId = UserId;
        }
        public String getFirstName() {
            return FirstName;
        }
        public void setFirstName(String FirstName) {
            this.FirstName = FirstName;
        }
        public String getLastName() {
            return LastName;
        }
        public void setLastName(String LastName) {
            this.LastName = LastName;
        }
    }
    
  • User
    User about 12 years
    Why you say "override" in 3) ? BaseAdapter doesn't have getFilter().
  • DustinB
    DustinB about 12 years
    Good catch -- not needed. My custom Adapter class (actually inheriting from AmazingAdapter from the AmazingListView open source widget) implements the android.widget.Filterable interface which requires getFilter().
  • Sufian
    Sufian about 11 years
    This tutorial helped me. The answer had some unclear things, or maybe I didn't read it with enough concentration.
  • radhoo
    radhoo almost 11 years
    excellent approach in using the ORIG variable to save the initial instance. It worked good, thank you.
  • anuruddhika
    anuruddhika over 10 years
    @Dustin what is this getFilteredResults(constraint) method?. i tried your answer. but that method not support ne.
  • DustinB
    DustinB over 10 years
    @anuruddhika, That is just your method to do any filtering you need and will return the results. It isn't an Android method you are overriding.
  • jean d'arme
    jean d'arme over 8 years
    @DustinB could You help me with this one? My problem is ListView with PinnedHeader's
  • Abdul Wahab
    Abdul Wahab over 8 years
    I implemented it, working with fine result, but also showing empty items list size is same
  • Riot Goes Woof
    Riot Goes Woof almost 7 years
    It should be noted that the Tutorial and this answer both have a really bad error. It only filters down. In publishResults, it sets the value of the array holding all the list items to equal the filtered list, overwriting the original data with less data, making it so you can never "unfilter". You should make a temporary list that holds your filtered items and use that for your getCount() and getItem(). But do your comparing in performFiltering with the original list, so you can always filter on every possible item. not just the ones that have already been filtered down.
  • Alberto M
    Alberto M over 6 years
    there's no getArray method to override in my BaseAdapter class.
  • zekromWex
    zekromWex about 2 years
    This one is a quick answer for those who don't want to override the getFilter function of the adapter