Porting the contextual anction mode for pre-Honeycomb Android apps

mar. 20 mars 2012
Contextual action bar for pre-ics app

A very nice Android design pattern that appeared in Honeycomb is the Contextual Action Bar (CAB). In a list view, when you select some items, the Action Bar is replaced with contextual actions that can be applied on selected item. Smart and sexy.

But, as for the Action Bar pattern, there is no easy way to port this behavior to pre-Honeycomb devices.

In this article, we will see how to use the Action Bar Sherlock library to emulate the CAB pattern. If you don't know how to setup action bar sherlock, refer to my previous article.

To see a live example of this tutorial, you can refer to my Miximum reminder App (shameless promotion).

Creating a basic list

Let's start by creating a basic list activity.

package fr.miximum.article;

import com.actionbarsherlock.app.SherlockListActivity;

import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class ArticleActivity extends SherlockListActivity {

    static final String[] COUNTRIES = new String[] {
        "Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra",
        "Angola", "Anguilla", "Canada", "France", "Spain" };

    private ListView mListView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mListView = getListView();
        mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
        setListAdapter(new ArrayAdapter(this,
                android.R.layout.simple_list_item_multiple_choice, COUNTRIES));
    }
}

Run the project. Notice that items can be selected, but there is not any contextual action bar yes.

In some native code, we would have used the CHOICE_MODE_MULTIPLE_MODAL constant to enable batch contextual actions. Those methods are only available from the 11th version of the api (honeycomb).

The Action Bar Sherlock ActionMode

Let's see how to use the ActionBarSherlock's ActionMode class. First, create the menu that will define the contextual actions. In res/menu/contextual_actions.xml:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/cab_action_delete"
        android:title="@string/action_delete"
        android:icon="@android:drawable/ic_menu_delete" />
</menu>

Then, let's update our activity to enable action mode on item click:

package fr.miximum.article;

import com.actionbarsherlock.app.SherlockListActivity;
import com.actionbarsherlock.view.ActionMode;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;

import android.os.Bundle;
import android.util.SparseBooleanArray;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class ArticleActivity extends SherlockListActivity  implements OnItemClickListener {

    static final String[] COUNTRIES = new String[] {
        "Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra",
        "Angola", "Anguilla", "Canada", "France", "Spain" };

    private ListView mListView;
    private ActionMode mMode;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setListAdapter(new ArrayAdapter(this,
                android.R.layout.simple_list_item_multiple_choice, COUNTRIES));

        mMode = null;
        mListView = getListView();
        mListView.setItemsCanFocus(false);
        mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
        mListView.setOnItemClickListener(this);
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        // Notice how the ListView api is lame
        // You can use mListView.getCheckedItemIds() if the adapter
        // has stable ids, e.g you're using a CursorAdaptor
        SparseBooleanArray checked = mListView.getCheckedItemPositions();
        boolean hasCheckedElement = false;
        for (int i = 0 ; i < checked.size() && ! hasCheckedElement ; i++) {
            hasCheckedElement = checked.valueAt(i);
        }

        if (hasCheckedElement) {
            if (mMode == null) {
                mMode = startActionMode(new ModeCallback());
            }
        } else {
            if (mMode != null) {
                mMode.finish();
            }
        }
    };

    private final class ModeCallback implements ActionMode.Callback {

        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            // Create the menu from the xml file
            MenuInflater inflater = getSupportMenuInflater();
            inflater.inflate(R.menu.contextual_actions, menu);
            return true;
        }

        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            // Here, you can checked selected items to adapt available actions
            return false;
        }

        @Override
        public void onDestroyActionMode(ActionMode mode) {
            // Destroying action mode, let's unselect all items
            for (int i = 0; i < mListView.getAdapter().getCount(); i++)
                mListView.setItemChecked(i, false);

            if (mode == mMode) {
                mMode = null;
            }
        }

        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
            long[] selected = mListView.getCheckedItemIds();
            if (selected.length > 0) {
                for (long id: selected) {
                    // Do something with the selected item
                }
            }
            mode.finish();
            return true;
        }
    };
}

Well, that was not THAT hard, right? Notice that some ActionBarSherlock class names are the same as the Android API, so be careful to import the correct versions, or you will run into hard to find bugs.

Hope you find this article useful. See you.