I'm trying to add the search functionality to a ListView that has a custom adapter. When I type something in the EditText it searches and shows the results corectly but if I try to erase what I just wrote it won't come out with the initial list, it will stay with the already filtered list.
Here is the code :
In MainActivity :
private TextWatcher searchTextWatcher = new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
adapter.getFilter().filter(s.toString());
adapter.notifyDataSetChanged();
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void afterTextChanged(Editable s) {
}
};
In LazyAdapter :
public Filter getFilter() {
return new Filter() {
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
data = (ArrayList<HashMap<String, String>>) results.values;
LazyAdapter.this.notifyDataSetChanged();
}
#Override
protected FilterResults performFiltering(CharSequence constraint) {
ArrayList<HashMap<String, String>> filteredResults = getFilteredResults(constraint);
FilterResults results = new FilterResults();
results.values = filteredResults;
return results;
}
};
}
protected ArrayList<HashMap<String, String>> getFilteredResults(
CharSequence constraint) {
ArrayList<HashMap<String, String>> filteredTeams = new ArrayList<HashMap<String, String>>();
for(int i=0;i< data.size();i++){
if(data.get(i).get(MainActivity.KEY_TITLE).toLowerCase().startsWith(constraint.toString().toLowerCase())){
filteredTeams.add(data.get(i));
}
}
return filteredTeams;
}
What is wrong with my code?
Thank you!
Realize that when you filter you are replacing your unfiltered results in your ArrayList with your filtered results. When you hit backspace to delete characters you are trying to now filter based on your already filtered list which is why your results won't change. You will need to keep a reference to your original data set that doesn't have any filter applied to it and always filter using that, but never change/replace it.
Related
I used RecyclerView and custom Adapter in my app ... adapter be implements Filterable for search. How to set high light search text ?
This my code for filterable in custom adapter :
private Filter filterResult = new Filter() {
#Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults filterResults = new FilterResults();
ArrayList<Moment> tempList = new ArrayList<>();
if (MOMENT_FILTER != null) {
if (TextUtils.isEmpty(constraint)) {
tempList = (ArrayList<Moment>) MOMENT_FILTER;
} else {
int length = MOMENT_LIST.size();
int i = 0;
while (i < length) {
Moment item = MOMENT_FILTER.get(i);
if (item.getMoment().contains(constraint))
tempList.add(item);
i++;
}
}
}
filterResults.values = tempList;
filterResults.count = tempList.size();
return filterResults;
}
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
MOMENT_LIST = (ArrayList<Moment>) results.values;
if (results.count > 0) {
notifyDataSetChanged();
}
}
};
#Override
public Filter getFilter() {
return filterResult;
}
And this my code in activity for text change (EditText) :
EDT_SEARCH.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
adapter.getFilter().filter(s);
}
#Override
public void afterTextChanged(Editable s) {
}
});
result is OK with Sahil answer!
Have a reference of the searchText in your adapter
then in your OnBindViewHolder you can do as
String text = list.get(position).getText(); // Your getter Method
String htmlText = text.replace(searchText,"<font color='#c5c5c5'>"+searchText+"</font>");
// Only searchText would be displayed in a different color.
holder.textView.setText(Html.fromHtml(htmlText );
I just got lost. I'm populating database using SimpleCursorAdapter to listview. I'd like to filter it with EditText, but every question/code/example/tutorial is simply not working for me. But I've seen many using ArrayAdapter.The question is that, can I populate listview with a SimpleCursorAdapter and them filter it using EditText with an ArrayAdapter?
You must use filter method for filter with edittext.Like above,
kisiText.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
if(s.toString().isEmpty()){
kisiInfoList.clear();
listeyiCek();
adapter = new RehberListAdapter(RehberActivity.this, kisiInfoList);
rehberListView.setAdapter(adapter);
}
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
// TODO Auto-generated method stub
}
#Override
public void afterTextChanged(Editable s) {
adapter.getFilter().filter(s.toString());
}
});
And here is a filter method that your adapter includes,
#Override
public Filter getFilter() {
return new Filter() {
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint,
FilterResults results) {
if (results.count == 0)
notifyDataSetInvalidated();
else {
kisiList = (List<Kisi>) results.values;
notifyDataSetChanged();
}
}
#Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
if (constraint == null || constraint.length() == 0) {
results.values = kisiList;
results.count = kisiList.size();
} else {
List<Kisi> nPlanetList = new ArrayList<Kisi>();
for (Kisi p : kisiList) {
if (p.getAdSoyad()
.toUpperCase()
.startsWith(constraint.toString().toUpperCase()))
nPlanetList.add(p);
}
results.values = nPlanetList;
results.count = nPlanetList.size();
}
return results;
}
};
}
When you enter text it is filtering and restricting your listView
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 use the following code to filter my ArrayAdapter and I call setAdapter after the filtering is done. For some reason the unfiltered data flashes on the screen before the filter is applied. Any ideas?
mList = getData();
Collections.sort(mList);
mAdapter = new CustomItem(getActivity(), R.layout.list_item, mList);
String filter = mSpinner1.getSelectedItem().toString();
mAdapter.getFilter().filter(filter);
mListView.setAdapter(mAdapter);
Here's the code in my custom Filter:
#Override
public Filter getFilter()
{
return new Filter()
{
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint, FilterResults results)
{
mSubItems = (ArrayList<CustomItem>)results.values;
if (results.count > 0)
{
notifyDataSetChanged();
}
else
{
notifyDataSetInvalidated();
}
}
#Override
protected FilterResults performFiltering(CharSequence constraint)
{
setFilter(constraint);
List<CustomItem> filteredResults = getFilteredResults();
FilterResults results = new FilterResults();
results.values = filteredResults;
results.count = filteredResults.size();
return results;
}
};
}
You may need to do some fancy footwork to make this look the way you want. When the user first selects a filtering option you could remove the listView content.
Then use a FilterListener on the filter to know when filtering is complete and show the listView content again.
mAdapter.getFilter().filter(chars, new Filter.FilterListener() {
public void onFilterComplete(int count) {
// show the listView content again;
}
});
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.