40

I have an app where I want to be able to show a TextView (or EditText) that allows the user to select some text, then press a button to have something done with that text. Implementing this on Android versions prior to Honeycomb is no problem but on Honeycomb and above the default long-press action is to show an action bar with Copy/Cut/Paste options. I can intercept long-press to show my own action bar, but then I do not get the text selection handles displayed.

Once I have started my own ActionMode how do I get the text selection handles displayed?

Here is the code I'm using to start the ActionMode, which works except there are no text selection handles displayed:

public boolean onLongClick(View v) {
    if(actionMode == null)
        actionMode = startActionMode(new QuoteCallback());
    return true;
}

class QuoteCallback implements ActionMode.Callback {

    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
        MenuInflater inflater = mode.getMenuInflater();
        inflater.inflate(R.menu.quote, menu);
        return true;
    }

    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
        return false;
    }

    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
        switch(item.getItemId()) {

        case R.id.quote:
            Log.d(TAG, "Selected menu");
            mode.finish();
            // here is where I would grab the selected text
            return true;
        }
        return false;
    }

    public void onDestroyActionMode(ActionMode mode) {
        actionMode = null;
    }
}
Charles
  • 50,943
  • 13
  • 104
  • 142
Clyde
  • 7,389
  • 5
  • 31
  • 57
  • Is there any way by which we can do this on a button cliick, like.. i have a textview, which is selectable. i want to launch the defaultactionmode associated wit text view (with select all and copy), on click of a button. I cant use performLongClick () of textview as, it is already overridden. Is there any possibility to acheive this? I tried startActionMode(), but it opens with a blank action bar.. – Rahul May 21 '15 at 15:03

3 Answers3

59

I figured out the answer to my own question; TextView (and therefore EditText) has a method setCustomSelectionActionModeCallback() which should be used instead of startActionMode(). Using this enables customisation of the menu used by TextView for text selection. Sample code:

bodyView.setCustomSelectionActionModeCallback(new StyleCallback());

where StyleCallback customises the text selection menu by removing Select All and adding some styling actions:

class StyleCallback implements ActionMode.Callback {

    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
        Log.d(TAG, "onCreateActionMode");
        MenuInflater inflater = mode.getMenuInflater();
        inflater.inflate(R.menu.style, menu);
        menu.removeItem(android.R.id.selectAll);
        return true;
    }

    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
        return false;
    }

    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
        Log.d(TAG, String.format("onActionItemClicked item=%s/%d", item.toString(), item.getItemId()));
        CharacterStyle cs;
        int start = bodyView.getSelectionStart();
        int end = bodyView.getSelectionEnd();
        SpannableStringBuilder ssb = new SpannableStringBuilder(bodyView.getText());

        switch(item.getItemId()) {

        case R.id.bold:
            cs = new StyleSpan(Typeface.BOLD);
            ssb.setSpan(cs, start, end, 1);
            bodyView.setText(ssb);
            return true;

        case R.id.italic:
            cs = new StyleSpan(Typeface.ITALIC);
            ssb.setSpan(cs, start, end, 1);
            bodyView.setText(ssb);
            return true;

        case R.id.underline:
            cs = new UnderlineSpan();
            ssb.setSpan(cs, start, end, 1);
            bodyView.setText(ssb);
            return true;
        }
        return false;
    }

    public void onDestroyActionMode(ActionMode mode) {
    }
}

The XML for the menu additions is:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/italic"
          android:showAsAction="always"
          android:icon="@drawable/italic"
          android:title="Italic"/>
    <item android:id="@+id/bold"
          android:showAsAction="always"
          android:icon="@drawable/bold"
          android:title="Bold"/>
    <item android:id="@+id/underline"
          android:showAsAction="always"
          android:icon="@drawable/underline"
          android:title="Underline"/>
</menu>
ByteHamster
  • 4,884
  • 9
  • 38
  • 53
