Android: Contextual Action Bar for ListView Item Deletion using ActionBarSherlock

27 July 2013 By Nithya Vasudevan 19,880 views 4 Comments
28 Flares Twitter 2 Facebook 9 Google+ 17 28 Flares ×

Prior to Android 3.0, the long press gesture (a touch that’s held in the same position for a moment) was used to display contextual actions for a given data item in a contextual menu. This pattern changed with Android 3.0. The long press gesture is now used to select data, combining contextual actions and selection management functions for selected data into a new element called the contextual action bar (CAB).

Reference:http://developer.android.com/design/patterns/selection.html

What is CAB?

The selection CAB is a temporary action bar that overlays your app’s current action bar while data is selected. It appears after the user long presses on a selectable data item. Select additional data items by touching them. It is recommended to use when you perform actions with selected data like plain text or data items from ListView or GridView components. The action mode is disabled and the contextual action bar disappears when the user deselects all items, presses the BACK button, or selects the checkmark button on the left side of the bar.

Implementing CAB for ListView item selection

  • If you’re using Android 3.0 or higher there is a detailed description of CAB setup on each selection method – whether contextual action should be performed on a single selected item or on a group of selected items.
  • Although it is available from Android 3.0 it is a good practice to use it in apps working on earlier API versions. If your app works on android version 2.x and higher the best approach is to use ActionBarSherlock library. It has its own contextual action bar implementation, which is easy to set up. But it doesn’t support the native ListView integration, so you need to control CAB’s lifecycle by yourself.

You can invoke contextual action mode based on one of the two events or both.

  1. The user selects a checkbox or similar UI component within the view.
  2. The user performs a long-click on the view.

This tutorial explains the second method.

In this tutorial we’ll see how to use Contextual Action Bar for ListView using ActionBarSherlock library where list items can be selected on long press gesture. Here, contextual action bar has delete menu item, which deletes the selected items from ListView.

Output

Android Project

Create an Android project and name it as CABDemo. Include ActionBarSherlock as a library project.

strings.xml

Open res/values/strings.xml and replace it with following content.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">CABDemo</string>
    <string name="hello_world">Hello world!</string>

    <string-array name="laptops">
        <item>Acer</item>
        <item>Asus</item>
        <item>Dell</item>
        <item>HCL</item>
        <item>HP</item>
        <item>IBM</item>
        <item>LG</item>
        <item>Sony</item>
        <item>Samsung</item>
    </string-array>
</resources>

strings_menu.xml

Create a new strings_menu.xml in res/values/ folder and copy the following content.

<?xml version="1.0" encoding="utf-8"?>
<resources>
	<string name="menu_settings">Settings</string>
	<string name="menu_add">Add</string>
	<string name="menu_rate">Rate</string>
	<string name="menu_share">Share</string>	
	
	<string name="menu_delete">Delete</string>	
</resources>

styles.xml

Open res/values/styles.xml and replace it with following content. Here, we use Action bar Sherlock theme.

<resources>
    <style name="AppBaseTheme" parent="Theme.Sherlock">
    </style>

    <style name="AppTheme" parent="AppBaseTheme">
    </style>
</resources>

Menu XML files

activity_main.xml

Create a new activity_main.xml in res/menu folder and copy the following content. This file defines action bar menu items.

<menu xmlns:android="http://schemas.android.com/apk/res/android" >

    <item
        android:id="@+id/menu_add"
        android:orderInCategory="100"
        android:showAsAction="ifRoom|withText"
        android:title="@string/menu_add"
        android:icon="@android:drawable/ic_input_add"/>
    
      <item
        android:id="@+id/menu_rate"
        android:orderInCategory="200"
        android:showAsAction="ifRoom|withText"
        android:title="@string/menu_rate"
        android:icon="@android:drawable/btn_star_big_on"/>
      
         <item
        android:id="@+id/menu_share"
        android:orderInCategory="300"
        android:showAsAction="ifRoom|withText"
        android:title="@string/menu_share"
        android:icon="@android:drawable/ic_menu_share"/>
</menu>

context_menu.xml

Create a new context_main.xml in res/menu folder and copy the following content. This file defines menu items for contextual action bar. We define one menu item which is used to delete items from ListView.

<menu xmlns:android="http://schemas.android.com/apk/res/android" >

    <item
        android:id="@+id/menu_delete"
        android:orderInCategory="100"
        android:showAsAction="ifRoom|withText"
        android:title="@string/menu_delete"
        android:icon="@android:drawable/ic_menu_delete"/>
