In my previous post , we implemented a Custom ListView.
Now,we'll see how to implement a search function in it.The default text filter associated with the ListView tends to malfunction when used with custom list items.There is a workaround to this,which I'll be posting soon.
For now,we'll see how we can search the ListView under the current circumstances.
The idea is to have an EditText above the ListView and when the user types a search query,the ListView filters itself to show only the relevant results;Like this:
Here's the main.xml that shows the EditText along with the ListView:
Now,we'll see how to implement a search function in it.The default text filter associated with the ListView tends to malfunction when used with custom list items.There is a workaround to this,which I'll be posting soon.
For now,we'll see how we can search the ListView under the current circumstances.
The idea is to have an EditText above the ListView and when the user types a search query,the ListView filters itself to show only the relevant results;Like this:
Here's the main.xml that shows the EditText along with the ListView:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <EditText android:id="@+id/searchBox" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <ListView android:id="@android:id/list" android:layout_width="fill_parent" android:layout_height="0dp" android:layout_weight="1"/> <ImageView android:id="@android:id/empty" android:layout_width="fill_parent" android:layout_height="fill_parent" android:src="@drawable/no_cocktail"/> </LinearLayout>
Take note of the Id's used for the ListView and ImageView.They are @android:id/list and @android:id/empty respectively.Assigning these pre-defined IDs allows us to control what is shown to the user under different conditions,without much effort.ie,When the ListView contains items,the user will see the EditText and the ListView.When the list is empty,the user will see the EditText and the ImageView.As shown above,there is no player in the list whose name starts with 'h'.Hence,The ImageView with Id @android:id/empty is displayed.This is possible only because we are using a ListActivity.This method won't work in an Activity.
Let's move on to the coding now.Here's the ListActivity.The onCreate() method is used here to populate the ArrayList of players.
public class SearchCustomListViewActivity extends ListActivity { //ArrayList thats going to hold the search results ArrayList<HashMap<String, Object>> searchResults; //ArrayList that will hold the original Data ArrayList<HashMap<String, Object>> originalValues; LayoutInflater inflater; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); final EditText searchBox=(EditText) findViewById(R.id.searchBox); ListView playersListView=(ListView) findViewById(android.R.id.list); //get the LayoutInflater for inflating the customomView //this will be used in the custom adapter inflater=(LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); //these arrays are just the data that //I'll be using to populate the ArrayList //You can use our own methods to get the data String names[]={"Ronaldo","Messi","Torres","Iniesta", "Drogba","Gerrard","Rooney","Xavi"}; String teams[]={"Real Madrid","Barcelona","Chelsea", "Barcelona","Chelsea","Liverpool", "ManU","Barcelona"}; Integer[] photos={R.drawable.cr7,R.drawable.messi, R.drawable.torres,R.drawable.iniesta, R.drawable.drogba,R.drawable.gerrard, R.drawable.rooney,R.drawable.xavi}; originalValues=new ArrayList<HashMap<String,Object>>(); //temporary HashMap for populating the //Items in the ListView HashMap<String , Object> temp; //total number of rows in the ListView int noOfPlayers=names.length; //now populate the ArrayList players for(int i=0;i<noOfPlayers;i++) { temp=new HashMap<String, Object>(); temp.put("name", names[i]); temp.put("team", teams[i]); temp.put("photo", photos[i]); //add the row to the ArrayList originalValues.add(temp); } //searchResults=OriginalValues initially searchResults=new ArrayList<HashMap<String,Object>>(originalValues); //create the adapter //first param-the context //second param-the id of the layout file //you will be using to fill a row //third param-the set of values that //will populate the ListView final CustomAdapter adapter=new CustomAdapter(this, R.layout.players_layout,searchResults); //finally,set the adapter to the default ListView playersListView.setAdapter(adapter); searchBox.addTextChangedListener(new TextWatcher() { public void onTextChanged(CharSequence s, int start, int before, int count) { //get the text in the EditText String searchString=searchBox.getText().toString(); int textLength=searchString.length(); //clear the initial data set searchResults.clear(); for(int i=0;i<originalValues.size();i++) { String playerName=originalValues.get(i).get("name").toString(); if(textLength<=playerName.length()){ //compare the String in EditText with Names in the ArrayList if(searchString.equalsIgnoreCase(playerName.substring(0,textLength))) searchResults.add(originalValues.get(i)); } } adapter.notifyDataSetChanged(); } public void beforeTextChanged(CharSequence s, int start, int count, int after) { } public void afterTextChanged(Editable s) { } }); }
As you can see,this is pretty much the previous post,except for this part:
searchBox.addTextChangedListener(new TextWatcher() { public void onTextChanged(CharSequence s, int start, int before, int count) { //get the text in the EditText String searchString=searchBox.getText().toString(); int textLength=searchString.length(); //clear the initial data set searchResults.clear(); for(int i=0;i<originalValues.size();i++) { String playerName=originalValues.get(i).get("name").toString(); if(textLength<=playerName.length()){ //compare the String in EditText with Names in the ArrayList if(searchString.equalsIgnoreCase(playerName.substring(0,textLength))) searchResults.add(originalValues.get(i)); } } adapter.notifyDataSetChanged(); } public void beforeTextChanged(CharSequence s, int start, int count, int after) { } public void afterTextChanged(Editable s) { } });Examine the code and you will find it pretty self-explanatory.searchBox is the EditText.What we have done here is to attach a TextWatcher interface to the searchBox.
The methods of this interface will be called when the text in the searchBox changes.You can have a look at the interface here.When the text in the searchBox changes,we clear the initial data set and compare the entered text with all the names in the ArrayList of players.All names matching the search query are added to the ArrayList searchResult and notifyDataSetChanged() is called on the adapter.
notifyDataSetChanged() tells the CustomAdapter that the underlying data has changed and that the ListView should refresh itself.As the ArrayList searchResult is used for displaying the ListView,the changed data will be reflected in the ListView.That's it!! We''ve done it!! :).Have a look at the CustomAdapter if you have'nt already:
//define your custom adapter private class CustomAdapter extends ArrayAdapter<HashMap<String, Object>> { public CustomAdapter(Context context, int textViewResourceId, ArrayList<HashMap<String, Object>> Strings) { //let android do the initializing :) super(context, textViewResourceId, Strings); } //class for caching the views in a row private class ViewHolder { ImageView photo; TextView name,team; } ViewHolder viewHolder; @Override public View getView(int position, View convertView, ViewGroup parent) { if(convertView==null) { convertView=inflater.inflate(R.layout.players_layout, null); viewHolder=new ViewHolder(); //cache the views viewHolder.photo=(ImageView) convertView.findViewById(R.id.photo); viewHolder.name=(TextView) convertView.findViewById(R.id.name); viewHolder.team=(TextView) convertView.findViewById(R.id.team); //link the cached views to the convertview convertView.setTag(viewHolder); } else viewHolder=(ViewHolder) convertView.getTag(); int photoId=(Integer) searchResults.get(position).get("photo"); //set the data to be displayed viewHolder.photo.setImageDrawable(getResources().getDrawable(photoId)); viewHolder.name.setText(searchResults.get(position).get("name").toString()); viewHolder.team.setText(searchResults.get(position).get("team").toString()); //return the view to be displayed return convertView; } }
And here's everything put together:
public class SearchCustomListViewActivity extends ListActivity { //ArrayList thats going to hold the search results ArrayList<HashMap<String, Object>> searchResults; //ArrayList that will hold the original Data ArrayList<HashMap<String, Object>> originalValues; LayoutInflater inflater; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); final EditText searchBox=(EditText) findViewById(R.id.searchBox); ListView playerListView=(ListView) findViewById(android.R.id.list); //get the LayoutInflater for inflating the customomView //this will be used in the custom adapter inflater=(LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); //these arrays are just the data that //I'll be using to populate the ArrayList //You can use our own methods to get the data String names[]={"Ronaldo","Messi","Torres","Iniesta", "Drogba","Gerrard","Rooney","Xavi"}; String teams[]={"Real Madrid","Barcelona","Chelsea", "Barcelona","Chelsea","Liverpool", "ManU","Barcelona"}; Integer[] photos={R.drawable.cr7,R.drawable.messi, R.drawable.torres,R.drawable.iniesta, R.drawable.drogba,R.drawable.gerrard, R.drawable.rooney,R.drawable.xavi}; originalValues=new ArrayList<HashMap<String,Object>>(); //temporary HashMap for populating the //Items in the ListView HashMap<String , Object> temp; //total number of rows in the ListView int noOfPlayers=names.length; //now populate the ArrayList players for(int i=0;i<noOfPlayers;i++) { temp=new HashMap<String, Object>(); temp.put("name", names[i]); temp.put("team", teams[i]); temp.put("photo", photos[i]); //add the row to the ArrayList originalValues.add(temp); } //searchResults=OriginalValues initially searchResults=new ArrayList<HashMap<String,Object>>(originalValues); //create the adapter //first param-the context //second param-the id of the layout file //you will be using to fill a row //third param-the set of values that //will populate the ListView final CustomAdapter adapter=new CustomAdapter(this, R.layout.players_layout,searchResults); //finally,set the adapter to the default ListView cocktailListView.setAdapter(adapter); searchBox.addTextChangedListener(new TextWatcher() { public void onTextChanged(CharSequence s, int start, int before, int count) { //get the text in the EditText String searchString=searchBox.getText().toString(); int textLength=searchString.length(); searchResults.clear(); for(int i=0;i<originalValues.size();i++) { String playerName=originalValues.get(i).get("name").toString(); if(textLength<=playerName.length()){ //compare the String in EditText with Names in the ArrayList if(searchString.equalsIgnoreCase(playerName.substring(0,textLength))) searchResults.add(originalValues.get(i)); } } adapter.notifyDataSetChanged(); } public void beforeTextChanged(CharSequence s, int start, int count, int after) { } public void afterTextChanged(Editable s) { } }); } //define your custom adapter private class CustomAdapter extends ArrayAdapter<HashMap<String, Object>> { public CustomAdapter(Context context, int textViewResourceId, ArrayList<HashMap<String, Object>> Strings) { //let android do the initializing :) super(context, textViewResourceId, Strings); } //class for caching the views in a row private class ViewHolder { ImageView photo; TextView name,team; } ViewHolder viewHolder; @Override public View getView(int position, View convertView, ViewGroup parent) { if(convertView==null) { convertView=inflater.inflate(R.layout.players_layout, null); viewHolder=new ViewHolder(); //cache the views viewHolder.photo=(ImageView) convertView.findViewById(R.id.photo); viewHolder.name=(TextView) convertView.findViewById(R.id.name); viewHolder.team=(TextView) convertView.findViewById(R.id.team); //link the cached views to the convertview convertView.setTag(viewHolder); } else viewHolder=(ViewHolder) convertView.getTag(); int photoId=(Integer) searchResults.get(position).get("photo"); //set the data to be displayed viewHolder.photo.setImageDrawable(getResources().getDrawable(photoId)); viewHolder.name.setText(searchResults.get(position).get("name").toString()); viewHolder.team.setText(searchResults.get(position).get("team").toString()); //return the view to be displayed return convertView; } } }
Thank you for the turorial. But I have a little problem with it. I tried to used it for my app, but I have the Problem, that the search doesnt work. Whatever i type, the resut is everytime the first entry in the list. Here you can see the code: http://stackoverflow.com/questions/11040117/searchbox-in-listview-always-the-same-result
ReplyDeletei'm very sorry for replying so late..i was held up for quite sometime..
DeleteI had the same error when i tried the logic for the first time ;-).. i'm glad you figured it out yourself..:)
thansk so much :*
ReplyDeleteyour welcome..:)
Deletethis worked for me after a lot search in Google.
ReplyDeletethank u so much
:)
DeleteGreat Tutorial........!!!Really
ReplyDeleteerror: The method getResources() is undefined for the type CustomAdapter
ReplyDeletegetResources() is a method in Activity. My CustomAdapter, being nested in the activity , has access to it.
DeleteHi, I've run your code, no error there but i can get the app started, it keep crash. Any idea why?
ReplyDeleteWow great and simple tutorial, without much filters and other long process
ReplyDeleteAwesome tutorial.. Simple implementation and great :)Thank you
ReplyDelete