Here is the code of my filter:
private class NameFilter extends Filter {
#Override
protected FilterResults performFiltering(CharSequence constraint) {
// NOTE: this function is *always* called from a background thread,
// and
// not the UI thread.
constraint = constraint.toString().toLowerCase();
FilterResults result = new FilterResults();
if (constraint != null && constraint.toString().length() > 0) {
ArrayList<Place> filt = new ArrayList<Place>();
ArrayList<Place> lItems = new ArrayList<Place>();
synchronized (this) {
lItems.addAll(objects);
}
for (int i = 0, l = lItems.size(); i < l; i++) {
Place m = lItems.get(i);
if (m.getName().toLowerCase().contains(constraint))
filt.add(m);
}
result.count = filt.size();
result.values = filt;
}
else {
synchronized (this) {
result.values = objects;
result.count = objects.size();
}
}
return result;
}
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint,
FilterResults results) {
// NOTE: this function is *always* called from the UI thread.
filtered = (ArrayList<Place>) results.values;
notifyDataSetChanged();
clear();
for (int i = 0, l = filtered.size(); i < l; i++)
add(filtered.get(i));
notifyDataSetInvalidated();
}
}
Here is my activity code:
lvPlace = (ListView) findViewById(R.id.listView1);
final ArrayList<Place> searchResults = GetSearchResults();
filterEditText = (EditText) findViewById(R.id.filter_text);
filterEditText.addTextChangedListener(filterTextWatcher);
adapter = new PlaceAdapter(this, 0, searchResults);
lvPlace.setAdapter(adapter);
lvPlace.requestFocus();
lvPlace.setTextFilterEnabled(true);
private TextWatcher filterTextWatcher = 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) {
if (adapter != null) {
adapter.getFilter().filter(s.toString().toLowerCase());
filterEditText.getText().toString();
} else {
Log.d("filter", "no filter availible");
}
}
};
#Override
protected void onDestroy() {
super.onDestroy();
filterEditText.removeTextChangedListener(filterTextWatcher);
}
I've struggled with this for a while becasue when I was typing something in the search box it was working fine, but once I've removed the text from the search field, the list doesn't return to the initial state.
Please help me solve this problem!
Try this in your publishResults method:
protected void publishResults(CharSequence constraint,FilterResults results)
{
filtered.clear();
filtered = (ArrayList<Place>) results.values;
if(filtered.size() < 1)
filtered = objects;
notifyDataSetChanged();
for (int i = 0, l = filtered.size(); i < l; i++)
add(filtered.get(i));
notifyDataSetInvalidated();
}
With this code, you can take out the else clause in your filter method.
As an extra, you can also make your for loop a bit more efficient by using a loop like this:
for(Place place : filtered)
add(place);
when I was typing something in the search box it was working fine, but
once I've removed the text from search field list was not returning to
initial state.
Without seeing the code for your adapter, this most likely happens because you don't keep a reference to the initial values that are used by the adapter. For example if you set an ArrayList with data into the adapter and start to enter characters in the filtering EditText this will work because by filtering you're using only a subset of the previous set of values(which will always be available). When you delete a character from the EditText you need to do the filtering on the previous set of values, but as that set was replaced the values don't exist anymore and the filtering breaks.
The solution is to make a copy of the initial values to always have the complete ArrayList of values to do the filtering. One copy of the ArrayList will be used by the adapter(this will be the ArrayList that gets replaced in publishResults with the results of the filtering) and one will be used to do the filtering on by the filter(this ArrayList will remain the same!) in the performFiltering method.
Related
I do a method to filter a ListView with the text inside of an EditText. My problem is that I put chars inside the edittext, there is a moment which there is no results, but I don't know how to clean the listview.
My textwatcher:
search_watcher = new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (count < before) {
// We're deleting char so we need to reset the adapter data
adapter.resetData();
}
adapter.getFilter().filter(s.toString());
...
The adapter is:
private class FarmacoFilter extends Filter {
#Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
// We implement here the filter logic
String str_search = constraint.toString();
if (str_search == null || str_search.length() == 0) {
// No filter implemented we return all the list
results.values = farmList;
results.count = farmList.size();
}
else {
// We perform filtering operation
List<PojoFarmaco> nFarmList = new ArrayList<PojoFarmaco>();
for (PojoFarmaco p : farmList) {
if (p.getName().toUpperCase().contains(str_search.toUpperCase()) || p.getAmpolla().toUpperCase().contains(str_search.toUpperCase()))
nFarmList.add(p);
}
results.values = nFarmList;
results.count = nFarmList.size();
}
return results;
}
#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 {
farmList = (List<PojoFarmaco>) results.values;
notifyDataSetChanged();
}
}
}
Example:
If I write, "GOO", I will find "GOOGLE" and other results, but If I carry on writing more chars like "GOOZZZ", I will find the latest results.
What I need to do? Thanks for your advices.
you should keep the original dataset intact. In this moment you are overriding it in this line
farmList = (List<PojoFarmaco>) results.values;
If we call farmListOriginal the original dataSet and farmList a copy of it, you could easily fix it like
if (results.count == 0)
farmList = new ArrayList<>(farmListOrig);
else {
farmList = (List<PojoFarmaco>) results.values;
}
notifyDataSetChanged();
my assumption is that farmList is an ArrayList
The solution is to edit the publishResults of the getFilter:
#Override
protected void publishResults(CharSequence constraint,
FilterResults results) {
// Now we have to inform the adapter about the new list filtered
if (results.count == 0){
resetData();
farmList = (List<PojoFarmaco>) results.values;
}
else {
farmList = (List<PojoFarmaco>) results.values;
}
notifyDataSetChanged();
}
It's the same solution as Blackbelt said, but I have the resetData method to revert to original data.
My problem is the following: i want to implement a search mechanism into my JSON-data generated ListView, but i don't know what to do in my case.
I've found a lot of examples but i want to implement the simplest solution possible and, especially, an understandable one.
Here's my Pastebin with the Adapter and the MainActivity class.
http://pastebin.com/SSXHXK7m
Can you give me any suggestion? I'm stuck.
The search functionality would be something like this
public void searchList(String query){
List<Records> matchedActors = new ArrayList<Records>();
for(int i = 0; i < adapter.getCount(); i++){
if(adapter.getItem(i).getAuthor().equals(query)
matchedActors.add(adapter.getItem(i));
}
// See below for what to put here
}
You can then either modify the adapter by creating/calling a method in your adapter class that is something like
public void modifyList(List<Records> actorList){
this.actorList = actorList;
notifyDataSetChanged();
}
and call adapter.modifyList(matchedActors) after your search, or by instantiating a new instance of your adapter after your serach via,
adapter = new RecordsAdapter(MainActivity.this, R.layout."your_layout_resource", actorList);
listview.setAdapter(adapter);
EDIT
I didn't notice you were using a Filterable ArrayAdapter :P You can implement the following funcitionality in your adapter.
#Override
public Filter getFilter() {
Filter filter = new Filter() {
#Override
protected void publishResults(CharSequence constraint,FilterResults results) {
actorList = (List<Records>) results.values;
notifyDataSetChanged();
}
#Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
List<Records> matchedActors = new ArrayList<Records>();
//NOTE mOriginalValues will be a class variable we use to keep track of our values
if (mOriginalValues == null) {
mOriginalValues = new ArrayList<Records>(actorList);
}
if (constraint == null || constraint.length() == 0) {
results.count = mOriginalValues.size();
results.values = mOriginalValues;
}
else {
constraint = constraint.toString().toLowerCase();
List<Records> matchedActors = new ArrayList<Records>();
for(int i = 0; i < getCount(); i++){
if(mOriginalValues.getAuthor().toLowerCase().startsWith(constraint)
matchedActors.add(getItem(i);
}
// set the Filtered result to return
results.count = matchedActors.size();
results.values = matchedActors;
}
return results;
}
return filter;
}
I display a friend list in a ListView and use a TextWatcher set on an EditText to allow search. This works with 1 limitation (which I am not getting): typing a character correctly filters the list, removing a char however doesn't do anything. The original list is correctly displayed when the input is empty, but the Filter is also supposed to work when a character gets removed, since onTextChanged() gets invoked. Where am I going wrong?
Class (BaseAdapter) fields:
private ArrayList<User> friends;
private ArrayList<User> originalFriendList;
//to keep track of the original list
this.originalFriendList = friends;
Filter class:
private class FriendSearchFilter extends Filter {
#Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
if (constraint != null && constraint.length() > 0) {
ArrayList<User> filterList = new ArrayList<User>();
for (User friend : friends) {
if (StringUtils.containsIgnoreCase(friend.getLast_name(), constraint)
|| StringUtils.containsIgnoreCase(friend.getFirst_name(), constraint)) {
filterList.add(friend);
}
}
results.count = filterList.size();
results.values = filterList;
} else {
// the input is empty
results.count = originalFriendList.size();
results.values = originalFriendList;
}
return results;
}
#Override
#SuppressWarnings("unchecked")
protected void publishResults(CharSequence constraint, FilterResults results) {
friends = (ArrayList<User>) results.values;
notifyDataSetChanged();
}
}
TextWatcher:
etSearch.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int start,
int before, int count) {
/* no action required */
}
#Override
public void onTextChanged(CharSequence charSequence, int start, int count, int after) {
if(mFriendListFrag == null || !mFriendListFrag.equals(mFragAdapter.getItem(mViewPager.getCurrentItem())));
mFriendListFrag = (FriendListFragment) mFragAdapter.getItem(mViewPager.getCurrentItem());
mFLAdapter = (FriendListAdapter) mFriendListFrag.getListAdapter();
mFLAdapter.getFilter().filter(charSequence.toString());
Log.e("ON", "TEXT CHANGED");
}
#Override
public void afterTextChanged(Editable editable) {
/* no action required */
}
});
just in case someone has a similar problem: I had to compare the constraint against the original list, and not the "working list" that was overwritten at the time I tried to filter backwards:
for (User friend : originalFriendList)
instead of
for (User friend : friends)
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.
I've already implemented a list view with search filter but right now, I have to changed it to expandable list view with child search filter. There would be an edit text as a search bar and filters all the child of all groups. This is the scenario,
*Person - object consist of name, address, phone number and photo.
Group 1 - Friends (Child 1 - Person, Child 2 - Person, Child 3 - Person)
Group 2 - Family (Child 1 - Person, Child 2 - Person)
Group 3 - Officemates (Child 1 - Person, Child 2 - Person, Child 3 - Person, Child 4 - Person)
As of now, I have to port from array adapter to base expandable list adapter and filterable. Can someone help me with this? Thanks.
edit = (EditText)findViewById(R.id.editText1);
edit.addTextChangedListener(filterTextWatcher);
private TextWatcher filterTextWatcher = new TextWatcher() {
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
public void afterTextChanged(Editable s) {
((Filterable) ((ListAdapter) Adapter)).getFilter().filter(edit.getText().toString());
}
};
public class ListAdapter extends BaseExpandableListAdapter implements Filterable {
public void notifyDataSetInvalidated() {
super.notifyDataSetInvalidated();
}
public Filter getFilter() {
if (filter == null)
filter = new MangaNameFilter();
return filter;
}
private class MangaNameFilter extends Filter {
#Override
protected FilterResults performFiltering(CharSequence constraint) {
// NOTE: this function is *always* called from a background thread, and
// not the UI thread.
constraint = edit.getText().toString().toLowerCase();
FilterResults result = new FilterResults();
if(constraint != null && constraint.toString().length() > 0) {
detailsList = detailsSer.GetAlldetails();
dupCatList = detailsList;
ArrayList<detailsEntity> filt = new ArrayList<detailsEntity>();
ArrayList<detailsEntity> lItems = new ArrayList<detailsEntity>();
synchronized(this) {
lItems.addAll(dupCatList);
}
for(int i = 0, l = lItems.size(); i < l; i++) {
detailsEntity m = lItems.get(i);
if (m.description.toLowerCase().contains(constraint))
filt.add(m);
}
result.count = filt.size();
result.values = filt;
} else {
detailsList = detailsSer.GetAlldetails();
dupCatList = detailsList;
synchronized(this) {
result.count = dupCatList.size();
result.values = dupCatList;
}
}
return result;
}
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint, FilterResults result) {
// NOTE: this function is *always* called from the UI thread.
filtered = (ArrayList<detailsEntity>)result.values;
ArrayList<Integer> IdList = new ArrayList<Integer>();
IdList.clear();
for(int i = 0; i < filtered.size(); i++) {
IdList.add(filtered.get(i).catID);
}
HashSet<Integer> hashSet = new HashSet<Integer>(IdList);
midList = new ArrayList<Integer>(hashSet) ;
Collections.sort(midList);
Adapter = new CategoryListAdapter(context, R.layout.list1, R.layout.list2, filtered, midList);
List.setAdapter(Adapter);
}
}
}