</menu>

XML layout files

This Android contextual action bar example uses two layout files; one for displaying the main layout containing the ListView and other for defining the row layout for ListView item.

activity_main.xml

This application uses XML layout file (activity_main.xml) to display ListView.

Open activity_main.xml file in res/layout and copy the following content.

<?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="fill_parent"
    android:orientation="vertical" >

    <ListView
        android:id="@+id/laptopList"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:divider="#323232"
        android:dividerHeight="1dp"
        android:scrollbars="none" >
    </ListView>

</LinearLayout>

list_item.xml

Create a new layout file list_item.xml in res/layout and copy the following content. For simplicity, this file contains only a TextView.

<?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:orientation="vertical" 
    android:padding="10dp">
    
    <TextView 
        android:id="@+id/laptop_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="#000000"/>

</LinearLayout>

Create Bean class

Laptop.java

Create a new Java class “Laptop.java” in package “com.theopentutorials.cabdemo.beans” and copy the following code.

package com.theopentutorials.cabdemo.beans;

public class Laptop {
	private String brand;
	
	public Laptop() {
		super();
	}
	public Laptop(String brand) {
		super();
		this.brand = brand;
	}
	public String getBrand() {
		return brand;
	}
	public void setBrand(String brand) {
		this.brand = brand;
	}
	@Override
	public String toString() {
		return  brand;
	}
}

Create Android Custom Adapter class for ListView

Create a new Java class “LaptopListAdapter.java” in package “com.theopentutorials.cabdemo.adapters” and copy the following code.

package com.theopentutorials.cabdemo.adapters;

import java.util.List;
import android.app.Activity;
import android.graphics.Color;
import android.util.SparseBooleanArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import android.widget.Toast;

import com.theopentutorials.cabdemo.R;
import com.theopentutorials.cabdemo.beans.Laptop;

public class LaptopListAdapter extends ArrayAdapter<Laptop> {

	Activity context;
	List<Laptop> laptops;
	private SparseBooleanArray mSelectedItemsIds;

	public LaptopListAdapter(Activity context, int resId, List<Laptop> laptops) {
		super(context, resId, laptops);
		mSelectedItemsIds = new SparseBooleanArray();
		this.context = context;
		this.laptops = laptops;
	}

	private class ViewHolder {
		TextView laptopTxt;
	}

	public View getView(int position, View convertView, ViewGroup parent) {
		ViewHolder holder;
		if (convertView == null) {
			LayoutInflater inflater = (LayoutInflater) context
					.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
			convertView = inflater.inflate(R.layout.list_item, null);
			holder = new ViewHolder();
			holder.laptopTxt = (TextView) convertView
					.findViewById(R.id.laptop_name);
			convertView.setTag(holder);
		} else {
			holder = (ViewHolder) convertView.getTag();
		}

		Laptop laptop = getItem(position);
		holder.laptopTxt.setText(laptop.toString());
		convertView
				.setBackgroundColor(mSelectedItemsIds.get(position) ? 0x9934B5E4
						: Color.TRANSPARENT);

		return convertView;
	}

	@Override
	public void add(Laptop laptop) {
		laptops.add(laptop);
		notifyDataSetChanged();
		Toast.makeText(context, laptops.toString(), Toast.LENGTH_LONG).show();
	}

	@Override
	public void remove(Laptop object) {
		// super.remove(object);
		laptops.remove(object);
		notifyDataSetChanged();
	}

	public List<Laptop> getLaptops() {
		return laptops;
	}

	public void toggleSelection(int position) {
		selectView(position, !mSelectedItemsIds.get(position));
	}

	public void removeSelection() {
		mSelectedItemsIds = new SparseBooleanArray();
		notifyDataSetChanged();
	}

	public void selectView(int position, boolean value) {
		if (value)
			mSelectedItemsIds.put(position, value);
		else
			mSelectedItemsIds.delete(position);

		notifyDataSetChanged();
	}

	public int getSelectedCount() {
		return mSelectedItemsIds.size();
	}

	public SparseBooleanArray getSelectedIds() {
		return mSelectedItemsIds;
	}
}

Activity

Open “MainActivity” from the package “com.theopentutorials.cabdemo” and copy the following code.

package com.theopentutorials.cabdemo;

import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.util.SparseBooleanArray;
import android.view.Gravity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ListView;
import android.widget.Toast;

