Android ListView with Custom Layout and Filter example

How to display a List by inflating a Custom Layout in a ListView and filter the results from an EditText

Well if you familiar with using the ListView to display a simple list from an Array and looking to enhance the display with a custom layout then here is an example which will covers most of your needs. In this example we cover the follwing topics
  • Display data in a List using the ListView
  • Define a custom Layout based on your display needs
  • Ability to filter the results as you type in an EditText by extending the Filter class
  • Attach an OnItemClickListener to check when the user taps on an Item


Android Listview custom layout with filter example
Android Listview custom layout with filter example

Source for Activity - AndroidListViewCustomLayoutActivity.java

package com.as400samplecode;

import java.util.ArrayList;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Filter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;

public class AndroidListViewCustomLayoutActivity extends Activity {

 MyCustomAdapter dataAdapter = null;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);

  //Generate list View from ArrayList
  displayListView();

 }

 private void displayListView() {

  //Array list of countries
  ArrayList<Country> countryList = new ArrayList<Country>();
  Country country = new Country("AFG","Afghanistan","Asia",
    "Southern and Central Asia");
  countryList.add(country);
  country = new Country("ALB","Albania","Europe","Southern Europe");
  countryList.add(country);
  country = new Country("DZA","Algeria","Africa","Northern Africa");
  countryList.add(country);
  country = new Country("ASM","American Samoa","Oceania","Polynesia");
  countryList.add(country);
  country = new Country("AND","Andorra","Europe","Southern Europe");
  countryList.add(country);
  country = new Country("AGO","Angola","Africa","Central Africa");
  countryList.add(country);
  country = new Country("AIA","Anguilla","North America","Caribbean");
  countryList.add(country);

  //create an ArrayAdaptar from the String Array
  dataAdapter = new MyCustomAdapter(this,
    R.layout.country_info, countryList);
  ListView listView = (ListView) findViewById(R.id.listView1);
  // Assign adapter to ListView
  listView.setAdapter(dataAdapter);

  //enables filtering for the contents of the given ListView
  listView.setTextFilterEnabled(true);

  listView.setOnItemClickListener(new OnItemClickListener() {
   public void onItemClick(AdapterView<?> parent, View view,
     int position, long id) {
    // When clicked, show a toast with the TextView text
    Country country = (Country) parent.getItemAtPosition(position);
    Toast.makeText(getApplicationContext(),
      country.getCode(), Toast.LENGTH_SHORT).show();
   }
  });

  EditText myFilter = (EditText) findViewById(R.id.myFilter);
  myFilter.addTextChangedListener(new TextWatcher() {

  public void afterTextChanged(Editable s) {
  }

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

  public void onTextChanged(CharSequence s, int start, int before, int count) {
   dataAdapter.getFilter().filter(s.toString());
  }
  });

 }

 private class MyCustomAdapter extends ArrayAdapter<Country> {

  private ArrayList<Country> originalList;
  private ArrayList<Country> countryList;
  private CountryFilter filter;

  public MyCustomAdapter(Context context, int textViewResourceId, 
    ArrayList<Country> countryList) {
   super(context, textViewResourceId, countryList);
   this.countryList = new ArrayList<Country>();
   this.countryList.addAll(countryList);
   this.originalList = new ArrayList<Country>();
   this.originalList.addAll(countryList);
  }

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


  private class ViewHolder {
   TextView code;
   TextView name;
   TextView continent;
   TextView region;
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {

   ViewHolder holder = null;
   Log.v("ConvertView", String.valueOf(position));
   if (convertView == null) {

   LayoutInflater vi = (LayoutInflater)getSystemService(
     Context.LAYOUT_INFLATER_SERVICE);
   convertView = vi.inflate(R.layout.country_info, null);

   holder = new ViewHolder();
   holder.code = (TextView) convertView.findViewById(R.id.code);
   holder.name = (TextView) convertView.findViewById(R.id.name);
   holder.continent = (TextView) convertView.findViewById(R.id.continent);
   holder.region = (TextView) convertView.findViewById(R.id.region);

   convertView.setTag(holder);

   } else {
    holder = (ViewHolder) convertView.getTag();
   }

   Country country = countryList.get(position);
   holder.code.setText(country.getCode());
   holder.name.setText(country.getName());
   holder.continent.setText(country.getContinent());
   holder.region.setText(country.getRegion());

   return convertView;

  }

  private class CountryFilter extends Filter
  {

   @Override
   protected FilterResults performFiltering(CharSequence constraint) {

    constraint = constraint.toString().toLowerCase();
    FilterResults result = new FilterResults();
    if(constraint != null && constraint.toString().length() > 0)
    {
    ArrayList<Country> filteredItems = new ArrayList<Country>();

    for(int i = 0, l = originalList.size(); i < l; i++)
    {
     Country country = originalList.get(i);
     if(country.toString().toLowerCase().contains(constraint))
      filteredItems.add(country);
    }
    result.count = filteredItems.size();
    result.values = filteredItems;
    }
    else
    {
     synchronized(this)
     {
      result.values = originalList;
      result.count = originalList.size();
     }
    }
    return result;
   }

   @SuppressWarnings("unchecked")
   @Override
   protected void publishResults(CharSequence constraint, 
     FilterResults results) {

    countryList = (ArrayList<Country>)results.values;
    notifyDataSetChanged();
    clear();
    for(int i = 0, l = countryList.size(); i < l; i++)
     add(countryList.get(i));
    notifyDataSetInvalidated();
   }
  }


 }
}

