Contextual Action bar (CAB) in Android

By -

Before getting into the action bar and Contextual Action bar concept and coding for it, let me take you through the concept of 2 ways to show contextual actions:

1. Floating Context Menu
2. Contextual Action Mode

1. Floating Context Menu

android context menu

In earlier versions of Android, we were used to see almost all the apps having context menu ready for showing options (menu items) whenever user performs a long press on any element. We can say long press gesture was universally used to display contextual actions in a context menu.

“Long press gesture – That is, a touch that’s held in the same position for a moment.”

Now, since from Android 3.0, the purpose of Long press gesture has changed, its now used to handle multi-select and contextual actions.

2. Contextual Action Mode

  • The contextual action mode is a system implementation of ActionMode that focuses user interaction toward performing contextual actions.
  • When a user enables this mode by selecting an item, a contextual action bar appears at the top of the screen to present actions the user can perform on the currently selected item(s).

ActionMode

Represents a contextual mode of the user interface. Action modes can be used to provide alternative interaction modes and replace parts of the normal UI until finished. Examples of good action modes include text selection and contextual actions.

Contextual Action bar (CAB)

A Contextual action bar (CAB) is a temporary action bar that overlays the app’s action bar for the duration of a particular sub-task.
Contextual Action bar (CAB) in Android

As I have mentioned earlier, CABs are used for tasks that involve acting on selected data or text. For example: Cut, Copy, Paste, Delete or any other operations can be perform on single or batch of selected data.

Contextual Action bar (CAB) in Android

As shown in snap-1 (left) above, Contextual action bar (Selection CAB) takes place at top bar as soon as user performs long press gesture, from here user can:

  1. Select more items or deselect items by just touching them
  2. Select and trigger any actions displayed in the bar, the selected action triggers to all the selected items. Then the action bar automatically dismiss itself.
  3. You can dismiss CAB in 3 ways:
    • Deselect all the selected items
    • Press Back key from navigation bar
    • Select Check mark button (left) from the CAB. It doesn’t dismiss only the CAB but also removes the selection on data which you have done.

When to use which? (Context Menu or CAB)

Now I am sure there is no doubt regarding when to use Context Menu and when to CAB. As I have mentioned if you are developing app for android 3.0 or higher, you should use Contextual Action bar instead of displaying menu items in floating context menu.

And if you are providing compatibility to lower Android version, you should fall back to a floating context menu on those devices.

Using Contextual Action bar (CAB):

There are 2 designs by which you can implement Contextual Action bar:

  1. Enable CAB when user selects a particular view
  2. Enable CAB whenever user performs a long press gesture on particular view

1: Enable CAB when user selects a particular view

If you want to invoke the contextual action mode only when the user select particular views then follow the below steps:

  1. Implement the ActionMode.Callback interface. In its callback methods, you can specify the actions for the contextual action bar, respond to click events on action items, and handle other lifecycle events for the action mode.
  2. Call startActionMode() when you want to show the bar (such as when the user long-clicks the view).

Implement the ActionMode.Callback interface:

class ActionBarCallBack implements ActionMode.Callback {
 
        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
            // TODO Auto-generated method stub
            return false;
        }
 
        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            // TODO Auto-generated method stub
            mode.getMenuInflater().inflate(R.menu.contextual_menu, menu);
            return true;
        }
 
        @Override
        public void onDestroyActionMode(ActionMode mode) {
            // TODO Auto-generated method stub
 
        }
 
        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            // TODO Auto-generated method stub
 
            mode.setTitle("CheckBox is Checked");
            return false;
        }
}

Call startActionMode()

MainActivity.this.startActionMode(new ActionBarCallBack());

For example:
Let’s build an example to enable Contextual action mode on the CheckBox selection.

package com.technotalkative.contextualactionbarsingle;

import android.app.Activity;
import android.os.Bundle;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;

public class MainActivity extends Activity {
 
