在浏览器里,我们经常在网页上看到中意的内容,想选择特定的内容,完后复制,再在其它地方粘贴,这是很常见的功能,不过由于是在网页中,所以理论上来说,应该是用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;
}
});
}
}
源码下载