Source for POJO - Country.java

package com.as400samplecode;

public class Country {
 
 String code = null;
 String name = null;
 String continent = null;
 String region = null;
 
 public Country(String code, String name, String continent, String region) {
  super();
  this.code = code;
  this.name = name;
  this.continent = continent;
  this.region = region;
 }
 
 public String getCode() {
  return code;
 }
 public void setCode(String code) {
  this.code = code;
 }
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
 public String getContinent() {
  return continent;
 }
 public void setContinent(String continent) {
  this.continent = continent;
 }
 public String getRegion() {
  return region;
 }
 public void setRegion(String region) {
  this.region = region;
 }

 @Override
 public String toString() {
  return  code + " " + name + " "
    + continent + " " + region;
 }
 
 
}

Source for Main Screen Layout - main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="fill_parent" android:layout_height="fill_parent"
 android:orientation="vertical">

 <TextView android:layout_width="fill_parent"
  android:layout_height="wrap_content" android:padding="10dp"
  android:text="@string/some_text" android:textSize="20sp" />

 <EditText android:id="@+id/myFilter" android:layout_width="match_parent"
  android:layout_height="wrap_content" android:ems="10" 
  android:hint="@string/some_hint">
  <requestFocus />
 </EditText>

 <ListView android:id="@+id/listView1" android:layout_width="fill_parent"
  android:layout_height="fill_parent" />

</LinearLayout>

Source for Custom List Layout - country_info.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="6dip" >

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:text="Code: "
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/textView1"
        android:layout_below="@+id/textView1"
        android:text="Name: "
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <TextView
        android:id="@+id/textView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/textView2"
        android:layout_below="@+id/textView2"
        android:text="Continent: "
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <TextView
        android:id="@+id/textView4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/textView3"
        android:layout_below="@+id/textView3"
        android:text="Region: "
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <TextView
        android:id="@+id/continent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/textView3"
        android:layout_alignBottom="@+id/textView3"
        android:layout_toRightOf="@+id/textView3"
        android:text="TextView" />

    <TextView
        android:id="@+id/region"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/textView4"
        android:layout_alignBottom="@+id/textView4"
        android:layout_alignLeft="@+id/continent"
        android:text="TextView" />

    <TextView
        android:id="@+id/name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/textView3"
        android:layout_toRightOf="@+id/textView3"
        android:text="TextView" />

    <TextView
        android:id="@+id/code"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/textView2"
        android:layout_alignLeft="@+id/name"
        android:text="TextView" />

