11

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).
Steve Prentice
  • 23,230
  • 11
  • 54
  • 55

2 Answers2

6

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);

    }
  • Sorry to bring back an old post but I've tried the way described above but my listView items are not blue highlighted (like on whatsapp). How can I make that happen ? Thanks ! – Leonardo Nov 03 '13 at 09:04
  • If UI requires navigate away and return back via recreation (for example, when UI based on the Fragment), dont forget to switch ListView state to `ListView.CHOICE_MODE_SINGLE` if it was in `ListView.CHOICE_MODE_MULTIPLE_MODAL` on moment of going away. Also, keep selection diring switching from MULTI to SINGLE. I do it like this: if (! isCABDestroyed) { int selection = contentList.getCheckedItemPositions().keyAt(0); contentList.setChoiceMode(ListView.CHOICE_MODE_SINGLE); contentList.setItemChecked(selection, true); } // do the navigation away – A. Petrov Sep 30 '15 at 07:47
3

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

tyczj
  • 71,600
  • 54
  • 194
  • 296
  • Perhaps I'm missing something, but how does the list go into the singleChoiceMode after the action mode is destroyed? – Steve Prentice Apr 20 '12 at 19:17
  • just override the `onListItemClick` in the listview then when the user clicks the item in the listview show the fragment that pertains to the click. unless I am missing something here? – tyczj Apr 20 '12 at 19:37
  • I guess the part that I didn't mention is that my list items take advantage of state_activated. state_activated only is set to true when the item is actually selected as a choice in singleChoice mode, so I really do want my ListView to change back to singleChoice mode. – Steve Prentice Apr 20 '12 at 19:40
  • what I did is on my `onListItemClick` keep track of what the the current and previous position. when I click the list I clear the last item by using `lv.setItemChecked(mPreviousPosition,false)` so that clears the selection and then change the selection to the new one with `lv.setItemChecked(position,true)`. hopefully that is what you mean – tyczj Apr 20 '12 at 20:16
  • I'll give it a try and accept the answer if I can get it to work. Thanks. – Steve Prentice Apr 20 '12 at 21:42