List View Filter Android

List View Filter 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.

Android SearchView Filter ListView

Place this inside your adapter:

@Override
public Filter getFilter(){
return new Filter(){

@Override
protected FilterResults performFiltering(CharSequence constraint) {
constraint = constraint.toString().toLowerCase();
FilterResults result = new FilterResults();

if (constraint != null && constraint.toString().length() > 0) {
List<String> founded = new ArrayList<String>();
for(YourListItemType item: origData){
if(item.toString().toLowerCase().contains(constraint)){
founded.add(item);
}
}

result.values = founded;
result.count = founded.size();
}else {
result.values = origData;
result.count = origData.size();
}
return result;


}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
clear();
for (String item : (List<String>) results.values) {
add(item);
}
notifyDataSetChanged();

}

}
}

And this inside constructor of your adapter

public MyAdapter(Context context, int layoutResourceId, String[] places) {
super(context, layoutResourceId, data);
this.context = context;

this.data = Arrays.asList(places);
this.origData = new ArrayList<String>(this.data);

}

Custom Listview Adapter with filter Android

You can use the Filterable interface on your Adapter, have a look at the example below:

public class SearchableAdapter extends BaseAdapter implements Filterable {

private List<String>originalData = null;
private List<String>filteredData = null;
private LayoutInflater mInflater;
private ItemFilter mFilter = new ItemFilter();

public SearchableAdapter(Context context, List<String> data) {
this.filteredData = data ;
this.originalData = data ;
mInflater = LayoutInflater.from(context);
}

public int getCount() {
return filteredData.size();
}

public Object getItem(int position) {
return filteredData.get(position);
}

public long getItemId(int position) {
return position;
}

public View getView(int position, View convertView, ViewGroup parent) {
// A ViewHolder keeps references to children views to avoid unnecessary calls
// to findViewById() on each row.
ViewHolder holder;

// When convertView is not null, we can reuse it directly, there is no need
// to reinflate it. We only inflate a new View when the convertView supplied
// by ListView is null.
if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item, null);

// Creates a ViewHolder and store references to the two children views
// we want to bind data to.
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.list_view);

// Bind the data efficiently with the holder.

convertView.setTag(holder);
} else {
// Get the ViewHolder back to get fast access to the TextView
// and the ImageView.
holder = (ViewHolder) convertView.getTag();
}

// If weren't re-ordering this you could rely on what you set last time
holder.text.setText(filteredData.get(position));

return convertView;
}

static class ViewHolder {
TextView text;
}

public Filter getFilter() {
return mFilter;
}

private class ItemFilter extends Filter {
@Override
protected FilterResults performFiltering(CharSequence constraint) {

String filterString = constraint.toString().toLowerCase();

FilterResults results = new FilterResults();

final List<String> list = originalData;

int count = list.size();
final ArrayList<String> nlist = new ArrayList<String>(count);

String filterableString ;

for (int i = 0; i < count; i++) {
filterableString = list.get(i);
if (filterableString.toLowerCase().contains(filterString)) {
nlist.add(filterableString);
}
}

results.values = nlist;
results.count = nlist.size();

return results;
}

@SuppressWarnings("unchecked")
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
filteredData = (ArrayList<String>) results.values;
notifyDataSetChanged();
}

}
}

In your Activity or Fragment where of Adapter is instantiated :

editTxt.addTextChangedListener(new TextWatcher() {

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
System.out.println("Text ["+s+"]");

mSearchableAdapter.getFilter().filter(s.toString());
}

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

}

@Override
public void afterTextChanged(Editable s) {
}
});

Here are the links for the original source and another example

How to apply search filter using startsWith on a simple listview in android

You need to use a customized adapter instead of the default one, and override the getFilter() to filter the list with the String's startsWith() method:

Adapter:

