Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change ListView choiceMode from singleChoice to multipleChoiceModal

I have a ListView that normally is the singleChoice choiceMode. When the user long presses on an item, I want to enter an action mode that allows selecting multiple items so they can perform an action on any selected items.

I am able to configure the ListView so that it is in singleChoice mode and the user is able to select list items to display a details fragment next to it and have the list item itself shown in its activated state.

I am also able to configure the ListView so that it is in the multipleChoiceModal choiceMode and performing a long press on an item starts the action mode and allows multiple selections, but now the ListView will not allow a single selection in the normal mode (no action mode).

How can I have a ListView that is in singleChoice mode and then transition it to multipleChoiceModal mode when an item is long pressed?

This is the closest I've been able to come up with:

  1. set the ListView to singleChoice mode
  2. set the ListView's OnItemLongClickListener and in that listener:
    1. set the ListView's OnItemLongClickListener to null
    2. set the ListView's choiceMode to multipleChoiceModal
    3. call view.performClick() on the item that was long pressed.

This approach has a couple problems.

  1. The action mode isn't started until the second time I long press on an item.
  2. When I call getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); in onDestroyActionMode I get a java.lang.StackOverflowError because that method ends up trying to destroy the action mode as well (but we have no yet returned from the destroy).
like image 931
Steve Prentice Avatar asked Apr 20 '12 17:04

Steve Prentice


2 Answers

It really seemed awkard this choice mode switch since there is no clean and simple solution I could google. HFM (have faith man) and KISS (keep it simple stupid) helped ;)

1.start in single mode choice and set all the listeners (this is done where you set the list adapter)

listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 
listView.setOnItemLongClickListener(liListener);
listView.setMultiChoiceModeListener(mcListener);

2.implement the interfaces to switch between choice modes. The TRICK to make it work is to switch back to single choice mode outside the implementation, meaning AFTER you destroy the action mode!! So just use a simple flag to mark the CAB destruction. Another TRICK is to return false onItemLongClick so that the the choice mode have time to get into effect.

private OnItemLongClickListener liListener = new OnItemLongClickListener() {
        @Override
        public boolean onItemLongClick(AdapterView<?> parent, View view,
                int position, long id) {                
            listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
            isCABDestroyed = false;                 
            return false; // so this action does not consume the event!!!
        }
    };

private MultiChoiceModeListener mcListener = new MultiChoiceModeListener() {
    @Override
    public void onItemCheckedStateChanged(ActionMode mode, int position,
                                          long id, boolean checked) {
        final int checkedCount = listView.getCheckedItemCount();
        switch (checkedCount) {
            case 0:
                mode.setSubtitle(null);
                break;
            case 1:
                mode.setSubtitle("One item selected");
                break;
            default:
                mode.setSubtitle("" + checkedCount + " items selected");
                break;
        }
    }

    @Override
    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
        switch (item.getItemId()) {
            case R.id.delete:
                //do your action command here
                mode.finish();
                return true;
            default:
                return false;
        }
    }

    @Override
    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
        MenuInflater inflater = mode.getMenuInflater();
        inflater.inflate(R.menu.context_menu, menu);
        return true;
    }

    @Override
    public void onDestroyActionMode(ActionMode mode) {
        isCABDestroyed = true; // mark readiness to switch back to SINGLE CHOICE after the CABis destroyed  

    }

    @Override
    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
        return false;
    }
};

3.Here is the switch back

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        super.onListItemClick(l, v, position, id);  
        if(isCABDestroyed) {
            listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
            //do your action command  here
        }
        l.setItemChecked(position, true);

    }
like image 83
11 revs Avatar answered Oct 17 '22 01:10

11 revs


I used this in one of my programs

us the ListView.CHOICE_MODE_MULTIPLE_MODAL then lv.setMultiChoiceModeListener(new ModeCallBack());

    public class ModeCallBack implements ListView.MultiChoiceModeListener{

    View mSelectView;
    TextView mSelectedCount;
    ArrayList<Long> mCheckedItems;

    @Override
    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getActivity());
        SharedPreferences.Editor edit = pref.edit();

        if(item.getItemId() == R.id.bowler_delete){

            for(int i=0; i<mCheckedItems.size(); i++){
                long id = mCheckedItems.get(i);

                getActivity().getContentResolver().delete(BowlersDB.CONTENT_URI,BowlersDB.ID+"="+id,null);
            }
        }else if(item.getItemId() == R.id.bowler_add_ball){
            if(mCheckedItems.size() > 1){
                Toast.makeText(getActivity(),"Can only add bowling balls to one bowler at a time",Toast.LENGTH_SHORT).show();
            }else{
                edit.putLong(Preferences.BOWLER_SELECTED_FOR_BALL,mCheckedItems.get(0)).commit();

                ListFragment lf = new ManufacturersList();
                FragmentTransaction ft;
                ft = getFragmentManager().beginTransaction();
                ft.replace(R.id.frameOne, lf).addToBackStack(null).commit();
                //mRemover.rFragment();
            }
        }else if(item.getItemId() == R.id.add_bowler_to_team){
            for(int i=0; i<mCheckedItems.size(); i++){

                long id = mCheckedItems.get(i);
                ContentValues values = new ContentValues();
                values.put(TeamBowlers.BOWLER_ID,id);
                values.put(TeamBowlers.TEAM_ID,pref.getLong(Preferences.TEAM_SELECTED,1));
                getActivity().getContentResolver().insert(TeamBowlers.CONTENT_URI, values);

            }
            FragmentManager fm = getFragmentManager();
            fm.popBackStack();
        }
        mode.finish();
        return true;
    }

    @Override
    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
        MenuInflater inflate = getActivity().getMenuInflater();
        if(fromTeam){
            inflate.inflate(R.menu.bowlers_team_action_menu, menu);
        }else{
            inflate.inflate(R.menu.bowler_action_menu, menu);
        }
        if(mSelectView == null){
            mSelectView = (ViewGroup)LayoutInflater.from(getActivity()).inflate(R.layout.select_count_layout,null);

            mSelectedCount = (TextView)mSelectView.findViewById(R.id.count_tv);

        }
        if(mCheckedItems == null){
            mCheckedItems = new ArrayList<Long>();
        }
        mode.setCustomView(mSelectView);
        return true;
    }

    @Override
    public void onDestroyActionMode(ActionMode mode) {
        mCheckedItems = null;

    }

    @Override
    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
        if(mSelectView == null){
            mSelectView = (ViewGroup)LayoutInflater.from(getActivity()).inflate(R.layout.select_count_layout,null);

            mSelectedCount = (TextView)mSelectView.findViewById(R.id.count_tv);
        }

        if(mCheckedItems == null){
            mCheckedItems = new ArrayList<Long>();
        }
        return true;
    }

    @Override
    public void onItemCheckedStateChanged(ActionMode mode, int position,long id, boolean checked) {         

        final int count = lv.getCheckedItemCount();
        mSelectedCount.setText(String.valueOf(count));
        if(checked){
            mCheckedItems.add(id);
        }else{
            mCheckedItems.remove(id);
        }
    }

}

this allows for single choice single listview click and long click multiple selection. This was all pulled from the ICS messaging app so you can browse that too

like image 22
tyczj Avatar answered Oct 17 '22 02:10

tyczj