import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.view.ActionMode;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
import com.theopentutorials.cabdemo.adapters.LaptopListAdapter;
import com.theopentutorials.cabdemo.beans.Laptop;

public class MainActivity extends SherlockFragmentActivity implements
		OnItemClickListener {

	ListView laptopListView;
	LaptopListAdapter laptopListAdapter;
	List<Laptop> laptops = new ArrayList<Laptop>();

	Activity activity = this;
	private ActionMode mActionMode;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		laptopListView = (ListView) findViewById(R.id.laptopList);

		setLaptopList();
		laptopListAdapter = new LaptopListAdapter(this, R.layout.list_item,
				laptops);
		laptopListView.setAdapter(laptopListAdapter);
		laptopListView.setOnItemClickListener(this);
		laptopListView.setOnItemLongClickListener(new OnItemLongClickListener() {

					public boolean onItemLongClick(AdapterView<?> parent,
							View view, int position, long id) {
						onListItemSelect(position);
						return true;
					}
				});
	}

	@Override
	public void onItemClick(AdapterView<?> adapterView, View view,
			int position, long id) {
		if (mActionMode == null) {
			/*no items selected, so perform item click actions 
			 * like moving to next activity */
			Toast toast = Toast.makeText(getApplicationContext(), "Item "
					+ (position + 1) + ": " + laptops.get(position),
					Toast.LENGTH_SHORT);
			toast.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, 0);
			toast.show();

		} else
			// add or remove selection for current list item
			onListItemSelect(position);
	}

	private void onListItemSelect(int position) {
		laptopListAdapter.toggleSelection(position);
		boolean hasCheckedItems = laptopListAdapter.getSelectedCount() > 0;

		if (hasCheckedItems && mActionMode == null)
			// there are some selected items, start the actionMode
			mActionMode = startActionMode(new ActionModeCallback());
		else if (!hasCheckedItems && mActionMode != null)
			// there no selected items, finish the actionMode
			mActionMode.finish();

		if (mActionMode != null)
			mActionMode.setTitle(String.valueOf(laptopListAdapter
					.getSelectedCount()) + " selected");
	}

	private void setLaptopList() {
		String[] blogs = getResources().getStringArray(R.array.laptops);
		for (String brand : blogs) {
			Laptop laptop = new Laptop(brand);
			laptops.add(laptop);
		}
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		MenuInflater inflater = getSupportMenuInflater();
		inflater.inflate(R.menu.activity_main, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		if (item.getItemId() == R.id.menu_add) {
			// adds item to listview
		} else if (item.getItemId() == R.id.menu_share) {
			// share your application by using share intent
		} else if (item.getItemId() == R.id.menu_rate) {
			// add rate feature to your application by launching market
		}
		return true;
	}

	private class ActionModeCallback implements ActionMode.Callback {

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

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

		@Override
		public boolean onActionItemClicked(ActionMode mode, MenuItem item) {

			switch (item.getItemId()) {
			case R.id.menu_delete:
				// retrieve selected items and delete them out
				SparseBooleanArray selected = laptopListAdapter
						.getSelectedIds();
				for (int i = (selected.size() - 1); i >= 0; i--) {
					if (selected.valueAt(i)) {
						Laptop selectedItem = laptopListAdapter
								.getItem(selected.keyAt(i));
						laptopListAdapter.remove(selectedItem);
					}
				}
				mode.finish(); // Action picked, so close the CAB
				return true;
			default:
				return false;
			}

		}

		@Override
		public void onDestroyActionMode(ActionMode mode) {
			// remove selection
			laptopListAdapter.removeSelection();
			mActionMode = null;
		}
	}
}

Here, we won’t use ListView’s built in selection handling. In the case of long click, actionMode activation required switching between different choice modes of ListView like multipleChoice and none, which works buggy. So we have manually added several methods in adapter class for handling item selection.

Output

Run your application and you will get the output as shown in the beginning of this tutorial.

Folder Structure

The complete folder structure of this project is shown below.

Tags: , , , , , , , , ,

  • http://nimooli.com Nima

    Very buggy, rotate the device and you will be stuck in the Action mode forever :)

  • http://javatyro.blogspot.in siddhu

    sir can u please put the download link

  • Shaun

    Works like a charm! Thanks very much for putting this together.

  • I SikhWarrior I

    Thanks for this, I spent forever trying to find a tutorial that did this!