Android: ArrayList - filtering - android

I try added filter to ArrayList (data is from json/php script on my server). I find but I not see resolving...
I try more sample codes and more resolving, but nothing works.
listView = (ListView) findViewById(R.id.list);
list = new ArrayList<FriendItem>();
adapter = new FriendAllAdapter(context, list);
listView.setAdapter(adapter);
String filename = getResources().getString(R.string.friendalllist_php);
asyncLoadVolley = new AsyncLoadVolley(context, filename);
Map<String, String> map = new HashMap<String, String>();
map.put(Constant.ID, Sessions.getUserId(context));
asyncLoadVolley.setBasicNameValuePair(map);
asyncLoadVolley.setOnAsyncTaskListener(asyncTaskListener);
connectionDetector = new ConnectionDetector(context);
if(savedInstanceState==null) {
}
//enables filtering for the contents of the given ListView
listView.setTextFilterEnabled(true);
listView.setOnItemClickListener(listItemClickListener);
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) {
//here added filter...
}
});

For example:
You have a model - FriendItem
public class FriendItem {
private String firstName;
private String lastName;
//.....
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
and have an adapter - FriendAllAdapter.
In this adapter you should implement Filterable interface.
You"ll need to Override getFilter() method and return new object of Filter class (android.widget.Filter).
In performFiltering() method your need return FilterResults object with count of filtered elements and value - list of elements. Also in this method you need to realize the algorithm comparison of the search query to values in your FriendItem (list item). In my example I search FriendItem(s) which fields (firstName or lastName) contains text in my editText.
Input parameter is a CharSequence object which you set in onTextChanged() method on added textChangeListener to your EditText.
myFilter.addTextChangedListener(new TextWatcher() {
public void onTextChanged(CharSequence s, int start, int before, int count) {
adapter.getFilter().filter(s);
}
});
In publishResults() method your get filtering result your need cast results.values to List and replace your list to filtering result.
public class FriendAllAdapter extends BaseAdapter implements Filterable {
private List<FriendItem> list;
private final List<FriendItem> fullList = new ArrayList<>();
public FriendAllAdapter(Context context, List<FriendItem> list) {
//...... your code
this.list = list;
fullList.addAll(list);
}
//...... your code
#Override
public View getView(int position, View convertView, ViewGroup parent) {
//... your code
}
#Override
public Filter getFilter() {
Filter filter = new Filter() {
#Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
if (constraint == null || constraint.length() == 0) { // if your editText field is empty, return full list of FriendItem
results.count = fullList.size();
results.values = fullList;
} else {
List<FriendItem> filteredList = new ArrayList<>();
constraint = constraint.toString().toLowerCase(); // if we ignore case
for (FriendItem item : fullList) {
String firstName = item.getFirstName().toLowerCase(); // if we ignore case
String lastName = item.getLastName().toLowerCase(); // if we ignore case
if (firstName.contains(constraint.toString()) || lastName.contains(constraint.toString())) {
filteredList.add(item); // added item witch contains our text in EditText
}
}
results.count = filteredList.size(); // set count of filtered list
results.values = filteredList; // set filtered list
}
return results; // return our filtered list
}
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
list = (List<FriendItem>) results.values; // replace list to filtered list
notifyDataSetChanged(); // refresh adapter
}
};
return filter;
}
}
I think you`ll understand it.
P.S. sorry for my English.

Related

how to get space in edittext filter using custom listview? does it possible using simple adapter?

I have custom listview using simple adapter, Currently I have issue regarding filter that I have custom list data with numbers and characters in listview.
If I enter name then its give one blank space the filter results gets disappear.
I have list data like name then number for example : NAME 123, Whenever I enter name then gives space in that edit text then results are gone and list-view gets disappears.
I have tried this on below link but they used Array adapter, So my question is is it possible only in Array adapter or I can used simple adapter?
Android listview edittext filter space button?
If yes then how can I implement, kindly help. Advance thank you.
try this way
searchView.setOnEditorActionListener(new EditText.OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView v, int actionId,
KeyEvent event) {
return true;
}
});
addTextChangeListener();
now create method addTextChangeListener
private void addTextChangeListener() {
searchView.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence query, int start, int before, int count) {
query = query.toString().trim().toLowerCase();
final ArrayList<CityDataModel> filteredList = new ArrayList<>();
final CharSequence finalQuery = query;
new Thread(new Runnable() {
#Override
public void run() {
// Clear the filter list
filteredList.clear();
// If there is no search value, then add all original list items to filter list
if (TextUtils.isEmpty(finalQuery)) {
filteredList.addAll(cities);
} else {
// Iterate in the original List and add it to filter list...
for (CityDataModel item : cities) {
if (item.getCity_name().toLowerCase().contains(finalQuery.toString().toLowerCase())
) {
// Adding Matched items
filteredList.add(item);
}
}
}
// Set on UI Thread
((Activity) context).runOnUiThread(new Runnable() {
#Override
public void run() {
// Notify the List that the DataSet has changed...
adapter = new SearchCityAdapter(SearchCityClass.this, filteredList);
recyclerSearchCity.setAdapter(adapter);
}
});
}
}).start();
}
#Override
public void afterTextChanged(Editable s) {
}
});
}
You can use any Adapter, you can just implements your adapter with android.widget.Filterable
Example Adapter,
public class AppAdapter extends RecyclerView.Adapter<AppHolder> implements Filterable {
public static final String TAG = AppAdapter.class.getSimpleName();
private ArrayList<App> mApps = new ArrayList<>();
private List<App> mCurrentItmCopy = new ArrayList<>();
private String currentFilter;
private MyArrayFilter mFilter;
#Override
public AppHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View receiverView = LayoutInflater.from(parent.getContext()).
inflate(R.layout.layout_row_apps, parent, false);
return new AppHolder(receiverView);
}
#Override
public void onBindViewHolder(final AppHolder holder, int position) {
final App data = mApps.get(position);
}
#Override
public int getItemCount() {
return mApps.size();
}
#Override
public Filter getFilter() {
if (mFilter == null) {
mFilter = new MyArrayFilter();
}
return mFilter;
}
private class MyArrayFilter extends Filter {
#Override
protected FilterResults performFiltering(CharSequence prefix) {
FilterResults results = new FilterResults();
if (mCurrentItmCopy == null || (mCurrentItmCopy.size() == 0)) {
mCurrentItmCopy = new ArrayList<App>(mApps);
}
ArrayList<App> newValues = new ArrayList<App>();
if (prefix != null && !TextUtils.isEmpty(prefix.toString())) {
String prefixString = prefix.toString().toLowerCase();
for (App value : mCurrentItmCopy) {
String label = value.getLabel().toLowerCase();
if ((label.contains(prefixString)) && !newValues.contains(value)) {
newValues.add(value);
}
}
results.values = newValues;
results.count = newValues.size();
} else {
results.values = new ArrayList<App>(mCurrentItmCopy);
results.count = mCurrentItmCopy.size();
mCurrentItmCopy.clear();
}
return results;
}
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
currentFilter = constraint.toString();
if (results.count > 0) {
mApps.clear();
addAll((ArrayList<App>) results.values);
} else {
mApps.clear();
notifyDataSetChanged();
}
}
}
public void addAll(List<App> items) {
if (items != null) {
mApps.addAll(items);
}
notifyDataSetChanged();
}
}
In the above Adapter instead of App, you can use your object.
You can call from your activity or fragment like this,
mAdapter.getFilter().filter(newText);

Get filtered array size in AutoCompleteTextview

I am working on the project in which user can search data. For that, I have implemented AutoCompleteTextView.
autoComplete.setAdapter(new ArrayAdapter<String>(CheckRiskActivity.this,
R.layout.auto_text_row, druglist));
autoComplete.setThreshold(1);
//druglist is my arraylist
Text change listener is as below:
autoComplete.addTextChangedListener(new TextWatcher() {
#Override
public void afterTextChanged(Editable s) {
// here I want to get the size of filtered array list every time when the user adds any character.
}
#Override
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start,
int before, int count) {
}
});
Explanation: If my initial array size is 100 and if the user types 'a', then I want to get the size of filtered array.
Note: I have tried autoComplete.getAdapter().getCount(); but it gives the actual result after adding one more character.
You cannot get correct filtered items' count in TextWatcher, because filtering usually takes longer time than TextWatcher event listeners. Therefore you get incorrect autoComplete.getAdapter().getCount() in afterTextChanged(). I would recommend to use custom listener which will be called every time when filtered items are changed.
I will provide 2 similar approaches: using separate classes and using only 1 class.
APPROACH 1:
Your adapter should look like:
import android.content.Context;
import android.widget.ArrayAdapter;
import android.widget.Filter;
import java.util.ArrayList;
import java.util.List;
public class AutoCompleteAdapter extends ArrayAdapter
{
private List<String> tempItems;
private List<String> suggestions;
private FilterListeners filterListeners;
public AutoCompleteAdapter(Context context, int resource, List<String> items)
{
super(context, resource, 0, items);
tempItems = new ArrayList<>(items);
suggestions = new ArrayList<>();
}
public void setFilterListeners(FilterListeners filterFinishedListener)
{
filterListeners = filterFinishedListener;
}
#Override
public Filter getFilter()
{
return nameFilter;
}
Filter nameFilter = new Filter()
{
#Override
protected FilterResults performFiltering(CharSequence constraint)
{
if (constraint != null)
{
suggestions.clear();
for (String names : tempItems)
{
if (names.toLowerCase().startsWith(constraint.toString().toLowerCase()))
{
suggestions.add(names);
}
}
FilterResults filterResults = new FilterResults();
filterResults.values = suggestions;
filterResults.count = suggestions.size();
return filterResults;
}
else
{
return new FilterResults();
}
}
#Override
protected void publishResults(CharSequence constraint, FilterResults results)
{
List<String> filterList = (ArrayList<String>) results.values;
if (filterListeners != null && filterList!= null)
filterListeners.filteringFinished(filterList.size());
if (results != null && results.count > 0)
{
clear();
for (String item : filterList)
{
add(item);
notifyDataSetChanged();
}
}
}
};
}
An interface which is used to inform you when filtering will be finished:
public interface FilterListeners
{
void filteringFinished(int filteredItemsCount);
}
And you can use it:
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.AutoCompleteTextView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends Activity implements FilterListeners
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AutoCompleteTextView autoComplete = (AutoCompleteTextView) findViewById(R.id.autoComplete);
autoComplete.setThreshold(1);
List<String> stringList = new ArrayList<>();
stringList.add("Black");
stringList.add("White");
stringList.add("Yellow");
stringList.add("Blue");
stringList.add("Brown");
final AutoCompleteAdapter adapter = new AutoCompleteAdapter(this, android.R.layout.simple_list_item_1, stringList);
adapter.setFilterListeners(this);
autoComplete.setAdapter(adapter);
}
#Override
public void filteringFinished(int filteredItemsCount)
{
Log.i("LOG_TAG", " filteringFinished count = " + filteredItemsCount);
}
}
APPROACH 2:
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Filter;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends Activity
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final AutoCompleteTextView autoComplete = (AutoCompleteTextView) findViewById(R.id.autoComplete);
autoComplete.setThreshold(1);
final List<String> stringList = new ArrayList<>();
stringList.add("Black");
stringList.add("White");
stringList.add("Yellow");
stringList.add("Blue");
stringList.add("Brown");
final ArrayAdapter arrayAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, stringList)
{
private List<String> tempItems = stringList;
private List<String> suggestions = new ArrayList<>();
#Override
public Filter getFilter()
{
return nameFilter;
}
Filter nameFilter = new Filter()
{
#Override
protected FilterResults performFiltering(CharSequence constraint)
{
if (constraint != null)
{
suggestions.clear();
for (String names : tempItems)
{
if (names.toLowerCase().startsWith(constraint.toString().toLowerCase()))
{
suggestions.add(names);
}
}
FilterResults filterResults = new FilterResults();
filterResults.values = suggestions;
filterResults.count = suggestions.size();
return filterResults;
}
else
{
return new FilterResults();
}
}
#Override
protected void publishResults(CharSequence constraint, FilterResults results)
{
List<String> filterList = (ArrayList<String>) results.values;
filteringFinished(filterList.size());
if (results != null && results.count > 0)
{
clear();
for (String item : filterList)
{
add(item);
notifyDataSetChanged();
}
}
}
};
};
autoComplete.setAdapter(arrayAdapter);
}
private void filteringFinished(int filteredItemsCount)
{
Log.i("LOG_TAG", " filteringFinished count = " + filteredItemsCount);
}
}
filteringFinished() method will be called when you enter something to an autocomplete input field and it gets filtered.
UPDATE (Trie Search):
I have created a Github project with a simple example of using Trie search algorithm to increase autocomplete performance very much.
https://github.com/saqada/android-AutoCompleteWithTrie
according to Ayaz Alifov answer you cannot get correct filtered items' count in TextWatcher, because filtering usually takes longer time than TextWatcher event listeners.
but i have done a trick with a timerTask. so the TextWatcher would execute after counting.
editText.addTextChangedListener(
new TextWatcher() {
#Override public void onTextChanged(CharSequence s, int start, int before, int count) { }
#Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
private Timer timer=new Timer();
private final long DELAY = 1000; // milliseconds
#Override
public void afterTextChanged(final Editable s) {
timer.cancel();
timer = new Timer();
timer.schedule(
new TimerTask() {
#Override
public void run() {
// adapter.getCount() will give you the correct item's counts
Log.d(TAG, "run: afterTextChanged " + adapter.getCount());
}
},
DELAY
);
}
}
);
Edited: 5/Sep/2019
you can also get items count with the help of setting a registerDataSetObserver.
adapter.registerDataSetObserver(new DataSetObserver() {
#Override
public void onChanged() {
super.onChanged();
Log.d(TAG, "onChanged: " + adapter.getCount());
}
});
in this way the onChanged() will call every time text change. But if the suggestion list becomes empty, it will not be called.
Basically, we have to implement Filterable at Adapter class
public class DrugListAdapter extends BaseAdapter implements
Filterable {
Context context;
LayoutInflater inflater;
drugsFilter drugsFilter;
List<Drug> drugList = new ArrayList<>();
private List<Drug> drugListOrig;
public DrugListAdapter(Context context,
List<Drug> drugList) {
super();
this.context = context;
this.drugList = drugList;
this.drugListOrig = new ArrayList<>(
drugList);
inflater = LayoutInflater.from(context);
}
public void resetData() {
drugList = drugListOrig;
}
#Override
public int getCount() {
return drugList.size();
}
#Override
public Drug getItem(int position) {
return drugList.get(position);
}
#Override
public long getItemId(int id) {
return id;
}
private class ViewHolder {
TextView mVendorName;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
ViewHolder viewHolder;
Drug item = drugList.get(position);
if (view == null) {
viewHolder = new ViewHolder();
view = inflater.inflate(R.layout.item_drug,
parent, false);
viewHolder.mVendorName = (TextView) view
.findViewById(R.id.item_drug_drug_name);
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
}
viewHolder.mVendorName.setText(item.getDrug_name());
return view;
}
#Override
public Filter getFilter() {
if (drugsFilter == null) {
drugsFilter = new DrugsFilter();
}
return drugsFilter;
}
public class DrugsFilter extends Filter {
#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 = drugListOrig;
results.count = drugListOrig.size();
} else {
// We perform filtering operation
List<Drug> sList = new ArrayList<>();
for (Drug p : drugList) {
if (p.getDrug_name().toUpperCase()
.startsWith(constraint.toString().toUpperCase()))
sList.add(p);
}
results.values = sList;
results.count = sList.size();
}
return results;
}
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint,
FilterResults results) {
if (results.count == 0)
notifyDataSetInvalidated();
else {
drugList = (List<Drug>) results.values;
notifyDataSetChanged();
}
}
}
}
This part is for EditText and TextWatcher
String m;
mDrugEditText.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
if (count < before) {
adapter.resetData();
adapter.notifyDataSetChanged();
}
adapter.getFilter().filter(s.toString());
}
#Override
public void beforeTextChanged(CharSequence s, int start,
int before, int count) {
if (s.length() == 0 || s.length() == 1) {
mDrugEditText.invalidate();
}
if (s.length() == 3) {
if (mDrugEditText
.isPerformingCompletion()) {
return;
}
adapter.resetData();
adapter.notifyDataSetChanged();
}
}
#Override
public void afterTextChanged(Editable s) {
m = s.toString();
adapter.getFilter().filter(s.toString());
}
});
I am assuming that you have gone through the basic search options available in android/java and you are not satisfied with the results.
If you do not want to go through entire list at every text change, the only way is to implement a datastructure which does that.
The obvious solution will be trie.read this to get an idea about trie
Now, this works on the concept of pre-processing the data before searching. Since you have limited elements - it will not take much time, and you can possibly do it when the page loads.
Steps -
- Process and index all elements on load. Put indexes on a k-ary tree (it will be 32-ary, every character will be an alphabet).
- on text changed - traverse to the node and get the count. It will take O(1).
I believe this is the fastest you can go.
The above will work best if you have words indexed or if you just have to do startswith.
Sa Qada's answer is a very good approach, However, my below answer gave me better performance in my case.
autoCompleteTextViewCheckRisk.setAdapter(new ArrayAdapter<String>
(CheckRiskActivity.this, R.layout.auto_text_row, druglist));
//druglist is the Arraylist of String.
autoCompleteTextViewCheckRisk.setThreshold(1);
Text Change Listener:
autoCompleteTextViewCheckRisk.addTextChangedListener(new TextWatcher() {
#Override
public void afterTextChanged(Editable s) {
filter(druglist, s.toString());
}
#Override
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start,
int before, int count) {
}
});
Method for Filter:
private void filter(ArrayList<String> originalArrayList, String query) {
query = query.toLowerCase();
filteredArrayList.clear();
//filtered arraylist is also Arraylist of String, Just declared as global
for (String itemName : originalArrayList) {
final String text = itemName.toLowerCase();
if (text.startsWith(query)) {
filteredArrayList.add(itemName);
}
}
if (filteredArrayList.size() == 0) {
Log.i(TAG, "filter: No data found");
}
}

Android Filter ListView by BaseAdapter

i'm try to write simple filtering ListView by this below code. but that does not work correctly
in this code i'm using ContactListStructure class structure as:
public class ContactListStructure implements Serializable {
public Long id;
public String name;
public String mobile;
public Bitmap photo;
public Boolean checked;
...
}
and i'm fill this class as an ArrayList with phone contacts. after fill that i'm set this list into ListView without any problem,then i'm try to fillter this list by:
/* private ArrayList<ContactListStructure> contact_item; */
contactsAdapter = new ContactsAdapter ( G.contact_item , ActivityContactList.this);
lstContent.setAdapter ( contactsAdapter );
search_contacts.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
contactsAdapter.getFilter().filter(s.toString());
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count,int after) {
}
#Override
public void afterTextChanged(Editable s) {
}
});
POST UPDATE
search_contacts is an EditText and after type into that ListView dont filter and dont any change. my BaseAdapter :
public class ContactsAdapter extends BaseAdapter implements Filterable {
private Context mCtx=null;
private ArrayList<ContactListStructure> mData=null;
private ArrayList<ContactListStructure> arraylist;
private List<ContactListStructure> worldpopulationlist = null;
private ItemFilter mFilter = new ItemFilter();
static int i=0;
public ContactsAdapter (ArrayList<ContactListStructure> contact_item, Context ctx) {
mData=contact_item;
mCtx=ctx;
this.worldpopulationlist = contact_item;
}
#Override
public int getCount() {
return mData.size();
}
#Override
public ContactListStructure getItem(int position) {
return worldpopulationlist.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView==null) {
LayoutInflater inflator=((Activity)mCtx).getLayoutInflater();
convertView=inflator.inflate(R.layout.send_sms,null);
}
CheckBox chk_name_mobile = (CheckBox)convertView.findViewById(R.id.chk_name_mobile);
ImageView photo = (ImageView)convertView.findViewById(R.id.photo);
String name=mData.get(position).name;
chk_name_mobile.setText(name);
if( mData.get(position).photo == null )
photo.setImageDrawable( G.context.getResources().getDrawable(R.drawable.user) );
else
photo.setImageBitmap(mData.get(position).photo);
return convertView;
}
#Override
public Filter getFilter () {
return mFilter;
}
private class ItemFilter extends Filter {
#Override
protected FilterResults performFiltering(CharSequence constraint) {
String filterString = constraint.toString();
FilterResults results = new FilterResults();
final ArrayList<ContactListStructure> list = mData;
int count = list.size();
final ArrayList<ContactListStructure> nlist = new ArrayList<ContactListStructure>(count);
String filterableString ;
for (ContactListStructure wp : worldpopulationlist)
{
if (wp.getName().contains(filterString))
{
//Log.e ("wp: ", String.valueOf ( wp ) );
ContactListStructure item = new ContactListStructure();
item.id = wp.id;
item.name = wp.name;
item.mobile = wp.mobile;
item.photo = wp.photo;
nlist.add(item);
}
}
results.values = nlist;
results.count = nlist.size();
return results;
}
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
arraylist = (ArrayList<ContactListStructure>) results.values;
notifyDataSetChanged();
}
}
}
UPDATE POST:
after help in comments my notifyDataSetChanged not working. after type into edit text thats can be find but listview dont refresh.
You can try following this question here: ListView is blank while using getFilter function
It was able to filter based on a choice from the ListView.
I think you forgot to add the data returned from result to your mData :
protected void publishResults(CharSequence constraint, FilterResults results) {
arraylist = (ArrayList<ContactListStructure>) results.values;
mData.clear();
for(int i = 0, i < arraylist.size(); i++)
mData.add(arraylist.get(i));
notifyDataSetChanged();
}
}

List View Filter Android

I have created a list view in android and I want to add edit text above the list and when the user enter text the list will be filtered according to user input
can anyone tell me please if there is a way to filter the list adapter in android ?
Add an EditText on top of your listview in its .xml layout file.
And in your activity/fragment..
lv = (ListView) findViewById(R.id.list_view);
inputSearch = (EditText) findViewById(R.id.inputSearch);
// Adding items to listview
adapter = new ArrayAdapter<String>(this, R.layout.list_item, R.id.product_name, products);
lv.setAdapter(adapter);
inputSearch.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence cs, int arg1, int arg2, int arg3) {
// When user changed the Text
MainActivity.this.adapter.getFilter().filter(cs);
}
#Override
public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { }
#Override
public void afterTextChanged(Editable arg0) {}
});
The basic here is to add an OnTextChangeListener to your edit text and inside its callback method apply filter to your listview's adapter.
EDIT
To get filter to your custom BaseAdapter you"ll need to implement Filterable interface.
class CustomAdapter extends BaseAdapter implements Filterable {
public View getView(){
...
}
public Integer getCount()
{
...
}
#Override
public Filter getFilter() {
Filter filter = new Filter() {
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
arrayListNames = (List<String>) results.values;
notifyDataSetChanged();
}
#Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
ArrayList<String> FilteredArrayNames = new ArrayList<String>();
// perform your search here using the searchConstraint String.
constraint = constraint.toString().toLowerCase();
for (int i = 0; i < mDatabaseOfNames.size(); i++) {
String dataNames = mDatabaseOfNames.get(i);
if (dataNames.toLowerCase().startsWith(constraint.toString())) {
FilteredArrayNames.add(dataNames);
}
}
results.count = FilteredArrayNames.size();
results.values = FilteredArrayNames;
Log.e("VALUES", results.values.toString());
return results;
}
};
return filter;
}
}
Inside performFiltering() you need to do actual comparison of the search query to values in your database. It will pass its result to publishResults() method.
Implement your adapter Filterable:
public class vJournalAdapter extends ArrayAdapter<JournalModel> implements Filterable{
private ArrayList<JournalModel> items;
private Context mContext;
....
then create your Filter class:
private class JournalFilter extends Filter{
#Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults result = new FilterResults();
List<JournalModel> allJournals = getAllJournals();
if(constraint == null || constraint.length() == 0){
result.values = allJournals;
result.count = allJournals.size();
}else{
ArrayList<JournalModel> filteredList = new ArrayList<JournalModel>();
for(JournalModel j: allJournals){
if(j.source.title.contains(constraint))
filteredList.add(j);
}
result.values = filteredList;
result.count = filteredList.size();
}
return result;
}
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
if (results.count == 0) {
notifyDataSetInvalidated();
} else {
items = (ArrayList<JournalModel>) results.values;
notifyDataSetChanged();
}
}
}
this way, your adapter is Filterable, you can pass filter item to adapter's filter and do the work.
I hope this will be helpful.
In case anyone are still interested in this subject, I find that the best approach for filtering lists is to create a generic Filter class and use it with some base reflection/generics techniques contained in the Java old school SDK package. Here's what I did:
public class GenericListFilter<T> extends Filter {
/**
* Copycat constructor
* #param list the original list to be used
*/
public GenericListFilter (List<T> list, String reflectMethodName, ArrayAdapter<T> adapter) {
super ();
mInternalList = new ArrayList<>(list);
mAdapterUsed = adapter;
try {
ParameterizedType stringListType = (ParameterizedType)
getClass().getField("mInternalList").getGenericType();
mCompairMethod =
stringListType.getActualTypeArguments()[0].getClass().getMethod(reflectMethodName);
}
catch (Exception ex) {
Log.w("GenericListFilter", ex.getMessage(), ex);
try {
if (mInternalList.size() > 0) {
T type = mInternalList.get(0);
mCompairMethod = type.getClass().getMethod(reflectMethodName);
}
}
catch (Exception e) {
Log.e("GenericListFilter", e.getMessage(), e);
}
}
}
/**
* Let's filter the data with the given constraint
* #param constraint
* #return
*/
#Override protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
List<T> filteredContents = new ArrayList<>();
if ( constraint.length() > 0 ) {
try {
for (T obj : mInternalList) {
String result = (String) mCompairMethod.invoke(obj);
if (result.toLowerCase().startsWith(constraint.toString().toLowerCase())) {
filteredContents.add(obj);
}
}
}
catch (Exception ex) {
Log.e("GenericListFilter", ex.getMessage(), ex);
}
}
else {
filteredContents.addAll(mInternalList);
}
results.values = filteredContents;
results.count = filteredContents.size();
return results;
}
/**
* Publish the filtering adapter list
* #param constraint
* #param results
*/
#Override protected void publishResults(CharSequence constraint, FilterResults results) {
mAdapterUsed.clear();
mAdapterUsed.addAll((List<T>) results.values);
if ( results.count == 0 ) {
mAdapterUsed.notifyDataSetInvalidated();
}
else {
mAdapterUsed.notifyDataSetChanged();
}
}
// class properties
private ArrayAdapter<T> mAdapterUsed;
private List<T> mInternalList;
private Method mCompairMethod;
}
And afterwards, the only thing you need to do is to create the filter as a member class (possibly within the View's "onCreate") passing your adapter reference, your list, and the method to be called for filtering:
this.mFilter = new GenericFilter<MyObjectBean> (list, "getName", adapter);
The only thing missing now, is to override the "getFilter" method in the adapter class:
#Override public Filter getFilter () {
return MyViewClass.this.mFilter;
}
All done! You should successfully filter your list - Of course, you should also implement your filter algorithm the best way that describes your need, the code bellow is just an example.. Hope it helped, take care.

android custom list view adapter doesnt update number of views in getView

I have a listView with a custom adapter.
I have an edit text view in my activity where the user can type to filter the content on the list view.
Lets say I have two items on the list view. After doing the filter, the ArrayList is reduced to one element.
The problem is that the getView method is still executed for the two previous rows.
Here is my code.
ListActivity:
public class TeamsListActivity extends ListActivity {
private ArrayList<Team> teams = new ArrayList<Team>();
private TeamsListAdapter teamsListAdapter = null;
private EditText searchInput = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.teams_list);
// get association id from the intent.
int afId = getIntent().getExtras().getInt("afId");
Log.i(MainActivity.TAG, "id_association =" + afId);
// get the teams from the association.
this.teams = TeamsDAO.getInstance(getApplicationContext())
.getTeamsByAssociation(afId);
// inits the list adapter
teamsListAdapter = new TeamsListAdapter(this, teams);
setListAdapter(teamsListAdapter);
// gets the search input view
searchInput = (EditText) findViewById(R.id.teams_list_search_box);
searchInput.addTextChangedListener(new TextWatcher() {
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
public void afterTextChanged(Editable s) {
Log.d(Constants.TAG,"Teams search: " + s.toString());
teamsListAdapter.getFilter().filter(s);
}
});
}
}
My adapter
public class TeamsListAdapter extends ArrayAdapter<Team> implements Filterable {
private ArrayList<Team> teams = null;
private Context context = null;
public TeamsListAdapter(Context context,
ArrayList<Team> objects) {
super(context, R.layout.teams_list_row, objects);
this.context = context;
this.teams = objects;
}
#Override
public View getView(int position, View v, ViewGroup parent) {
if (v == null) {
LayoutInflater vi = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.teams_list_row, null);
}
Team t = teams.get(position);
if (t != null) {
TextView txtTeamName = (TextView) v.findViewById(R.id.teams_list_team_name);
txtTeamName.setText(t.getName());
ImageView ivTeamLogo = (ImageView) v.findViewById(R.id.teams_list_team_logo);
ivTeamLogo.setImageResource(R.drawable.af_braga);
}
return v;
}
#Override
public Filter getFilter() {
return new Filter() {
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
Log.d(Constants.TAG, "**** Search RESULTS for: " + constraint);
teams = (ArrayList<Team>) results.values;
Log.d(Constants.TAG,"size:"+teams.size());
TeamsListAdapter.this.notifyDataSetChanged();
}
#Override
protected FilterResults performFiltering(CharSequence constraint) {
Log.d(Constants.TAG, "**** PERFORM TEAK FILTERING for: " + constraint);
ArrayList<Team> filteredResults = getFilteredResults(constraint);
FilterResults results = new FilterResults();
results.values = filteredResults;
return results;
}
/**
* filters the teamsList
* #param constraint String The text to search
* #return ArrayList<Team>
*/
private ArrayList<Team> getFilteredResults(CharSequence constraint) {
ArrayList<Team> teams = TeamsListAdapter.this.teams;
ArrayList<Team> filteredTeams = new ArrayList<Team>();
for(int i=0;i< teams.size();i++){
if(teams.get(i).getName().toLowerCase().startsWith(constraint.toString().toLowerCase())){
filteredTeams.add(teams.get(i));
}
}
return filteredTeams;
}
};
}
}
Clarification of the problem.
1.Let´s say my list starts showing two elements.
2. The user inserts some text in the edit text to filter the list content which results in a updated arrayList with only one element who matches the query.
3. After called the notifyDataSetChanged the getView still is called like there was two rows in the dataset resulting in IndexOutOfBoundsException when executing Team t = teams.get(position) becuase the arrayList has only one element now so when position = 1 => the get(postion) fails.
Hope its clear now.
Well I fix my problem.
I needed to override a couple of methods in my adapter like this.
#Override
public int getCount() {
return teams.size();
}
#Override
public Team getItem(int position) {
return teams.get(position);
}
#Override
public long getItemId(int position) {
return teams.get(position).getId();
}
Now i have discoreved a problem with my search implementation. Because i am updating the original dataset the search only works one time. If I try to go back to original list i cant because my original data array has been modified. Easy to solve with with a database query instead of filtering the array list.
I think you dont need
teams = (ArrayList) results.values;
in publishResults function.

Categories

Resources