37

I use ActionMode to select items in a grid. The problem is that I cannot recognize whether exactly the Done button is clicked. The only I can is to know that ActionMode is finished. But pressing Back finishes the ActionMode too. The desired behavior is to accept selection on Done click, and exit ActionMode on Back press.

I tried to use ActionMode.setCustomView() but it doesn't affect the Done button. The Activity.onBackPressed() is not called when ActionMode is started.

The one solution I've found is to use ActionBarSherlock and get the Done button manually:

View closeButton = findViewById(R.id.abs__action_mode_close_button); 

But it works on Android 2.x-3.x only, because on 4.x a native action bar is used.

Tanya Vybornova
  • 551
  • 1
  • 6
  • 15

3 Answers3

39

Please don't do that as it's implementation specific and extremely non-standard.

You can use the onDestroyActionMode callback for when an action mode is dismissed.

Jake Wharton
  • 75,598
  • 23
  • 223
  • 230
  • I see, but I'm implementing a picker with multiselection support and I thought that the Done button is a best way to accept selection... Any way, thanks for you answer! It seems like I have to add a menu item for accepting selected items. – Tanya Vybornova Jul 25 '12 at 06:30
  • 10
    But how can we distinguish from the done button and the back button? I need to implement different actions for those... – rfgamaral Oct 07 '12 at 23:01
  • You might be able to intercept `onBackPressed`. Otherwise, you can't. – Jake Wharton Oct 07 '12 at 23:10
  • @JakeWharton how to distinguish the destruction of ActionMode due to screen rotation or done button . actually i have the a list of ids to add to fav database table , so if the device orientation change i should retain this array and if the user click the done button i should clear it , please help – user4o01 Oct 09 '12 at 19:28
  • @JakeWharton but how about this scenario: I have a fragment with a list view in it, and that fragment is in a ViewPager. Tap and hold starts CAB, and pressing done cancels CAB. Say i'm in the middle of multiselecting things (so CAB is on). When I swipe to another fragment in the ViewPager, naturally I want to remove the CAB so to do this, I finish the actionmode on the first fragment's onPause method and when the user swipes back to that fragment, i start the CAB again. I think i should be able to differentiate dismissing CAB via swipe and via Done so that I can restart CAB onPause – Neilers Jan 17 '13 at 13:29
  • If you dismissed the action mode then your app should know that it was done via swipe. If you want different behavior file a feature request on http://b.android.com for it. I'm simply telling you the existing behavior. – Jake Wharton Jan 17 '13 at 19:44
  • @Tanya: A menu item is absolutely the correct way to accept selected items. "Done" doesn't mean "done selecting items now do something" but rather "cancel all selections." – lilbyrdie Jun 07 '13 at 16:04
  • @JakeWharton thank you for answer. its useful for me. – Jatinkumar Patel Aug 05 '14 at 05:48
  • my problem is that onDestroyActionMode gets called as soon as the ActionMode is created, therefore I cannot use the BS standard callback. Android sucks – Gubatron May 02 '17 at 17:13
21

Here is the solution:

ActionMode mMode = MyActivityClass.this.startActionMode(some implementation);
int doneButtonId = Resources.getSystem().getIdentifier("action_mode_close_button", "id", "android");
View doneButton = MyActivityClass.this.findViewById(doneButtonId);
doneButton.setOnClickListener(new View.OnClickListener() {

    @Override
    public void onClick(View v) {
        // do whatever you want 
        // in android source code it's calling mMode.finish();
    }
});
Alexiosdev
  • 272
  • 3
  • 7
  • 4
    just a side note: pay attention that you call `findViewById` from the activity where you started the action mode and not from an other view, or you will get a `null pointer exception`. – OschtärEi Apr 16 '13 at 17:37
  • 1
    This does not work with ABS on pre-ICS devices like Gingerbread. – bk138 Jun 12 '13 at 14:42
  • 1
    This code is only works on API LEVEL >= 11 You need to add API LEVEL condition: View doneButton; if (android.os.Build.VERSION.SDK_INT >= 11) { doneButton = Resources.getSystem().getIdentifier("action_mode_close_button", "id", "android"); } else { doneButton = findViewById(R.id.abs__action_mode_close_button); } – Alexiosdev Jul 05 '13 at 13:49
  • 3
    WARNING: this is an inflexible solution and it is essentially accessing a private API. If Google ever changes the name of action_mode_close_button, this will cease to work. – Jon Willis Sep 13 '13 at 21:16
  • 1
    This solution works perfectly except on orientation change button looses its click listener. I am not destroying activity on orientation change. – Mahendran Dec 03 '14 at 07:12
11

Here is my implementation, and it's a proper hack but it works and I can't really find an alternative to doing something specific when the ActionMode DONE is clicked. I find it really weird that you can't capture this event more elegantly.

Any suggestions to making this slightly less ugly would be greatly appreciated...

In my activity..

boolean mActionModeIsActive = false;
boolean mBackWasPressedInActionMode = false;

@Override
public boolean dispatchKeyEvent(KeyEvent event)
{
    mBackWasPressedInActionMode = mActionModeIsActive && event.getKeyCode() == KeyEvent.KEYCODE_BACK;
    return super.dispatchKeyEvent(event);
}

@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu)
{
    mActionModeIsActive = true;
    return true;
}

@Override
public void onDestroyActionMode(ActionMode mode)
{
    mActionModeIsActive = false;

    if (!mBackWasPressedInActionMode)
        onActionModeDoneClick();

    mBackWasPressedInActionMode = false;
}

public void onActionModeDoneClick();
{
    // Do something here.
}

If you are using Fragments with your Activity then some of this code will probably need to be in the Fragment, and the other bits in the Activity.

@JakeWharton (and other ActionBarSherlock users) if you see this on your travels. I'd be interested to know if the above is compatible with ABS as I have yet to integrate ABS with my current project.

Eurig Jones
  • 8,226
  • 7
  • 52
  • 74
  • This did work, but I am using action modes in a few places in my project. So I decided to add MenuItems to the action modes and use these as the actual "confirm" items. The main I converted the icon into a dismiss. This makes the code a lot less hacky. – Eurig Jones Apr 01 '13 at 18:36
  • It looks like this would also detect when normal menu items are clicked, not just when the Done button was pressed. – JDJ Dec 17 '14 at 03:12
  • @JDJ what makes you think that? – androidguy Nov 29 '16 at 22:49
  • As for the answer, looks good but it works without mActionModeIsActive as well: instead just be sure to set `mBackWasPressedInActionMode = false` in onCreateActionMode and then remove the `&&` in `dispatchKeyEvent` so it's just the second test. – androidguy Nov 29 '16 at 22:53