public class StartsWithArrayAdapter extends ArrayAdapter<String> implements Filterable {

private final List<String> mList = new ArrayList<>();
private List<String> mFilteredList = new ArrayList<>();

public StartsWithArrayAdapter(Context context, int textViewResourceId, ArrayList<String> list) {
super(context, textViewResourceId, list);
this.mList.addAll(list);
this.mFilteredList.addAll(list);
}

@Override
public int getCount() {
return mFilteredList.size();
}

@NonNull
@Override
public Filter getFilter() {
return new Filter() {
@Override
protected FilterResults performFiltering(CharSequence charSequence) {
String charString = charSequence.toString();
if (charString.isEmpty()) {
mFilteredList = mList;
} else {
List<String> filteredList = new ArrayList<>();
for (String listItem : mList) {
if (listItem.toLowerCase().startsWith(charString.toLowerCase())) {
filteredList.add(listItem);
}
}
mFilteredList = filteredList;
}

FilterResults filterResults = new FilterResults();
filterResults.values = mFilteredList;
return filterResults;
}

@Override
@SuppressWarnings("unchecked")
protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
mFilteredList = (ArrayList<String>) filterResults.values;
clear();
addAll(mFilteredList);
notifyDataSetChanged();
}
};

}

}

Usage in your code:

public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {


StartsWithArrayAdapter adapterListOfWord;

// rest of code

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// omitted code

adapterListOfWord = new StartsWithArrayAdapter(this, android.R.layout.simple_list_item_1, mSource);

// rest of code

}
}

Custom Filtering of ListView with a Custom List Adapter including section headers

I did not find a solution to my original problem, but I came up with a better approach to the whole situation. I didn't know there was an ExpandableListView available in Android. This is basically a ListView, but the items are divided into Groups and their Childs which are expandable and collapsable, so exactly what I wanted.

Here is how I implemented it with working filters and groups:

So, to start off, here is my main layout file. Please note that I am using Fragments, which is why the code is a bit different in terms of getting the context for example. The functionality of the component stays the same though.

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

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<EditText
android:id="@+id/fragment_data_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:hint="@string/data_search_hint"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp" />

<ExpandableListView
android:id="@+id/fragment_data_expandable_list_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:groupIndicator="@null" />

</LinearLayout>

You will also need two layout files for your header/group items and for your child items. My header item has a TextView which displays the category name and an ImageView which displays a + or - to show if the category is collapsed or expanded.

Here is my header layout file:

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

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
android:descendantFocusability="blocksDescendants" >

<TextView
android:id="@+id/fragment_data_list_view_category"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:gravity="start"
android:textStyle="bold"
android:textSize="18sp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
android:paddingTop="8dp"
android:textColor="@android:color/primary_text_light"
android:text="@string/placeholder_header_listview"
android:maxLines="1"
android:ellipsize="end" />

<ImageView
android:id="@+id/fragment_data_list_view_category_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_gravity="end"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
android:paddingTop="8dp"
android:contentDescription="@string/content_description_list_view_header"
android:src="@drawable/ic_remove_black_24dp"
android:tag="maximized"/>

</RelativeLayout>

The property android:descendantFocusability="blocksDescendants" fixed a bug when I tried setting an onItemClickListener. If you have that problem, try using RelativeLayout's for your child layout if you're not already. It fixed it for me, the onClickItemListener did not execute with a LinearLayout.

And here is my layout file for the child items:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:descendantFocusability="blocksDescendants" >

<TextView
android:id="@+id/fragment_data_list_view_carrier_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/placeholder_item_listview"
android:textSize="18sp"
android:textStyle="normal"
android:textColor="@android:color/primary_text_light"
android:maxLines="1"
android:ellipsize="end" />

</RelativeLayout>

The following code is from my fragment class, which handles all the logic for the ExpandableListView:

public class Fragment_Data extends Fragment {

private Context mContext;

private ExpandableListView expandableListView;
private List<String> categories_list;
private HashMap<String, List<Carrier>> carriers_list;
private DataExpandableListAdapter adapter;

private DatabaseHelper dbHelper;

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
getActivity().setTitle(R.string.nav_item_data);
}

This first part shows the declaration of needed variables and the necessary method onViewCreated. The Carrier class is a custom object with properties like name, category and so on. The DatabaseHelper is also a custom class which handley my database and gets all the data for me, which is casted into Carrier objects. You can of course use anything you like as data types.

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

View view = inflater.inflate(R.layout.fragment_data_layout, container, false);
mContext = getContext();
expandableListView = (ExpandableListView) view.findViewById(R.id.fragment_data_expandable_list_view);
dbHelper = new DatabaseHelper(mContext, null, null, 1);

adapter = new DataExpandableListAdapter(mContext, categories_list, carriers_list);

displayList();

expandAllGroups();

EditText searchEditText = (EditText) view.findViewById(R.id.fragment_data_search);
searchEditText.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.filterData(s.toString());
expandAllGroups();
}