    private ActionMode mActionMode;
    private CheckBox checkBox1;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        getActionBar().setTitle("CAB demo - Individual view");
        checkBox1 = (CheckBox) findViewById(R.id.checkBox1);
        checkBox1.setOnCheckedChangeListener(new OnCheckedChangeListener() {
 
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                // TODO Auto-generated method stub
 
                if(isChecked)
                    mActionMode = MainActivity.this.startActionMode(new ActionBarCallBack());
                else
                    mActionMode.finish();
            }
        });
    }
 
    class ActionBarCallBack implements ActionMode.Callback {
 
        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
            // TODO Auto-generated method stub
            return false;
        }
 
        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            // TODO Auto-generated method stub
            mode.getMenuInflater().inflate(R.menu.contextual_menu, menu);
            return true;
        }
 
        @Override
        public void onDestroyActionMode(ActionMode mode) {
            // TODO Auto-generated method stub
 
        }
 
        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            // TODO Auto-generated method stub
 
            mode.setTitle("CheckBox is Checked");
            return false;
        }
    }
}

2: Enable CAB when user performs a long press gesture on particular view

If you want to invoke the contextual action mode only when the user performs long press gesture on view like ListView or GridView, and want to perform batch actions on multiple selected items then you can implement this, follow the below steps:

  1. Implement the AbsListView.MultiChoiceModeListener and set it to your ViewGroup (e.g. ListView). In its callback methods, you can specify the actions for the contextual action bar, respond to click events on action items, and handle its callback events (Which are actually inherited from ActionMode.Callback interface).
  2. Call setChoiceMode() with the CHOICE_MODE_MULTIPLE_MODAL argument.

AbsListView.MultiChoiceModeListener:

A MultiChoiceModeListener receives events for CHOICE_MODE_MULTIPLE_MODAL. It acts as the ActionMode.Callback for the selection mode and also receives onItemCheckedStateChanged(ActionMode, int, long, boolean) events when the user selects and deselects list items.

Implement the AbsListView.MultiChoiceModeListener:

getListView().setMultiChoiceModeListener(new MultiChoiceModeListener() {

            @Override
            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
                // TODO Auto-generated method stub
                return false;
            }
             
            @Override
            public void onDestroyActionMode(ActionMode mode) {
                // TODO Auto-generated method stub
            }

            @Override
            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
                // TODO Auto-generated method stub
                 
                MenuInflater inflater = getMenuInflater();
                inflater.inflate(R.menu.contextual_menu, menu);
                return true;
            }

            @Override
            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
                // TODO Auto-generated method stub
		return false;
            }

            @Override
            public void onItemCheckedStateChanged(ActionMode mode, int position,
                    long id, boolean checked) {
                // TODO Auto-generated method stub
            }
        });

Call setChoiceMode() with the CHOICE_MODE_MULTIPLE_MODAL argument:

getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);

Full example:
Have you used Gmail android app? (stupid question :)), but if you have used then I am sure you have tried to perform long gesture on mails to delete mails, so let’s develop same like example. Here we will enable contextual action mode whenever user performs long press gesture and we will display number of items selected.
Step 1: Take ListView in activity_main.xml layout

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity"
    android:background="@android:color/background_light" >
 
    <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:choiceMode="multipleChoice">
    </ListView>
 
</RelativeLayout>

Step 2: Define row layout (row_list_item.xml) for ListView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:layout_gravity="center_vertical"
    android:padding="5dp"
    android:background="@android:color/background_light" >
 
    <ImageView
        android:id="@+id/imageView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher" />
 
    <TextView
        android:id="@+id/textView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="17sp"
        android:layout_marginLeft="10dp"
        android:text="Test"
        android:textStyle="bold" />
 
</LinearLayout>

Step 3: Create a contextual menu (contextual_menu.xml) in menu folder, this menu gets displayed as contextual action bar whenever user performs long press gesture

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
    
    <item
        android:id="@+id/item_delete"
        android:icon="@android:drawable/ic_menu_delete"
        android:showAsAction="ifRoom|withText"
        android:title="Delete"
        android:titleCondensed="Delete">
    </item>

</menu>

