
In Andriod, when you select text, a menu appears with actions that you can perform: Cut, Copy, Submit. Android Marshmallow (SDK 23) has the opportunity to expand this menu and give the user easy access to additional features when working with the text: "Translate", "Comment", "Quote".
In preparation for speaking at the
GDG conference
in Nizhny Novgorod, I discovered that this new feature is extremely poorly documented, the only available article is not
entirely true, and there are vanishingly few examples on the Internet of using this feature. I had to figure it out myself. The results of the study and I want to share. This can save you a lot of time.
Since various menus in Android appeared evolutionarily, it’s easiest to start the story “from the stove”. Developers with experience can boldly flip right through to the
“New” section without any risk of missing anything. If that - then come back. Younger developers can find useful examples in practice under spoilers.
Old
The first. Vulgaris menu
The story about the oldest regular menu. With examples and screenshotsThe menus in Android are quite similar: they are described using
XML , then at the right time the corresponding objects are created in memory according to this description using
MenuInflater , this menu is displayed, and information about clicking on the corresponding element comes to callback.
')
The usual menu, which at times was called by pressing the hardware key “Menu”, is described in the application resources as an XML file.
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/action_foo" android:orderInCategory="100" android:title="@string/word_foo"/> <item android:id="@+id/action_foobar" android:orderInCategory="101" android:title="@string/word_bar"/> <item android:id="@+id/action_baz" android:orderInCategory="102" android:title="@string/word_baz"/> </menu>
Menu items will be sorted by field android: orderInCategory.
The menu is created by invoking the inflater in the
onCreateOptionsMenu () method.
@Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.my_menu, menu); return true; }
Information on the selection of a menu item falls into the
onOptionsItemSelected () method.
@Override public boolean onOptionsItemSelected(MenuItem item) {
Everything described above leads to the following menu:

Everything is simple and familiar.
The second. Context menu
The story about the context menu. With examples and screenshotsThe context menu is a floating menu that appears when you long click on any element of the interface. This element must be previously registered to work with the context menu.
Context menus are most often used when working with lists. But this story would be more about lists than about menus, so we leave it outside the article. Examples will be without them.
Consider a simple example.
You can register an item, for example, in
onCreate () :
@Override protected void onCreate(Bundle savedInstanceState) { ... registerForContextMenu(findViewById(R.id.text_view_one)); }
Then everything is similar to the previous item, only inside other methods (
onCreateContextMenu () and
onContextItemSelected () ):
@Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.my_menu, menu); }
@Override public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_foo: toast("foo"); return true; case R.id.action_foobar: toast("bar"); return true; case R.id.action_baz: toast("baz"); return true; default: return super.onContextItemSelected(item); } }
With a long click on the element with the text context menu appears.
Third. Menu in AppBar and Toolbar
The story about the menu with the Toolbar. With examples and screenshotsStarting with Android Honeycomb (aka 3.0, it’s SDK 11, that is, also quite a long time ago), the hardware Menu button was abolished, and the menu was displayed in the “action bar”, which is also the “application bar”. It was possible to display some of the menu items in this line as icons, and leave some of them hidden before clicking on the menu icon to the right.
Fields for describing icons and locations added XML with a description of the menu. Otherwise, it remains the same menu with the known
onCreateOptionsMenu () and
onOptionsItemSelected () .
Menu with icons and location. Added android: icon and android: orderInCategory <?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"> <item android:id="@+id/action_foo" android:icon="@android:drawable/ic_media_previous" android:orderInCategory="100" android:title="@string/word_foo" app:showAsAction="ifRoom" /> <item android:id="@+id/action_foobar" android:icon="@android:drawable/ic_media_next" android:orderInCategory="101" android:title="@string/word_bar" app:showAsAction="ifRoom" /> <item android:id="@+id/action_baz" android:icon="@android:drawable/ic_media_pause" android:orderInCategory="102" android:title="@string/word_baz" app:showAsAction="never"/> </menu>
 Visible part of the menu
