Search code examples
androidlistviewandroid-adaptertogglebuttonandroid-togglebutton

ListView Adapter with ToggleButton Filters


I'm developing an Android app that has a list of 80+ "God" objects, each having a "ClassName" string attribute ("Warrior", "Hunter", "Assassin", etc.).

I am trying to implement ToggleButtons that will filter the list by ClassName. Multiple filters should be allowed at once (example: Checking the "Warrior" and "Hunter" ToggleButtons should display all objects with ClassName "Warrior" OR "Hunter").

So far, I have only been able to filter a list based on one ClassName constraint. Any help in achieving this would be greatly appreciated!

Here is my main fragment:

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View myView =  inflater.inflate(R.layout.home,null);

        final ArrayList<God> gods = getGods();

        lv = (ListView) myView.findViewById(R.id.home_list);
        sv = (SearchView) myView.findViewById(R.id.home_search);
        sv.setQueryHint("Search Gods...");
        mage = (ToggleButton) myView.findViewById(R.id.mage);
        assassin = (Button) myView.findViewById(R.id.assassin);

        adapter = new GodAdapter(getActivity(), gods);
        lv.setAdapter(adapter);

        mage.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if(isChecked){
                    //Include all mages
                }
                else {
                    //Remove all mages

                }
            }
        });

assassin.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                    if(isChecked){
                        //Include all assassins
                    }
                    else {
                        //Remove all assassins

                    }
                }
            });

(...other buttons...)



        sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            public boolean onQueryTextSubmit(String query) {
                return false;
            }
            public boolean onQueryTextChange(String query) {
                adapter.getFilter().filter(query);
                return false;
            }
        });

        lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            public void onItemClick(AdapterView<?> arg0, View arg1, int position, long arg3) {
                String name = adapter.getItem(position).getName();

                for(int i = 0; i < gods.size(); i++) {
                    if(gods.get(i).getName().equals(name))
                    {
                        Fragment myFragment = new TabFragment();
                        myFragment.setArguments(createGodBundle(gods.get(i)));
                        replaceFragment(myFragment);
                        hideSoftKeyboard(getActivity());
                        break;
                    }
                }
            }
        });

        return myView;
    }

And here is my GodAdapter:

public class GodAdapter extends BaseAdapter implements Filterable {

    Context c;
    ArrayList<God> gods;
    ArrayList<God> filterList;
    CustomFilter filter;

    public GodAdapter(Context c, ArrayList<God> gods) {
        this.c = c;
        this.gods = gods;
        this.filterList = gods;
    }

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

    public God getItem(int position) {
        return gods.get(position);
    }

    public long getItemId(int position) {
        return gods.indexOf(getItem(position));
    }

    public View getView(int position, View convertView, ViewGroup parent) {


        LayoutInflater inflater = (LayoutInflater) c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        if(convertView == null)
            convertView = inflater.inflate(R.layout.god_selection, null);

        TextView name = (TextView) convertView.findViewById(R.id.god_name);
        ImageView image = (ImageView) convertView.findViewById(R.id.god_image);
        ImageView pantheon =(ImageView) convertView.findViewById(R.id.pantheon);
        ImageView type =(ImageView) convertView.findViewById(R.id.type);

        name.setText(gods.get(position).getName());
        image.setImageResource(gods.get(position).getImage());
        pantheon.setImageResource(gods.get(position).getPantheonIcon());
        type.setImageResource(gods.get(position).getClassIcon());

        return convertView;
    }

    public Filter getFilter() {
        if(filter == null)
            filter = new CustomFilter();
        return filter;
    }


    //Inner class for filtering
    class CustomFilter extends Filter {

        protected FilterResults performFiltering(CharSequence constraint) {

            FilterResults results = new FilterResults();

            if(constraint != null && constraint.length() > 0)
            {
                constraint = constraint.toString().toUpperCase();
                ArrayList<God> filters = new ArrayList<God>();

                for(int i = 0; i < filterList.size(); i++)
                {
                    if(filterList.get(i).getName().toUpperCase().contains(constraint))
                    {
                        God god = new God(filterList.get(i).getName(), filterList.get(i).getTitle(), filterList.get(i).getNameString(), filterList.get(i).getImage(),
                                filterList.get(i).getPantheon(), filterList.get(i).getPantheonIcon(), filterList.get(i).getClassName(), filterList.get(i).getClassIcon(), filterList.get(i).getType(),
                                filterList.get(i).getHealth(), filterList.get(i).getMana(), filterList.get(i).getDamage(), filterList.get(i).getProtPhys(), filterList.get(i).getProtMag(), filterList.get(i).getSpeed(),
                                filterList.get(i).getHp5(), filterList.get(i).getMp5(), filterList.get(i).getAttackSpeed());
                        filters.add(god);
                    }
                }
                results.count = filters.size();
                results.values = filters;
            } else {
                results.count = filterList.size();
                results.values = filterList;
            }
            return results;
        }

        protected void publishResults(CharSequence constraint, FilterResults results) {
            gods = (ArrayList<God>) results.values;
            notifyDataSetChanged();
        }
    }

}

Solution

  • I solved this issue by keeping a ArrayList of all filters in the Adapter.

    This ArrayList would then be your constraint and you can filter the elements by checking if it contains the God's class.


    Example:

    public class GodAdapter extends BaseAdapter implements Filterable {
    
        ...
        ArrayList<God> gods;
        ArrayList<God> filteredGods;
        private ArrayList<String> classFilters;
    
        ...
    
        public boolean toggleFilter(String class) {
            if (classFilters.contains(class)) {
                classFilters.remove(class);
                return false;
            } else {
                classFilters.add(class);
                return true;
            }
        }
    
        ...
    
        class CustomFilter extends Filter {
    
            protected FilterResults performFiltering(CharSequence constraint) {
    
                FilterResults results = new FilterResults();
    
                if(classFilters.size() > 0){
                    ArrayList<God> filteredList = new ArrayList<God>();
    
                    for(int i = 0; i < filterList.size(); i++){
                        God god = filterList.get(i);
                        if(classFilters.contains(god.getName().toUpperCase())){
                            filteredList.add(god);
                        }
                    }
                    results.count = filteredList.size();
                    results.values = filteredList;
                } else {
                    results.count = filterList.size();
                    results.values = filterList;
                }
                return results;
            }
    
            protected void publishResults(CharSequence constraint, FilterResults results) {
                // IMPORTANT publish the results to a separate list
                filteredGods = (ArrayList<God>) results.values;
                notifyDataSetChanged();
            }
    }
    

    I would recommend using an enum to describe God classes instead of Strings. Explaining why would be too far away from this question, but you can read about it here.