Custom cut/copy action bar for EditText that shows text selection handles
Asked Answered
S

3

41

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;
    }
}
Stomodaeum answered 21/10, 2012 at 6:4 Comment(1)
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..Xavierxaviera
S
60

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>
Stomodaeum answered 22/10, 2012 at 3:44 Comment(17)
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?Anatolia
Are we able to override methods for copy, share, web search etc. ?Deformity
@Deformity Dunno about override, but you can remove any pre-populated menu items then add your own.Stomodaeum
setCustomSelectionActionModeCallback() is only for TextView and EditText. Is there anything for WebView?Deformity
@Anatolia Did you find a solution to this problem?Blacksmith
No. Didn't have more ideas, so didn't try anything more.Anatolia
This only works with API>10, is there something for lower APIs?Wringer
What is bodyView exactly?Rambow
bodyView is the TextView that is showing the text being edited.Stomodaeum
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.Xavierxaviera
@Anatolia 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?Rigney
@Stomodaeum i do so for my app, thanks.if i want to use Toast in this class, how can i define it?Heinrich
@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.Stomodaeum
@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?Equivocal
@Clyde. this is the link of my question- would you please check it? #37053473Equivocal
@Stomodaeum - How you are getting reference to bodyView in StyleCallback class?Hereof
and dont forget bodyView.setTextIsSelectable(true);Shaia
L
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;
            }
        });         
    }    
}
Lengthen answered 8/5, 2015 at 7:3 Comment(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?Rigney
R
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>
Richman answered 11/2, 2016 at 8:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.