How can you implement multi-selection and Contextual ActionMode in ActionBarSherlock?
Asked Answered
C

2

27

How should I implement multi selection on AdapterView with ActionBarSherlock, because it does not provide MultiChoiceModeListener?

This is what it looks like

Contextual Action Mode

How can you do this?

Crystacrystal answered 6/2, 2013 at 19:50 Comment(2)
There is complete solution for this.. github.com/deniskratinov/selectablelistview hope this helps you...Minuscule
am looking this for recyclerview please help meAgitation
C
40

So here's what I did.

Edit: Over a year passed since I found out the previous answer had alot of useless code (woops) and the CAB thing can be achieved with much less effort and a cleaner code, so I took some time and updated it

The LibraryFragment ListView should be defined with choice mode "none"

<ListView
    android:id="@android:id/list"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:choiceMode="none"/>

The list item should have an ?attr/activatedBackgroundIndicator foreground in order to automatically draw highlighted semitransparent overlay on list.setItemChecked(pos, true)

list_item_library.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:foreground="?attr/activatedBackgroundIndicator"
    android:paddingBottom="5dp"
    android:paddingTop="5dp" >

....

The ListFragment

import android.support.v4.app.DialogFragment;
import com.actionbarsherlock.app.SherlockListFragment;
import com.actionbarsherlock.view.ActionMode;
import com.actionbarsherlock.view.Menu;

public final class LibraryFragment
        extends SherlockListFragment
{

    private MyListAdapter adapter;
    private ListView list;

    // if ActoinMode is null - assume we are in normal mode
    private ActionMode actionMode;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState)
    {
        View v = inflater.inflate(R.layout.fragment_library, null);
        this.list = (ListView) v.findViewById(android.R.id.list);
        this.initListView();
        return v;
    }

    @Override
    public void onPause()
    {
        super.onPause();
        if (this.actionMode != null) {
            this.actionMode.finish();
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        updateData();
    }

    // update ListView
    protected void updateData()
    {
        if (adapter == null) {
            return;
        }
        adapter.clear();
        // my kinda stuff :)
        File[] items = scan();
        if (items != null) {
            adapter.updateData(items);
            if (actionMode != null) {
                actionMode.invalidate();
            }
        }
        // if empty - finish action mode.
        if (actionMode != null && (files == null || files.length == 0)) {
            actionMode.finish();
        }
    }

    private void initListView()
    {
        this.adapter = new MyAdapter(getActivity());
        this.list.setAdapter(adapter);
        this.list.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener()
        {

            @Override
            public boolean onItemLongClick(AdapterView<?> arg0,
                    View arg1, int arg2, long arg3)
            {
                if (actionMode != null) {
                    // if already in action mode - do nothing
                    return false;
                }
                // set checked selected item and enter multi selection mode
                list.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE);
                list.setItemChecked(arg2, true);

                getSherlockActivity().startActionMode(
                        new ActionModeCallback());
                return true;
            }
        });
        this.list.setOnItemClickListener(new AdapterView.OnItemClickListener()
        {
            @Override
            public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
                    long arg3)
            {
                if (actionMode != null) {
                    // the items are auomatically "checked" becaise we've set AbsListView.CHOICE_MODE_MULTIPLE before
                    // starting action mode, so the only thing we have to care about is invalidating the actionmode
                    actionMode.invalidate(); //invalidate title and menus.
                } else {
                    // do whatever you should on item click
                }
            }
        });
    }


    // all our ActionMode stuff here :)
    private final class ActionModeCallback
            implements ActionMode.Callback
    {

        // " selected" string resource to update ActionBar text
        private String selected = getActivity().getString(
                R.string.library_selected);

        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu)
        {
            actionMode = mode;
            return true;
        }

        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu)
        {
            // remove previous items
            menu.clear();
            final int checked = list.getCheckedItemCount();
            // update title with number of checked items
            mode.setTitle(checked + this.selected);
            switch (checked) {
            case 0:
                // if nothing checked - exit action mode
                mode.finish();
                return true;
            case 1:
                // all items - rename + delete
                getSherlockActivity().getSupportMenuInflater().inflate(
                        R.menu.library_context, menu);
                return true;
            default:
                getSherlockActivity().getSupportMenuInflater().inflate(
                        R.menu.library_context, menu);
                // remove rename option - because we have more than one selected
                menu.removeItem(R.id.library_context_rename);
                return true;
            }
        }

        @Override
        public boolean onActionItemClicked(ActionMode mode,
                com.actionbarsherlock.view.MenuItem item)
        {
            SparseBooleanArray checked;
            switch (item.getItemId()) {
            case R.id.library_context_rename:
                // the rename action is present only when only one item is selected. 
                // so when the first checked item found, show the dialog and break
                checked = list.getCheckedItemPositions();
                for (int i = 0; i < checked.size(); i++) {
                    final int index = checked.keyAt(i);
                    if (checked.get(index)) {
                        final DialogFragment d = RenameDialog.instantiate(adapter.getItem(index).getFile(), LibraryFragment.this);
                        d.show(getActivity().getSupportFragmentManager(), "dialog");
                        break;
                    }
                }
                return true;

            case R.id.library_context_delete:
                // delete every checked item
                checked = list.getCheckedItemPositions();
                for (int i = 0; i < checked.size(); i++) {
                    final int index = checked.keyAt(i);
                    if (checked.get(index)) {
                        adapter.getItem(index).getFile().delete();
                    }
                }
                updateData();
                return true;
            default:
                return false;
            }
        }

        @Override
        public void onDestroyActionMode(ActionMode mode)
        {
            list.clearChoices();

            //workaround for some items not being unchecked.
            //see https://mcmap.net/q/383564/-listview-selection-remains-persistent-after-exiting-choice-mode
            for (int i = 0; i < list.getChildCount(); i++) {
                (list.getChildAt(i).getBackground()).setState(new int[] { 0 });
            }

            list.setChoiceMode(AbsListView.CHOICE_MODE_NONE);
            actionMode = null;
        }

    }