|  Hidden part of the menu if you click on the "three points"
|
At this level, people usually know about the menu after the first newcomer book. Thank you for having enough patience to read this far, it will be more interesting further.
Fourth. Contextual action mode
Story about contextual action mode. With examples and screenshotsThe context action mode, also known as the “Contextual action mode”, allows you to show the user a set of actions that can be performed on the selected item. Like the context menu, this tool is convenient when working with lists, but we do not consider lists in this article.
In order to consider the operation of this mode, we need an element that you can "choose." Take for example
ToggleButton . This mode the application activates itself, respectively, do it when the status of the ToggleButton changes:
checkedListener = new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { actionMode = startActionMode(actionModeCallback); actionMode.setTitle("Action Mode"); } else { if (actionMode != null) { actionMode.finish(); } } } }; toggleButton.setOnCheckedChangeListener(checkedListener);
actionModeCallback is an instance of the
ActionMode.Callback class that contains callbacks for working with menus. The menu remains the same, all with the same good old mechanics:
actionModeCallback = new ActionMode.Callback() { @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { MenuInflater inflater = mode.getMenuInflater(); inflater.inflate(R.menu.my_menu_two, menu); return true; } ... @Override public void onDestroyActionMode(ActionMode mode) { actionMode = null; toggleButton.setChecked(false); } };
Notice the value that we store in the actionMode variable. This is an instance of the
ActionMode class, we need it in order to be able to change the title (
actionMode.setTitle () ), the subtitle (
actionMode.setSubtitle () ), and also complete this mode (
actionMode.finish () ). After the action is completed, the mode does not automatically end, and if we need to, we must complete it ourselves.
The selection of the user is also processed in the usual way:
actionModeCallback = new ActionMode.Callback() { ... @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { switch (item.getItemId()) { case R.id.action_compass: toast("compass"); return true; case R.id.action_camera: toast("camera"); return true; default: return false; } } };
The menu for contextual actions should obviously differ from the main one, so we will create another XML with a description.
XML with menu description. Same in structure, but with other points <?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"> <item android:id="@+id/action_camera" android:icon="@android:drawable/ic_menu_camera" android:orderInCategory="100" android:title="@string/word_camera" app:showAsAction="ifRoom" /> <item android:id="@+id/action_compass" android:icon="@android:drawable/ic_menu_compass" android:orderInCategory="101" android:title="@string/word_compass" app:showAsAction="ifRoom" /> </menu>
We get this behavior:
Everything old, that is, known at least with the SDK 11 is now over, the unknown has begun.
New
In Android Marshmallow (aka 6.0, aka SDK 23), two innovations appeared in the menu. Both of these innovations are not yet supported by the Support Library and only work on devices with SDK 23. Therefore, before calling the methods that appeared, you need to check the SDK number of the device on which the application is running.
For the convenience of the story, we will get rid of these checks by specifying minSdkVersion 23.
The fifth. Another context menu
The method for creating the
“Context Action Mode” (described above) has been extended. The second parameter
startActionMode () can pass a constant that specifies the type of display.
The value of
ActionMode.TYPE_PRIMARY corresponds to the old behavior. That is,
startActionMode (actionModeCallback) and
startActionMode (actionModeCallback, ActionMode.TYPE_PRIMARY) are the same.
If you specify the type
ActionMode.TYPE_FLOATING , the menu takes the following form:

No icons. Location - horizontal.
If there are so many points that they do not fit in width, then the familiar “three points” will appear:
Everything else is exactly the same as in
“Contextual action mode” (see above).
The sixth. Expansion of the menu when selecting text
And finally, what was promised in the
article on innovations in Android Marshmallow . Now you can add a menu to the menu that appears when you select text. The animated picture of this miracle was at the very beginning of the article, but if you want, it is again under the spoiler.
Animated picture from the menu Implementation
To achieve this effect, you first need to create a
callback inherited from ActionMode.Callback. That is, it is completely analogous to what we created in the two sections above for the
“Contextual action mode” .
Further, for all elements in which we want to expand the menu when editing, you need to specify this callback:
textView.setTextIsSelectable(true); textView.setCustomSelectionActionModeCallback(actionModeCallback); editText.setCustomSelectionActionModeCallback(actionModeCallback); editText.setCustomInsertionActionModeCallback(actionModeCallback);
The
callback specified in
setCustomSelectionActionModeCallback () will be used if there is selected text. Specified in
setCustomInsertionActionModeCallback () - if there is no text. Separated them because not all actions make sense when nothing is selected, and, accordingly, the contents of the appearing menu should be different.
On an empty EditText it looks like this:
Oh. Where did my menu go?
Look at the screenshots below. Any idea where all the added menu items are?
A hint can be found here:

I will explain. It is likely that the remaining items will not fit in the second, vertical part of the menu either. And there will be a scroll. In the screenshots above it happened.
The problem is that the user doesn’t see any signs of scrolling: the scrolling icon disappears almost immediately, and the edge of the unclaimed item doesn’t look. I got the bug, let's see what the creators of this feature say.
code.google.com/p/android/issues/detail?id=195043Where did my menu go again?

This time there is another problem: the menu does not expand with fullscreen input mode as described above. I found only one workout: turn off fullscreen mode using android: imeOptions = "flagNoExtractUi".
How to place your item before the standard?
Native menu items have order parameters from 1 to 5. Therefore, using the android: orderInCategory in the menu description, you cannot set the position before the native items. But you can change the order of items in the already formed menu, for example, like this:
@Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
We get the result:
Oddities in the documentation
There is no normal documentation for expanding the menu when selecting text. There is an
article already mentioned, an
article about Android Marshmallow updates .
I reread this place several dozen times, but I could not relate what is written there to practice. I tell in order.

If you read this article or tried it yourself, you noticed that
startActionMode (callback, ActionMode.TYPE_FLOATING) does create a floating toolbar, but not at all for selection. And for selection it creates
setCustomSelectionActionModeCallback () .

The second incomprehensible instruction. The
setCustomSelectionActionModeCallback () method of the
android.widget.TextView class expects to receive an
ActionMode.Callback .
android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/TextView.javaIf you look at
android.widget.Editor , then the mCustomSelectionActionModeCallback field is also of type
ActionMode.Callback :

And nowhere in this code is it expected that custom callback will be of type
ActionMode.Callback2 .
android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/Editor.javaThe article continues to talk hard about how to use
ActionMode.Callback2 :

My guess is that a fragment of some internal documentation on
ActionMode.Callback2 got into the article due to an editor's oversight. Do you have any other hypothesis, write about it.
Example
The entire code from this article can be found on
GitHub .