Using Android’s ActionMode

Implementing reusable contextual menus for Views and Activities

Nick Rout
Over Engineering
Published in
6 min readMay 2, 2018

--

Featured in Android Weekly Issue #308

When it comes to implementing menus on Android, there is a plethora of options at your disposable. You can use a ContextMenu, a PopupMenu, an ActionMenuView, an options menu in your Activity or your own custom implementation.

For contextual menus (i.e. menus that focus user interaction toward performing contextual actions on specific items in your UI), the documentation stipulates that using Android’s ActionMode is the “preferred technique for displaying contextual actions”.

So what is it? 🤔

ActionMode has seen various incarnations over the years, often changing with different Android releases. Essentially it just provides a bit of temporary system UI (sometimes “replacing” the normal UI) that displays a menu of contextual actions (usually in response to a user action such as a long-press or select). As of recent Android versions, this UI has become fairly consistent.

There are two types of ActionMode:

Primary

Appears as a contextual action bar that is shown over an existing app bar (or in place of one if your theme/layout does not include one). Menu items are populated as actions to the right of the action bar, potentially ending up in the overflow menu. A back button allows this ActionMode to be dismissed by the user and it can also be dismissed programatically. An optional title and subtitle can be set.

Floating

Appears as a floating toolbar that is shown over other contents at a variable position. Menu items are populated as actions in the floating toolbar, potentially ending up in the overflow menu. This ActionMode can only be dismissed programatically.

Note: Floating ActionMode is only available from API 23 onwards.

How do we show an ActionMode? 🏁

You can show an ActionMode from a View or an Activity. In both cases, there are two main functions you should be aware of:

fun startActionMode(callback: ActionMode.Callback): ActionMode

Shows a primary ActionMode and returns it for convenience. Accepts a callback that will handle lifecycle events for the ActionMode.

fun startActionMode(callback: ActionMode.Callback, type: Int): ActionMode

Same as above, with the addition of the type parameter. This indicates whether the ActionMode should be primary (ActionMode.TYPE_PRIMARY) or floating (ActionMode.TYPE_FLOATING). Note: This function is also only available from API 23 onwards.

There are no View or Activity functions that allow you to dismiss an ActionMode, as that is done on the ActionMode itself (more on that later). You do, however, get access to some ActionMode lifecycle callback functions that you can override in an Activity (eg. onActionModeStarted and onActionModeFinished).

What about that ActionMode.Callback? 👀

This could be considered the key part of getting an ActionMode to look and work as expected.

ActionMode.Callback is an interface in which you can specify actions (by inflating a menu), respond to click events on action items, and handle other lifecycle events for the ActionMode.

There are four required functions to override in an implementation of ActionMode.Callback:

override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
// Called after startActionMode
// Inflate a menu resource providing context menu items
val inflater = mode.menuInflater
inflater.inflate(R.menu.menu_actions, menu)
return true
}
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
// Called each time the action mode is shown
return false
}
override fun onDestroyActionMode(mode: ActionMode) {
// Called when the action mode is finished
actionMode = null
}
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
// Called when the user selects a contextual menu item
when (item.itemId) {
R.id.action_1 -> // Handle first action
...
}
return true
}

Simply passing a callback like this to one of the startActionMode functions is enough to show an ActionMode!

Floating ActionMode is special ✨

When floating ActionMode was added in API 23, an abstract class extension of ActionMode.Callback was also introduced. It is appropriately called ActionMode.Callback2. It adds just one more function that you can optionally override (a default implementation will be used if not):

override fun onGetContentRect(mode: ActionMode, view: View, outRect: Rect) {
// Provides content rect information
// Required to dynamically position floating ActionMode,
// such that it doesn't obscure app content
outRect.set(left, top, right, bottom)
}

You could pass in the bounds of your View here, or the bounds of the content on which you are focusing contextual actions. If you want to show the floating ActionMode at a specific (x, y) point, you could make left = right and top = bottom.

It is important to note that these positional values are relative to the coordinate space of whatever View you have used to show the floating ActionMode. For an Activity, this is the Window Decor View.

Adding convenience classes 🤓

It would be nice if we had a reusable class that made showing (and dismissing) a specific type of ActionMode easier. We also need to to consider adding recommendations that don’t come out-of-the-box (eg. An ActionMode should be dismissed after an action item has been clicked).

Here are two examples of such classes:

A reusable callback class for a primary ActionMode
A reusable callback class for a floating ActionMode
A simple interface that listens for action item clicks

Note that these classes are not necessarily complete (eg. They lack functions to show an ActionMode for an Activity). However, with them we can now show an ActionMode of a specific type easier and with less code:

// Start primary ActionMode
val primaryActionModeCallback = PrimaryActionModeCallback()
primaryActionModeCallback.startActionMode(view, R.menu.menu_actions, "Title", "Subtitle")
// Start floating ActionMode
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val floatingActionModeCallback = FloatingActionModeCallback()
floatingActionModeCallback.startActionMode(view, R.menu.menu_actions, x, y, width, height)
}

Styling ActionModes 🎨

The default appearance of an ActionMode is familiar and also draws user attention. Regardless, you may still wish to adjust the look to match a proposed design, your brand or a specific color scheme.

ActionModes are styled on a theme level. The base colors and text appearances are in fact derived from your app/activity theme (eg. Dark background with light text for Theme.AppCompat and the opposite for Theme.AppCompat.Light).

At the time of this writing, there only exists theme attributes that can directly style the primary ActionMode. Under the hood, floating ActionMode uses an internal widget called FloatingToolbar which relies on its own set of private attributes.

Some of the notable primary ActionMode theme attributes include:

  • actionModeBackground: The color or drawable used for the background of the contextual action bar
  • actionModeStyle: A reference to a Widget.AppCompat.ActionMode style that includes attributes such as height, titleTextStyle and subtitleTextStyle
  • windowActionModeOverlay: Flag indicating whether primary ActionMode should overlay window content or resize the content to fit beneath it

I hope this post has provided some insight into the different types of ActionMode. Together with ActionMode.Callback, they can be used to show contextual menus for any View or Activity. If you have any questions, thoughts or suggestions then I’d love to hear from you!

Find me on Twitter @ricknout

--

--

Nick Rout
Over Engineering

Principal Android Engineer at GoDaddy | Ex-Google | Google Developer Expert for Android