Nexus-4-Flat

In this tutorial we’ll see how to use Contextual Action Bar with ActionBarSherlock and ListView in case when data items selected with long-click, and don’t have any UI components like checkbox to perform selection.

Contextual Action Bar (CAB) is an important mode of Action Bar and a very user friendly design pattern. It is recommended to use when you perform actions with selected data like plain text or data items from ListView or GridView components. Although it is available from Android 3.0 it is a good practice to use it in apps working on earlier API versions.

Implement CAB with ListView

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.

If your app works on android version 2.x and higher the best approach is to use ActionBarSherlock library. It has it’s 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 lifesycle by yourself. There are two accepted ways of selecting items in a ListView:

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

There is a good tutorial for the first variant with ActionBarSherlock. The second way is described below.

Activating CAB on long-click 

First create a new project with name com.androidperspective.examples.selectablelistview and activity called ListViewActivity. Include ActionBarSherlock as a library project. You can find the instruction here.

Let’s create a layout for the main activity and call it activity_list_view:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
    tools:context=".ListViewActivity" >	
    <ListView android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</FrameLayout>

For the contextual action bar we need to create menu resource in the menu folder, named contextual_list_view:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
    <item android:id="@+id/print_item" 
        android:title="Print"/>
</menu>

For simplicity we’ll use SherlockListActivity as a base class for our activity.

public class ListViewActivity extends SherlockListActivity {

private ActionMode mActionMode;
private String[] mItems = {"Eclair", "Froyo", "Gingerbread", "Honeycomb", "Ice Cream Sandwich", "Jelly Bean"};

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

	//add on long click listener to start action mode
	getListView().setOnItemLongClickListener(new OnItemLongClickListener() {
		@Override
		public boolean onItemLongClick(AdapterView<?> adapterView, View view, int position, long id) {
			onListItemCheck(position);
			return true;
		}
	});

	setListAdapter(new SelectableAdapter(this, mItems));
}

@Override
protected void onListItemClick(ListView l, View v, int position, long id) {		
	if(mActionMode == null) {
		// no items selected, so perform item click actions
	} else
		// add or remove selection for current list item
		onListItemCheck(position);		
}	

private void onListItemCheck(int position) {
SelectableAdapter adapter = (SelectableAdapter) getListAdapter();
adapter.toggleSelection(position);
boolean hasCheckedItems = adapter.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(adapter.getSelectedCount()) + " selected");
}
}

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 multipleChoise and none, which works buggy. So we’ll put responsibility for items selection on the adapter adding several methods to it. Create inner class SelectableAdapter:

private class SelectableAdapter extends ArrayAdapter<String>{

private SparseBooleanArray mSelectedItemsIds;

public SelectableAdapter(Context context, String[] objects) {
	super(context, android.R.layout.simple_list_item_1, objects);
	mSelectedItemsIds = new SparseBooleanArray();
}

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();// mSelectedCount;
}
        
public SparseBooleanArray getSelectedIds() {
	return mSelectedItemsIds;
}
        
@Override
public View getView(int position, View convertView, ViewGroup parent) {
        	
if(convertView == null){
	LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(LAYOUT_INFLATER_SERVICE);
	convertView = inflater.inflate(android.R.layout.simple_list_item_1, null);
}         	
((TextView) convertView).setText(getItem(position));    
//change background color if list item is selected
convertView.setBackgroundColor(mSelectedItemsIds.get(position)? 0x9934B5E4: Color.TRANSPARENT);        	
        	
return convertView;
}

}

The last thing we need to implement is actionMode callback listener. Inner class ActionModeCallback:

private class ActionModeCallback implements ActionMode.Callback {

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

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

@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
	// retrieve selected items and print them out
	SelectableAdapter adapter = (SelectableAdapter) ListViewActivity.this.getListAdapter();
	SparseBooleanArray selected = adapter.getSelectedIds();
	StringBuilder message = new StringBuilder();			
	for (int i = 0; i < selected.size(); i++){				
		if (selected.valueAt(i)) {
			String selectedItem = adapter.getItem(selected.keyAt(i));
			message.append(selectedItem + "\n");
		}
	}			
	Toast.makeText(ListViewActivity.this, message.toString(), Toast.LENGTH_LONG).show();

	// close action mode
	mode.finish();
	return false;
}

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

}

You can find the source code on my github.

If you are new to ListView I would recommend this tutorial.

Thanks for reading!

Advertisements