Android Marshmallow实现文字选中

在浏览器里,我们经常在网页上看到中意的内容,想选择特定的内容,完后复制,再在其它地方粘贴,这是很常见的功能,不过由于是在网页中,所以理论上来说,应该是用JavaScript或者其它类似的技术实现的,而不是Android,今天在看Marshmallow的文档时,意外发现新推出了Text Selection的功能,就学习了一下,自己写了个小例子,先看效果图:


功能很简单,就是上面一个TextView,选中时会弹出一个自定义的菜单,选择要复制的内容,完后选择“复制”,再在下面的EditText中长按,选择粘贴,将刚才复制的内容粘贴到EditText的指定位置,另外,这里复制不仅仅针对本应用,在其它地方也可以粘贴,因为复制的内容放到剪切板里面去了。注意,这是Marshmallow引入的新功能,其它版本应该是看不到效果的。


具体实现步骤如下:

1. 创建布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

    <TextView
        android:id="@+id/copyArea"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="测试Android的Marshmallow版本提供的Text Selection功能"
        android:textSize="20sp"/>

    <EditText
        android:id="@+id/pasteArea"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

这没什么好说的,上面一个TextView,下面一个EditText


2. 创建菜单文件

<menu xmlns:android="http://schemas.android.com/apk/res/android" >

    <item
        android:id="@+id/copy"
        android:title="复制"/>
    
    <item
        android:id="@+id/paste"
        android:title="粘贴"/>
</menu>

这个也很简单,两个菜单,复制和粘贴


3. 创建ActionMode.Callback

这个也不是什么新方法,在3.0版本就引入了,主要需要实现四个回调

(1)

@Override
            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
                MenuInflater inflater = mode.getMenuInflater();
                inflater.inflate(R.menu.main, menu);
                return true;
            }
(2)创建的时候,将第2步中创建的菜单引入

@Override
            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
                for (int i = 0; i < menu.size(); i++) {
                    MenuItem item = menu.getItem(i);
                    if (!menuIds.contains(item.getItemId()))
                        item.setVisible(false);
                }
                return false;
            }
这里的menuIds是一个List,将我们自定义的两个菜单的id存入,之所以需要这个,是因为Android系统默认已经指定了几个菜单,默认我们的菜单是加在指定的几个菜单后面的,所以这里需要将Android默认的菜单隐藏起来。

(3)

@Override
            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
                switch (item.getItemId()) {
                    case R.id.copy:
                        int min = 0;
                        int max = copyArea.getText().length();
                        if (copyArea.isFocused()) {
                            final int start = copyArea.getSelectionStart();
                            final int end = copyArea.getSelectionEnd();

                            min = Math.max(0, Math.min(start, end));
                            max = Math.max(0, Math.max(start, end));
                        }

                        cmb.setPrimaryClip(ClipData.newPlainText("paste_content", copyArea.getText().subSequence(min, max)));

                        mode.finish();
                        return true;
                    case R.id.paste:
                        if (pasteArea.isFocusable()) {
                            int index = pasteArea.getSelectionStart();
                            Editable editable = pasteArea.getText();
                            editable.insert(index, cmb.getPrimaryClip().getItemAt(0).coerceToText(MainActivity.this));
                        }

                        mode.finish();
                        return true;
                }

                return false;
            }
这里是实现的重点,也就是点击“复制”和“粘贴”菜单时,所需要做的操作,先看复制,copyArea就是上面的TextView,也就是我们需要复制的部分,当我们点了“复制”按钮时,通过getSelectionStart和getSelectionEnd方法,我们可以获取到我们选择的内容,注意这两个方法在没有选择的时候,都有可能返回-1,出于程序健壮性的考虑,这里做了一些数学上的判断,确保获取到正确选择的内容。而cmb是一个ClipboardManager对象,也就是剪切板,通过它,我们可以将复制的内容保存在系统里,在应用外也可以使用,最后的mode.finish用于关闭菜单。

再看粘贴,粘贴在EditText处理,粘贴时,首先获取当前光标的位置,再获取EditText当前的内容,完后在光标位置插入我们刚才保存在剪切板里面的内容即可。

(4)

@Override
            public void onDestroyActionMode(ActionMode mode) {
                actionMode = null;
            }
最后这个是在菜单destroy时,将actionMode赋值为Null,以便gc回收,这个actionMode是在下面长按EditText时声明的


4. 设置EditText的长按事件

 pasteArea.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                if (actionMode != null) {
                    return false;
                }

                actionMode = startActionMode(callback, ActionMode.TYPE_FLOATING);

                return true;
            }
        });
actionMode不为null表示菜单已经弹出了,直接返回,这里通过startActionMode方法,指定第3步中声明的callback,从而弹出刚才的菜单,后面一个参数TYPE_FLOATING是指浮动菜单


5. 指定TextView的选择属性

 copyArea.setTextIsSelectable(true);
        copyArea.setCustomSelectionActionModeCallback(callback);

通过以上的几步,我们就可以实现效果图中的效果了,完整MainActivity.java如下:

public class MainActivity extends AppCompatActivity {
    private ActionMode actionMode;
    private ActionMode.Callback callback;
    private TextView copyArea;
    private EditText pasteArea;
    private List<Integer> menuIds = new ArrayList<>();
    private ClipboardManager cmb;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        init();
    }

    private void init() {
        menuIds.add(R.id.copy);
        menuIds.add(R.id.paste);

        copyArea = (TextView) findViewById(R.id.copyArea);
        pasteArea = (EditText) findViewById(R.id.pasteArea);
        cmb = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);

        callback = new ActionMode.Callback() {
            @Override
            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
                MenuInflater inflater = mode.getMenuInflater();
                inflater.inflate(R.menu.main, menu);
                return true;
            }

            @Override
            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
                for (int i = 0; i < menu.size(); i++) {
                    MenuItem item = menu.getItem(i);
                    if (!menuIds.contains(item.getItemId()))
                        item.setVisible(false);
                }
                return false;
            }

            @Override
            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
                switch (item.getItemId()) {
                    case R.id.copy:
                        int min = 0;
                        int max = copyArea.getText().length();
                        if (copyArea.isFocused()) {
                            final int start = copyArea.getSelectionStart();
                            final int end = copyArea.getSelectionEnd();

                            min = Math.max(0, Math.min(start, end));
                            max = Math.max(0, Math.max(start, end));
                        }

                        cmb.setPrimaryClip(ClipData.newPlainText("paste_content", copyArea.getText().subSequence(min, max)));

                        mode.finish();
                        return true;
                    case R.id.paste:
                        if (pasteArea.isFocusable()) {
                            int index = pasteArea.getSelectionStart();
                            Editable editable = pasteArea.getText();
                            editable.insert(index, cmb.getPrimaryClip().getItemAt(0).coerceToText(MainActivity.this));
                        }

                        mode.finish();
                        return true;
                }

                return false;
            }

            @Override
            public void onDestroyActionMode(ActionMode mode) {
                actionMode = null;
            }
        };

        copyArea.setTextIsSelectable(true);
        copyArea.setCustomSelectionActionModeCallback(callback);

        pasteArea.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                if (actionMode != null) {
                    return false;
                }

                actionMode = startActionMode(callback, ActionMode.TYPE_FLOATING);

                return true;
            }
        });
    }
}

源码下载



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值