Clyde
  • 7,389
  • 5
  • 31
  • 57
  • 4
    I'm trying the same, but because I add some more items, they don't fit in the ActionBar on phones. If I let them go to the overflow menu, the ActionMode is always destroyed as soon as I click on the overflow button. Did you encounter the same problem? Has anyone a workaround for it? – Maniac Jul 12 '13 at 07:15
  • Are we able to override methods for copy, share, web search etc. ? – thisiscrazy4 Jul 29 '13 at 18:53
  • @thisiscrazy4 Dunno about override, but you can remove any pre-populated menu items then add your own. – Clyde Jul 31 '13 at 22:55
  • 7
    setCustomSelectionActionModeCallback() is only for TextView and EditText. Is there anything for WebView? – thisiscrazy4 Aug 01 '13 at 15:10
  • @Maniac Did you find a solution to this problem? – Mahadevan Sreenivasan May 17 '14 at 13:36
  • No. Didn't have more ideas, so didn't try anything more. – Maniac May 18 '14 at 08:47
  • This only works with API>10, is there something for lower APIs? – David May 21 '14 at 08:30
  • What is **bodyView** exactly? – Iman Marashi May 09 '15 at 17:58
  • bodyView is the TextView that is showing the text being edited. – Clyde May 09 '15 at 21:21
  • Is there any way by which we can do this on a button cliick, like.. i have a textview, which is selectable. i want to launch the defaultactionmode associated wit text view (with select all and copy), on click of a button. I cant use performLongClick () of textview as, it is already overridden. Is there any possibility to acheive this? I tried startActionMode(), but it opens with a blank action bar. – Rahul May 21 '15 at 15:03
  • @Maniac I have the same problem right now. Did you solve it? I have about 6 items and not all are showing. and Clyde: Do your copy/paste/cut functions still work after this customization? –  Nov 29 '15 at 20:29
  • @Clyde i do so for my app, thanks.if i want to use Toast in this class, how can i define it? –  Apr 29 '16 at 10:36
  • @MinaDahesh Your question is completely orthogonal to the topic of this answer. To learn how to use Toast I recommend you read the [Toast Developer Guide](http://developer.android.com/guide/topics/ui/notifiers/toasts.html). – Clyde Apr 29 '16 at 20:30
  • @Clyde. i've one question, as i use this code, i can't use the the default menu btns like copy and paste... why ? and how can i solve it? – Mina Dahesh May 06 '16 at 13:36
  • @Clyde. this is the link of my question- would you please check it? http://stackoverflow.com/questions/37052973/how-to-enable-select-copy-texts-in-android – Mina Dahesh May 06 '16 at 14:48
  • @Clyde - How you are getting reference to bodyView in StyleCallback class? – shivamDev31 Sep 06 '16 at 11:36
  • and dont forget `bodyView.setTextIsSelectable(true);` – necip Nov 14 '21 at 22:40
10

Above solution is good if you want to customize the options in action bar. But if you want to override action bar copy/Paste etc, below is the code...

public class MainActivity extends Activity {
    EditText editText;
    private ClipboardManager myClipboard;
    private ClipData myClip;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myClipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
        editText = (EditText) findViewById(R.id.editText3);

        myClipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
        editText = (EditText) findViewById(R.id.editText3);
        editText.setCustomSelectionActionModeCallback(new Callback() {

            @Override
            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
                // TODO Auto-generated method stub
                return false;
            }

            @Override
            public void onDestroyActionMode(ActionMode mode) {
                // TODO Auto-generated method stub

            }

            @Override
            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
                // TODO Auto-generated method stub
                return true;
            }

            @Override
            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
                // TODO Auto-generated method stub
                switch (item.getItemId()) {
                case android.R.id.copy:
                    int min = 0;
                    int max = editText.getText().length();
                    if (editText.isFocused()) {
                        final int selStart = editText.getSelectionStart();
                        final int selEnd = editText.getSelectionEnd();

                        min = Math.max(0, Math.min(selStart, selEnd));
                        max = Math.max(0, Math.max(selStart, selEnd));
                    }
                    // Perform your definition lookup with the selected text
                    final CharSequence selectedText = editText.getText()
                            .subSequence(min, max);
                    String text = selectedText.toString();

                    myClip = ClipData.newPlainText("text", text);
                    myClipboard.setPrimaryClip(myClip);
                    Toast.makeText(getApplicationContext(), "Text Copied",
                            Toast.LENGTH_SHORT).show();
                    // Finish and close the ActionMode
                    mode.finish();
                    return true;
                case android.R.id.cut:
                    // add your custom code to get cut functionality according
                    // to your requirement
                    return true;
                case android.R.id.paste:
                    // add your custom code to get paste functionality according
                    // to your requirement
                    return true;

                default:
                    break;
                }
                return false;
            }
        });         
    }    
}
SKG
  • 346
  • 4
  • 11
  • 1
    I followed your solution and it workd almost. The only catch is that not all of my custom items is showing on the text selection toolbar. Anyway to implement a horizontal list to it? and the other thing is that the default functions like copy/paste, they dont work anymore, but the icons is showing, is it possible to reactive them again? –  Dec 15 '15 at 20:39
0

Easiest way to do it is to add a line in your main theme style which you have defined in your application tag of AndroidManifest. Open your theme style and add the following :

<item name="actionModeBackground">@color/your_color</item>

OR

<item name="android:actionModeBackground">@color/your_color</item>

For example: My theme style which I have defined:

<style name="AppTheme" parent="AppBaseTheme">

        <item name="calendarViewStyle">@style/Widget.Holo.CalendarView</item>
        <item name="android:actionBarStyle">@style/AppTheme1</item>
        <!-- below is the line you have to add -->
        <item name="android:actionModeBackground">@color/black_actionBar</item>
</style>
Pankaj
  • 7,908
  • 6
  • 42
  • 65