Crystacrystal answered 6/2, 2013 at 19:50 Comment(13)
Thanks for sharing. I was looking for exactly this !Rew
thanks for this great post, but I have an issue on orientation change. Let's say, I selected a few items on the actionmode, then rotate the device, I am able to restore the actionbar, and set the selected items. But the highlight doesn't show up until I scroll the list off the screen and back, the highlight showed. Any idea?Snooze
@Snooze how do you restore state? Make sure you call setChecked() on Adapter. In this case it should work. But if that's what you were doing and it's not working, I'm afraid I can't help you since I don't have enough free time to test these things out.Crystacrystal
hey DD, thank you for getting back to me, really appreciate for your kindness. And yes, I did call setChecked() after the rotation, the adapter's items are checked (by setChecked), I can see from the LogCat. When i scroll the list to the listItems (I have a big listview header, so i have to scroll down to the list items after each rotation), the highlight do not show, then i scroll back to the top, then scroll back down to the list items, the highlight showed.Snooze
This answer is the new "42". Thank you so much for providing this sample code.Brabble
I used a rather simple way by setting <style name="activated" parent="android:Theme.Holo"> <item name="android:background">?android:attr/activatedBackgroundIndicator</item> </style> as my style of the list item.Artefact
@Artefact I thought about this option recently. When I wrote that answer I wasn't aware of it. I will reconsider updating it soon.Crystacrystal
Updated the answer with more cleaner way. Now no custom selectors or adapters needed.Crystacrystal
Your solution is perfect but the problem i am facing is that i am not able to highlight the selected image and using this line :" android:foreground="?attr/activatedBackgroundIndicator" gives me an resource not found exception and ya i have already added the actionbarsherlock libraryNeeley
@SatyenUdeshi ActionBarSherlock has this attributes in it's themes. Are you sure your app theme extends Sherlock theme? Anyway, you can try to use drawable instead @drawable/abs__activated_background_holo_dark or @drawable/abs__activated_background_holo_light. Also, there is now Google's Appcompat-v7 library with ActionBar that has less bugs that you can use instead of Sherlock.Crystacrystal
Thank you for your answer. I'm supporting API 8+ which is kind of problem to do multi-select list with CAB. I'm using support library v7 instead of ActionBarSherlock and with little changes in your code it works flawlessly.Ruella
I had problems as well in highlighting the selected item, though I'm not using ActionBarSherlock. The solution for me was to create a selector drawable, containg this <item android:state_activated="true" android:drawable="@drawable/my_item_selected"/>. Then using it as the list item's background like so android:background="@drawable/my_list_selector"Thomajan
@Thomajan yes, that's what the ?attr/activatedBackgroundIndicator which I mentioned as FrameLayout foreground does, which is fine for the default theme. If you want to override with your own colors you must create your own as that has the structure you mentioned.Crystacrystal
S
0

your solution is the best and easiest solution for this thread. But there is a tiny issue in the getView() - refer back to my comments above.

int version = android.os.Build.VERSION.SDK_INT;     
if(version < 11){
    if (checkedItems.contains(Integer.valueOf(position))) {
        convertView.getBackground().setState(
                new int[] { android.R.attr.state_checked });
    } else {
        convertView.getBackground().setState(
                new int[] { -android.R.attr.state_checked });
    }
}else{

    if (checkedItems.contains(Integer.valueOf(position))) {
        convertView.setActivated(true);
    } else {
        convertView.setActivated(false);        
    }
}

This will give you full support from API8 to API18

Snooze answered 29/7, 2013 at 21:59 Comment(2)
setActivated is available only starting from API level 11. If you use API 11 there is no need for ActionBarSherlock and my solution is bad since there is much easier way of doing this starting API 11. developer.android.com/guide/topics/ui/menus.html#CABCrystacrystal
My project is set a minimum API8, so I have to use both. I will modify my suggestion above too. Thanks.Snooze

© 2022 - 2024 — McMap. All rights reserved.