Using Android’s ActionMode
Implementing reusable contextual menus for Views and Activities
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:
ActionMode
ActionMode
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.
ActionMode
s 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 aWidget.AppCompat.ActionMode
style that includes attributes such asheight
,titleTextStyle
andsubtitleTextStyle
windowActionModeOverlay
: Flag indicating whether primaryActionMode
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