@Override
public void afterTextChanged(Editable s) {

}
});

expandableListView.setOnItemLongClickListener(deleteSelectedItem);
expandableListView.setOnChildClickListener(editSelectedItem);

return view;
}

The onCreate method deals with all the important stuff like setting the adapter, inflating the layout and setting onClick events for the items and a onTextChange event for the search field.

private void expandAllGroups() {
for(int i = 0; i < adapter.getGroupCount(); i++) {
expandableListView.expandGroup(i);
}
}

private void displayList() {
prepareListData();

adapter = new DataExpandableListAdapter(mContext, categories_list, carriers_list);
expandableListView.setAdapter(adapter);

expandAllGroups();
}

private void prepareListData() {
categories_list = new ArrayList<>();
carriers_list = new HashMap<>();

categories_list = dbHelper.getCategoryList();

for(int i = 0; i < categories_list.size(); i++) {
List<Carrier> carrierList = dbHelper.getCarriersWithCategory(categories_list.get(i));
carriers_list.put(categories_list.get(i), carrierList);
}
}

With expandAllGroups() you can simply expand all groups, because they are collapsed by default. The displayList() simply sets the Adapter for the ExpandableListView and calls prepareListData(), which fills both the category (group) list and the carrier (child) list. Note that the child List is a hashmap with the key being the category and the value a Carrier List by itself, so the Adapter knows which child items belong to which parent.

Here is the code for the Adapter:

class DataExpandableListAdapter extends BaseExpandableListAdapter {

private Context mContext;
private List<String> list_categories = new ArrayList<>();
private List<String> list_categories_original = new ArrayList<>();
private HashMap<String, List<Carrier>> list_carriers = new HashMap<>();
private HashMap<String, List<Carrier>> list_carriers_original = new HashMap<>();

DataExpandableListAdapter(Context context, List<String> categories, HashMap<String, List<Carrier>> carriers) {
this.mContext = context;
this.list_categories = categories;
this.list_categories_original = categories;
this.list_carriers = carriers;
this.list_carriers_original = carriers;
}

You need to have a copy of both of your original lists, if you want to use filtering. This is used for restoring all data when the search query is empty or again or simply different. The filter deletes all items that do not match from the original list.

@Override
public int getGroupCount() {
return this.list_categories.size();
}

@Override
public int getChildrenCount(int groupPosition) {
return this.list_carriers.get(this.list_categories.get(groupPosition)).size();
}

@Override
public Object getGroup(int groupPosition) {
return this.list_categories.get(groupPosition);
}

@Override
public Object getChild(int groupPosition, int childPosition) {
return this.list_carriers.get(this.list_categories.get(groupPosition)).get(childPosition);
}

@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}

@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}

@Override
public boolean hasStableIds() {
return true;
}

@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}

Those methods need to be overwritten when you expand the BaseExpandableListAdapter. You can replace all the return null; statements with something similar like this, depending on your data.

@SuppressLint("InflateParams")
@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {

String headerTitle = (String) getGroup(groupPosition);

if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) this.mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.listview_header_data_layout, null);
}

TextView lblListHeader = (TextView) convertView.findViewById(R.id.fragment_data_list_view_category);
lblListHeader.setText(headerTitle);

ImageView expandIcon = (ImageView) convertView.findViewById(R.id.fragment_data_list_view_category_icon);
if(isExpanded) {
expandIcon.setImageResource(R.drawable.ic_remove_black_24dp);
} else {
expandIcon.setImageResource(R.drawable.ic_add_black_24dp);
}

return convertView;
}

This overriden method simply inflates the layout for each header/group/category item and sets it text and image depending on the state of the group, if it's collapsed or expanded.

@SuppressLint("InflateParams")
@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {

final String carrierName = ((Carrier)getChild(groupPosition, childPosition)).get_name();

if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) this.mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.listview_item_data_layout, null);
}

TextView txtListChild = (TextView) convertView.findViewById(R.id.fragment_data_list_view_carrier_name);

txtListChild.setText(carrierName);
return convertView;
}

Same thing with the child items.

Now finally to the filtering:
I use this custom method to filter out all items that I need matching the search query. Remember that this method is called each time the text of the EditText changes.

