How to Implement Multi-Selection and Contextual Actionmode in Actionbarsherlock

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

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 http://stackoverflow.com/a/10542628/1366471
for (int i = 0; i < list.getChildCount(); i++) {
(list.getChildAt(i).getBackground()).setState(new int[] { 0 });
}

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

}

Android compatibility contextual action bar

Setting up contextual actionbar is the same to setting up the 'regular' ActionBar items as far as the XML is concerned. This example in the developer's guide explains it all.

In order to use ActionBarSherlock, replace the default Android-callbacks to the ActionBarSherlock-edited callbacks (e.g. instead of Android.View.ActionMode, use com.actionbarsherlock.view.ActionMode).

Android Contextual Action Bar API 8+

The ActionBarActivity from the v7 support library supports the Contextual Action Bar via the same APIs as the API 11+ Activity class. The only difference is that the support methods all have "support" in them and you need to use the android.support.v7.view.ActionMode.Callback class for callbacks instead of the non-support version.

This has been a part of the v7 support library since the ActionBar classes were added to support the Action Bar in reversion 18, released in July of 2013.

See:

  • startSupportActionMode(ActionMode.Callback)
  • onSupportActionModeFinished(ActionMode)
  • onSupportActionModeStarted(ActionMode)

Multiple selection in custom ListView with CAB

Using ActionBarSherlock the MultiChoiceModeListener used in Luksprog´s answer is not yet available if you want to support API level < 11.

A workaround is to use the onItemClickListener.

List setup:

listView = (ListView) timeline.findViewById(android.R.id.list);
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
listView.setItemsCanFocus(false);
listView.setAdapter(new ListAdapter(getActivity(), R.layout.cleaning_list_item, items));

Listener of ListFragment or ListActivity:

@Override
public void onListItemClick(ListView l, View v, int position, long id) {
SparseBooleanArray checked = listView.getCheckedItemPositions();
boolean hasCheckedElement = false;
for (int i = 0; i < checked.size() && !hasCheckedElement; i++) {
hasCheckedElement = checked.valueAt(i);
}

if (hasCheckedElement) {
if (mMode == null) {
mMode = ((SherlockFragmentActivity) getActivity()).startActionMode(new MyActionMode());
mMode.invalidate();
} else {
mMode.invalidate();
}
} else {
if (mMode != null) {
mMode.finish();
}
}
}

Where MyActionMode is an implementation of ActionMode.Callback:

private final class MyActionMode implements ActionMode.Callback { /* ... */ }

Showing the menu item in left side of actionbarsherlock

It looks like you are looking for a 'Contextual ActionBar ActionMode'.

I'm on my phone at the moment but this question seems to point you in the right direction: How can you implement multi-selection and Contextual ActionMode in ActionBarSherlock?

Multiple selection in ListView with CAB

That tutorial does not use addHeaderView(). Your code uses addHeaderView(). Your error is involving the header view:

java.lang.ClassCastException: android.widget.HeaderViewListAdapter cannot be cast to com.damson.android.tipspromenad.tabs.CreateFragment$SelectableAdapter

This is because when you call addHeaderView(), getListAdapter() will no longer return your own adapter, but rather a new adapter, one that wraps yours and supplies the header view.

Call getWrappedAdapter() on the HeaderViewListAdapter to get your SelectableAdapter.


UPDATE

HeaderViewListAdapter wasThisReallySoHard=(HeaderViewListAdapter)listView.getAdapter();
SelectableAdapter adapter=(SelectableAdapter)wasThisReallySoHard.getWrappedAdapter();

MultiChoiceModeListener causing issues with SherlockListFragment

As a kind person from reddit has notified me, apparently ActionBarSherlock does not currently support MultiChoiceModeListener. The fact that I'm using ActionBarSherlock's menus when the listener wants native Android menus probably contributes to the issue as well.

How do I create a Search field in a contextual action bar?

First, we should set always value for app:showAsAction of all menu items:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<group android:id="@+id/group_search_mode">
<item
android:id="@+id/pdf_menu_search_item"
android:icon="@drawable/ic_pdf_action_search"
android:title="@string/search"
app:actionViewClass="android.support.v7.widget.SearchView"
app:showAsAction="always"/>
<item
android:id="@+id/pdf_menu_search_prev"
android:icon="@drawable/ic_pdf_action_search_prev"
android:title="@string/search_prev"
app:showAsAction="always"/>
<item
android:id="@+id/pdf_menu_search_next"
android:icon="@drawable/ic_pdf_action_search_next"
android:title="@string/search_next"
app:showAsAction="always"/>
</group>
</menu>

Secondary, in this case we don't need to set intent filter for our Activity and searchable info for our SearchView.

Definition of this Activity in AndroidManifest.xml:

<activity
android:name="com.myapp.myActivity"
android:label="@string/app_name" />

ActionMode.Callback implementation:

private ActionMode.Callback mActionModeSearchCallback = new ActionMode.Callback() {

private SearchView mSearchView;

@Override
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
actionMode.getMenuInflater().inflate(R.menu.home, menu);
mSearchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.pdf_menu_search_item));
mSearchView.setIconifiedByDefault(false);
mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String s) {
return false;
}

@Override
public boolean onQueryTextChange(String s) {
return false;
}
});
return true;
}

@Override
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
mSearchView.requestFocus();
return true;
}

@Override
public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
switch (menuItem.getItemId()) {
case R.id.pdf_menu_search_prev:
findPrevSearchResult();
return true;
case R.id.pdf_menu_search_next:
findNextSearchResult();
return true;
default:
return false;
}
}

@Override
public void onDestroyActionMode(ActionMode actionMode) {

}
};

I just tried this code on three 4.0+ devices and it was fully working. But I didn't test on devises with lower OS versions.

Hope it will be helpful for you.



Related Topics



Leave a reply



Submit