</RelativeLayout>

Source for application variables - strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

 <string name="app_name">Android ListView</string>
 <string name="some_text">
  Display and Filter some North American Countries!
 </string>
 <string name="some_hint">
  Type here to filter&#8230;
 </string>
</resources>

Source for application manifest - AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 package="com.as400samplecode" android:versionCode="1"
 android:versionName="1.0">

 <uses-sdk android:minSdkVersion="15" />

 <application android:icon="@drawable/ic_launcher"
  android:label="@string/app_name" 
  android:theme="@android:style/Theme.Holo.Light">
  <activity android:name=".AndroidListViewFilterActivity"
   android:label="@string/app_name">
   <intent-filter>
    <action android:name="android.intent.action.MAIN" />

    <category android:name="android.intent.category.LAUNCHER" />
   </intent-filter>
  </activity>
 </application>

</manifest>

References


21 comments :

  1. Thanks, it's good but it would be more give clear vision if you have provided documentation comments...

    ReplyDelete
  2. Excellent example! Thank you.

    ReplyDelete
  3. what does the clear() method do here? and about add(countryList.get(i));

    ReplyDelete
    Replies
    1. Hi Goutam,
      The clear() and add() calls you see on publishResults are from the parent class: ArrayAdapter.
      Look at the constructor: super(context, textViewResourceId, countryList);
      So, clear() and add() it's the same as countryList.clear() and countryList.add(). This is the list that the parent class is modifying.

      Delete
    2. Sorry, i said it's the same but in fact there are two lists with the same objects:
      for(int i = 0, l = countryList.size(); i < l; i++)
      add(countryList.get(i));

      By the way, you better use foreach loops:
      for(Country country : countryList)
      add(country);

      Delete
  4. Even better, without loop
    addAll(countryList);

    ReplyDelete
  5. I think you need to implements filterable

    private class MyCustomAdapter extends ArrayAdapter implements Filterable {

    ReplyDelete
  6. you should move this line:
    constraint = constraint.toString().toLowerCase();

    inside condition: if( constraint!=null .... )

    ReplyDelete
  7. NullPointerException on publishResults when there are no results.

    ReplyDelete
  8. This comment has been removed by the author.

    ReplyDelete
  9. If it wasn't for this easy to use code, I would still be stuck. I have one question though. Can I use a String array which is not visible to the user as a search criteria?

    ReplyDelete
  10. could you or anyone please tell me how to update the above lisview with filter ?

    i tried it by using dataAdapter.notifyDataSetChanged() after update the countryList. but not work.

    i want to update it after a json data was downloaded by AsyncTask Thanks

    ReplyDelete
  11. Thanks you so much!!!
    But i don't understand this code
    @Override
    protected void publishResults(CharSequence constraint,
    FilterResults results) {

    countryList = (ArrayList)results.values;
    notifyDataSetChanged();
    clear();
    for(int i = 0, l = countryList.size(); i < l; i++)
    add(countryList.get(i));
    notifyDataSetInvalidated();
    }
    }
    we have countrylist, you clear it? Then you again set data for country list by loop

    ReplyDelete
    Replies
    1. this is my variant of publishResults:
      protected void publishResults(CharSequence constraint, FilterResults results) {
      // TODO Auto-generated method stub
      if(results.count== 0){
      notifyDataSetInvalidated();
      }
      else{
      zoznam=(ArrayList) results.values;
      notifyDataSetChanged();
      }

      }

      Delete
  12. Excellent Tutorial, exactly what I needed

    ReplyDelete
  13. 'notifyDataSetInvalidated()' is VERY important since the adapter is linked to a list which is about to get freed from the memory. Meaning the data wont be valid anymore. Removing this call can cause the adapter to try and access to a non existing list (null object) and will reset the view with empty data = empty list view.

    ReplyDelete
  14. Thank You ! You are doing great job for beginners !

    ReplyDelete
  15. Thanks, it does help me. Finally my listview working fine.

    ReplyDelete
  16. Thanks! it works like a charm!

    ReplyDelete