Step 4: Implement MultiChoiceModeListener and call setChoiceMode() inside MainActivity

  • Inside onCreateActionMode() – We will enable contextual action mode with menu we have defined.
  • Inside onActionItemClicked() – We can perform contextual actions on the selected items.
  • Inside onItemCheckedStateChanged() – we can decide which items are selected and which are not. Here we will prepare title for the action bar with particular no. of items are selected.
package com.technotalkative.contextualactionmultiple;

import java.util.HashMap;
import java.util.Set;

import android.app.ListActivity;
import android.content.Context;
import android.os.Bundle;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class MainActivity extends ListActivity {
 
    private String[] data = {"One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine","Ten"};
     
    private SelectionAdapter mAdapter;
     
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
         
        mAdapter = new SelectionAdapter(this,
                    R.layout.row_list_item, R.id.textView1, data);
        setListAdapter(mAdapter);
        getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
         
        getListView().setMultiChoiceModeListener(new MultiChoiceModeListener() {
             
            private int nr = 0;
             
            @Override
            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
                // TODO Auto-generated method stub
                return false;
            }
             
            @Override
            public void onDestroyActionMode(ActionMode mode) {
                // TODO Auto-generated method stub
                 mAdapter.clearSelection();
            }
             
            @Override
            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
                // TODO Auto-generated method stub
                 
                nr = 0;
                MenuInflater inflater = getMenuInflater();
                inflater.inflate(R.menu.contextual_menu, menu);
                return true;
            }
             
            @Override
            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
                // TODO Auto-generated method stub
                switch (item.getItemId()) {
                 
                    case R.id.item_delete:
                        nr = 0;
                        mAdapter.clearSelection();
                        mode.finish();
                }
				return false;
            }
             
            @Override
            public void onItemCheckedStateChanged(ActionMode mode, int position,
                    long id, boolean checked) {
                // TODO Auto-generated method stub
                 if (checked) {
                        nr++;
                        mAdapter.setNewSelection(position, checked);                    
                    } else {
                        nr--;
                        mAdapter.removeSelection(position);                 
                    }
                    mode.setTitle(nr + " selected");
                 
            }
        });
         
        getListView().setOnItemLongClickListener(new OnItemLongClickListener() {
 
            @Override
            public boolean onItemLongClick(AdapterView<?> arg0, View arg1,
                    int position, long arg3) {
                // TODO Auto-generated method stub
                 
                getListView().setItemChecked(position, !mAdapter.isPositionChecked(position));
                return false;
            }
        });
    }
     
    private class SelectionAdapter extends ArrayAdapter<String> {
 
        private HashMap<Integer, Boolean> mSelection = new HashMap<Integer, Boolean>();
 
        public SelectionAdapter(Context context, int resource,
                int textViewResourceId, String[] objects) {
            super(context, resource, textViewResourceId, objects);
        }
 
        public void setNewSelection(int position, boolean value) {
            mSelection.put(position, value);
            notifyDataSetChanged();
        }
 
        public boolean isPositionChecked(int position) {
            Boolean result = mSelection.get(position);
            return result == null ? false : result;
        }
 
        public Set<Integer> getCurrentCheckedPosition() {
            return mSelection.keySet();
        }
 
        public void removeSelection(int position) {
            mSelection.remove(position);
            notifyDataSetChanged();
        }
 
        public void clearSelection() {
            mSelection = new HashMap<Integer, Boolean>();
            notifyDataSetChanged();
        }
 
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View v = super.getView(position, convertView, parent);//let the adapter handle setting up the row views
            v.setBackgroundColor(getResources().getColor(android.R.color.background_light)); //default color
             
            if (mSelection.get(position) != null) {
                v.setBackgroundColor(getResources().getColor(android.R.color.holo_blue_light));// this is a selected position so make it red
            }
            return v;
        }
    }
}

Download example:

https://github.com/PareshMayani/Contextual-Action-Bar

CEO & Co-Founder at SolGuruz® | Organiser @ GDG Ahmedabad | Top 0.1% over StackOverflow | 15+ years experienced Tech Consultant | Helping startups with Custom Software Development

Loading Facebook Comments ...
Loading Disqus Comments ...