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
Adb Cannot Start Daemon, Createprocess Failure, Error 2
Android - Firebase Quickstart Email/Password Auth Demo Doesn't Work
When Should One Use Rxjava Observable and When Simple Callback on Android
Resize Image to Full Width and Variable Height with Picasso
Find the Key Hash for a Signed App
How to Disable Crashlytics During Development
How to Get a Fragment to Remove Itself, I.E. Its Equivalent of Finish()
Custom Global Application Class Breaks with "Android.App.Application Cannot Be Cast To"
Cordova Platform Add Android Not Working While Listing Android Targets
Progress Bar While Loading Image Using Glide
Differencebetween Sendstickybroadcast and Sendbroadcast in Android
Failure [Install_Failed_Older_Sdk] Android-L