void filterData(String query) {
query = query.toLowerCase();
list_categories = new ArrayList<>();
list_carriers = new HashMap<>();

DatabaseHelper dbHelper = new DatabaseHelper(mContext, null, null, 1);

if(query.trim().isEmpty()) {
list_categories = new ArrayList<>(list_categories_original);
list_carriers = new HashMap<>(list_carriers_original);
notifyDataSetInvalidated();
}
else {
//Filter all data with the given search query. Yes, it's complicated
List<String> new_categories_list = new ArrayList<>();
HashMap<String, List<Carrier>> new_carriers_list = new HashMap<>();
List<String> all_categories_list = dbHelper.getCategoryList();
for(int i = 0; i < all_categories_list.size(); i++) {
List<Carrier> carriersWithCategoryList = dbHelper.getCarriersWithCategory(all_categories_list.get(i));
List<Carrier> matchingCarriersInCategory = new ArrayList<>();
for(Carrier carrierInCategory : carriersWithCategoryList) {
if(carrierInCategory.get_name().toLowerCase().contains(query)) {
matchingCarriersInCategory.add(carrierInCategory);
if(!new_categories_list.contains(all_categories_list.get(i))) {
new_categories_list.add(all_categories_list.get(i));
}
}
}
new_carriers_list.put(all_categories_list.get(i), matchingCarriersInCategory);
}

if(new_categories_list.size() > 0 && new_carriers_list.size() > 0) {
list_categories.clear();
list_categories.addAll(new_categories_list);
list_carriers.clear();
list_carriers.putAll(new_carriers_list);
}

notifyDataSetChanged();
}
}`

This might be very confusing, but it needs to be that complicated in my case because of my data structure. It might be easier in your case.

What this basically does is, that it first checks if the search query is empty. And if it is empty it resets both lists to the "backup" lists which I assigned in the constructor. I then call notifyDataSetInvalidated(); to tell the Adapter that it's content will be refilled. It might work aswell with notifyDataSetChanged();, I didn't test that, but it should since we set the original lists back to their old state.

Now, if the search query is not empty I go through every category and see if that specific category has any items that match the search query. If that is the case, that item is added to a new child list and it's category/parent will also be added to a new parent list, if it's not already in there.

And last but not least, the method checks if both lists are not empty. If they are not empty, the original lists are emptied and the new, filtered data, is put in and the Adapter is notified by calling notifyDataSetChanged();

I hope this will help anyone.

Listview custom filter gives wrong item on-clicked in filtered list

I found problem in method (check comments):

private void Filter(String text) {
for (Employee post : employeeArrayList) {
String filterPattern = text.toLowerCase().trim();
if (post.getnumber().toLowerCase().contains(filterPattern)) {
filterList.add(post);
}
}
//below every time you create new instance of MyAdapter - try comment this line (below) and test it
removeView.setAdapter(new MyAdapter(getApplicationContext(), filterList));
//this is your first instance but you set new adapter for listView
adapter.notifyDataSetChanged();
}

and replace your method with this:

private void Filter(String text) {
for (Employee post : employeeArrayList) {
String filterPattern = text.toLowerCase().trim();
if (post.getnumber().toLowerCase().contains(filterPattern)) {
filterList.add(post);
}
}
adapter.clear()
adapter.addAll(filterList);
}

So you don't create a new instance of the adapter every time, just swap data of the existing adapter

I haven't tested it but it should work fine with OnItemClickListener...

EDITED

Also same problem...

@Override
public void afterTextChanged(Editable s) {
filterList.clear();
if (s.toString().isEmpty()) {
//you set new instance of adapter and notify old, try comment this line (below) and test it
//removeView.setAdapter(new MyAdapter(getApplicationContext(), employeeArrayList));
adapter.notifyDataSetChanged();
} else {
Filter(s.toString());
}
}


Change to this

        @Override
public void afterTextChanged(Editable s) {
if (!s.toString().isEmpty()) {
filterList.clear(); //TODO here is problem, move this to your filter method
Filter(s.toString());
} else {
//Do nothing, if the EditText field is empty the list will remain filled
}
}

There are other ways to solve this and improve but I don't want to confuse you, because android is new experience for you

EDITED

You are using an ArrayAdapter but I thought it is RecyclerView, forget swapData method and try new changes... Sorry

adapter.clear()
adapter.addAll(filterList);

Filter a custom Listview using Buttons (Android)

Use two arrays in the Adapter. Use one for storage and one for currently shown items.

                   

							       

Related Topics



Leave a reply



Submit