Thursday, October 18, 2012

Android ListView : Custom Filter and Filterable interface

In the previous post we showed the way we can programming a custom adapter and a custom layout. One aspect we didn’t cover by now, is how we can filter the items in the ListView. In this post we describe how we can filter items inside a ListView while the user writes words inside an input field. We can use the example shown in the last post and modify it. What do we need to do it?

  • Modify the custom adapter, PlanetAdapter so that it will be able to filter the items
  • Add an input field where the user can insert the keys to filter the ListView
Let’s start then.

Android ListView Custom filter

Modify the custom adapter so that it implements the Filterable interface

If we didn’t use a custom adapter with custom object inside the ListView, the things would be much easier. For example if we used just String as item content we simply could add a few lines of code to make our list filterable; but we have a custom layout that uses not a simply String but a complex object (i.e. Planet). So we need first modify our custom adapter (PlanetAdapter) so that it implements the android.widget.Filter interface. This interface requires we implement just only one method called getFilter() that returns a android.widget.Filter object. The problem is the way we can create this filter. Very easy!

private class PlanetFilter extends Filter {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
....
}

@Override
protected void publishResults(CharSequence constraint,FilterResults results) {
.....
}

}

We create a private class called PlanetFilter that extends the android.widget.Filter. This class has two abstract methods:


  • FilterResults performFiltering(CharSequence constraint) : invoked in worker thread, that has the task to filter the results according to the constraint
  • void publishResults(CharSequence constraint,FilterResults results): that has the task to show the result set created by performingFiltering method

Let’s suppose we want to filter the planet list using the name attribute. In our case, then, the constraint can be the initial letters of the planet name, so that if the planet starts with those letters we show it otherwise we hide it.
@Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
// We implement here the filter logic
if (constraint == null || constraint.length() == 0) {
// No filter implemented we return all the list
results.values = planetList;
results.count = planetList.size();
}
else {
// We perform filtering operation
List<Planet> nPlanetList = new ArrayList<Planet>();

for (Planet p : planetList) {
if (p.getName().toUpperCase().startsWith(constraint.toString().toUpperCase()))
nPlanetList.add(p);
}

results.values = nPlanetList;
results.count = nPlanetList.size();

}
return results;
}

In this method we have to return a FilterResults that has two static attributes called values and count. Values is a list of values to be shown while count is the number of these values. The first thing we do in this method is to verify if the constraint is null or empty, in this case we don’t have to filter the result and simply
results.values = planetList;
results.count = planetList.size();

In other words all the list. If the constraints aren’t null or empty we simply select the planet with its name starting with the constraint, as shown below.
List<Planet> nPlanetList = new ArrayList<Planet>();

for (Planet p : planetList) {
if (p.getName().toUpperCase().startsWith(constraint.toString().toUpperCase()))
nPlanetList.add(p);
}

results.values = nPlanetList;
results.count = nPlanetList.size();
The next step is publishing the result.
@Override
protected void publishResults(CharSequence constraint,
FilterResults results) {

// Now we have to inform the adapter about the new list filtered
if (results.count == 0)
notifyDataSetInvalidated();
else {
planetList = (List<Planet>) results.values;
notifyDataSetChanged();
}

}

We said before that the PlanetAdapter has to implement Filterable interface and it has to implement getFilter() method:
@Override
public Filter getFilter() {
if (planetFilter == null)
planetFilter = new PlanetFilter();

return planetFilter;
}

This is code is quite trivial, we simply invalidate the result set if the result list is empty or we notify that the result set is changed.

It is done!….quite done!…We need now adding just an input field where user can insert the keyword to filter the result. The new layout becomes:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:focusable="true"
android:focusableInTouchMode="true">

<EditText
android:id="@+id/editTxt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:maxLines="1" />

<ListView android:id="@+id/listView"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:layout_weight="1"/>


<Button android:id="@+id/addBtn"
android:text="Add planet"
android:onClick="addPlanet"
android:layout_weight="0.5"
android:layout_height="80dp"
android:layout_width="match_parent"/>


</LinearLayout>
In the main activity we have to add some listener attached to EditText so that when user writes inside it we can filter the list items.
editTxt.addTextChangedListener(new TextWatcher() {

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
System.out.println("Text ["+s+"]");
aAdpt.getFilter().filter(s.toString());
}

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

}

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

Running the code we have:

     androdi_listview_filter android_listview_filtered_itmes

Tricks


One simple trick we have to use is to make soft keyboard hidden at the app startup. As soon as the app starts the EditText gains the focus and the OS shows the keyboard covering the list items. If we don’t want this we can simply “move” the focus somewhere else:

android:focusable="true"
android:focusableInTouchMode="true"
Source code here

Android ListView : Custom Filter and Filterable interface Rating: 4.5 Diposkan Oleh: Unknown

0 comments:

Post a Comment