Building Custom Lists using ListView on Android Tutorial – Part 2

Looking for a new mobile advertising platform? Check out my blog post on the new solution I am using (or sign-up if you don’t want to read the post)

Overview

Now that we have a Custom ListView with 2 TextViews and a ImageView, we may want to be able to search and filter through this list.  For this tutorial, we will add an EditText above the ListView to so users can filter between state or flower names.  If you skipped the first part of the tutorial, you can find it here.

 

 

Changes to First Tutorial Code

Since we are now going to have a filtered list, we are going to need to make some changes to the code we have implemented in the first tutorial.  The first thing we are going to need to do, is add an EditText view on the main layout where users can put their search terms and add a TextWatcher on that object that will call our filter method on the custom Filter we will create.  The new onCreate method within our Activity will now look like this:

	private ArrayList list;
	private Context context;
	private StateFlowerAdapter adapter;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        context = this;
        list = readFile();
        ListView listView = (ListView) findViewById(R.id.stateList);
        adapter = new StateFlowerAdapter(context, list);
        listView.setAdapter(adapter);
 
        EditText searchText = (EditText) findViewById(R.id.searchText);
        searchText.addTextChangedListener(new TextWatcher(){
 
			@Override
			public void afterTextChanged(Editable arg0) {
				// Do nothing
			}
 
			@Override
			public void beforeTextChanged(CharSequence s, int start, int count, int after) {
				// Do nothing
			}
 
			@Override
			public void onTextChanged(CharSequence s, int start, int before, int count) {
				adapter.getFilter().filter(s);
			}
 
        });

Notice that we needed to declare our adapter outside the onCreate method so we can use it properly within the anonymous TextWatcher class.  Now you may be thinking, how do I call filter function on my custom adapter default filter and it knows what I want to filter on? The answer is, it doesn’t, which brings us to our next item we need to complete.

 

 

Building a Custom Filter

For this tutorial (and probably for most of the projects you will have), I will build my filter class within my custom Adapter class.  The only reason you would ever want to separate it is when you will have a project that will have multiple adapters that will require the same filter.  In order to build a custom filter, we need to extend android.widget.Filter and override the performFiltering and publishResults functions.  The following is my custom Filter:

private class StateFlowerFilter extends Filter{
 
		@Override
		protected FilterResults performFiltering(CharSequence constraint) {
			FilterResults retval = new FilterResults();
			retval.values = items;
                        retval.count = items.size();
			if(constraint != null && constraint.toString().length() > 0) {
				constraint = constraint.toString().toUpperCase();
                                ArrayList filt = new ArrayList();
                                ArrayList tmpItems = new ArrayList();
                                tmpItems.addAll(items);
                                for(int i = 0; i < tmpItems.size(); i++) {
                                    StateFlower sf = tmpItems.get(i);
                                    if(sf.getState().toUpperCase().contains(constraint) || sf.getFlowerName().toUpperCase().contains(constraint)) {
                                        filt.add(sf);
                                    }
                                }
                               retval.count = filt.size();
                               retval.values = filt;
                           }
	                  return retval;
	        }
 
		@Override
		protected void publishResults(CharSequence constraint, FilterResults results) {
			filteredItems = (ArrayList)results.values;
			notifyDataSetChanged();
			clear();
			Log.d("Filter", "Starting to publish the results with " + filteredItems.size() + " items");
			for (int i = 0; i < filteredItems.size(); i++){
				add(filteredItems.get(i));
			}
			Log.d("Filter", "Finished publishing results");
			notifyDataSetInvalidated();
	}

As you can see in the performFiltering function, we are setting the CharSequence to all caps therefore our search will not be case sensitive.  Anytime the state name or the state flower contains CharSequence passed in, we add it to the FilterResults.  Note that the performFiltering method is automatically called when we call the filter method on our filter object.  Recall that we called filter on the inside the onTextChanged method from our Activity.  After calling the performFiltering function, the results are then passed into the publishResults method automatically.  It is within this method that we set our new filteredItems variable equal to the results’ values property (which holds our ArrayList<StateFlower>).

Now that we are properly filtering and storing the filtered results into this new filteredItems variable, we need to update the getView method of our adapter to utilise this new variable (so we will draw the views of the filtered items instead of all items).  This is simple enough, we just need to replace all occurences of the variable items in getView, with the new variable filteredItems.  See below for the new getView method:

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		View List;
		if(convertView==null){
			List=new View(context);
			LayoutInflater mLayoutinflater=(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			List=mLayoutinflater.inflate(R.layout.state_item, parent, false);
		}
		else{
			List = (View)convertView;
		}
		TextView nameText = (TextView)List.findViewById(R.id.stateNameTextView);
		nameText.setText(filteredItems.get(position).getState());
		TextView flowerText = (TextView) List.findViewById(R.id.stateFlowerText);
		flowerText.setText(filteredItems.get(position).getFlowerName());
		ImageView flowerImage = (ImageView) List.findViewById(R.id.stateFlowerImage);
		flowerImage.setImageResource(filteredItems.get(position).getImageResId(context));
		return List;
	}

Almost there, we just need to set the filteredItems equal to items in the constructor as well so before any filtering is done, we have the full list.

public StateFlowerAdapter(Context c, ArrayList&lt;StateFlower&gt; items) {
  super (c, R.layout.state_item);
  this.items = items;
  filteredItems = items;
  context = c;
}

Let’s go ahead and run our program and enter a word to filter on.  Did we get our desired results?  No! This is because we are calling the getFilter method on our adapter within the onTextChanged method, but we did not put in our custom getFilter method to use the new Filter we have just created.  The last and final step is to override that getFilter method within our adapter class to return a Filter of type StateFlowerFilter like so:

	@Override
	public Filter getFilter() {
		if (filter == null){
			filter = new StateFlowerFilter();
		}
		return filter;
	}

We obviously will need to declare a variable filter in as a class variable, and then we are all set.  Run the program and see filtering in action.

Final Thoughts

Now that we have a completely working filter, play around with the performFiltering method and customize what to be filtering on.  If you wanted to play jokes on a user, do an opposite filter that will return everything the user didn’t want.

By building and utilizing custom ListViews and custom Filtering, you can take your Android project to the next level.  You will be able to produce much more user-friendly and professional looking apps that will impress your user-base.  We no longer need to stick with a simple list of Strings within our List, but instead can display any combination of built-in Views provided by Android SDK (TextView, EditText, ImageView, CheckBox, etc.).  We can go from needing 2 or more screens to do simple tasks (such as rating an item), to doing it all within the same View!

Thanks for reading, and please let me know if you have any questions.

Likes the tutorial? Show it by donating to the site. All donations are made to Maryland Special Olympics:

3 Comments

  • Chrystiano Araújo says:

    First of all, congratulations for the great post.

    I would like to make just one correction. You forgot to mention that we should change the methods getCount and getItem in the StateFlowerAdapter class, which would raise an IndexOutOfRangeException.

  • Adam says:

    What is the difference between getView() func in tutorial part 1 and 2 ?

  • Brian says:

    As stated in the tutorial, part 2′s getView function utilizes a filtered list, while part 1 does not have a filter and utilizes the full list.

    filteredItems vs. items.

    Brian

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">