上下文菜单遇到的坑
需求是:长按文字,弹出context menu,有复制、翻译等操作,等于是自定义系统的这个contextMenu,这算是初入android要实现的第一个功能,接到这个任务时,给我方向是学习一下ContextMenu,然后实现复制功能,翻译功能待续。
一、 使用contextMenu遇到的问题
contextMenu的使用方法大致如下:
-
先使用registerForContextMenu(mView);方法将要弹出上下文菜单的view注册进ContextMenu。
registerForContextMenu(textView);
-
设置TextView可以被选中
textView.setTextIsSelectable(true);
-
重写onCreateContextMenu这个方法,这个方法里面可以加入自定义菜单
@Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { menu.add(0,0,0,"copy");//自定义菜单,第一个参数代表组别,第二个参数是menuID,第三个参数是menuName menu.add(0,1,0,"translate"); super.onCreateContextMenu(menu, v, menuInfo); }
-
重写onContextItemSelected方法,利用switch判断按下的菜单并执行相对应的操作
@Override public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()){ case 0: //这个代表菜单ID Toast.makeText(this, "copy", Toast.LENGTH_LONG).show(); break; case 1: Toast.makeText(this, "translate", Toast.LENGTH_LONG).show(); break; } return super.onContextItemSelected(item); }
运行结果如下:
咋一看,觉得这么简单,就这么实现了,再写个复制功能不就完美了么?于是继续写复制功能,然后才发现有问题了,我到底要复制啥呢?这个ContextMenu和系统的好像不太一样,没有mark文字,也不能自由mark,这个context menu是针对整个view显示的,于是又查资料(其实就是百度),查自定义contextMenu如何选中文字。然后查了一整天无果,但是找到了一篇介绍ContextMenu底层是如何实现的,这篇博客给了我思路
传送门:https://blog.csdn.net/huawuque183/article/details/78559605
思路就是使用setCustomSelectionActionModeCallback();对view设置回调,然后在回调接口里实现自己的菜单以及功能,下来开始了对ActionMode的折腾
二、 ActionMode遇到的问题
ActionMode的使用也分为三步,分别是:
-
定义一个ActionMode.CallBack,这里要注意Android 6.0以下和Android 6.0以上的显示方法不一样,Android 6.0以下是在ToolBar的位置显示一个menu,并且覆盖了ToolBar,要new ActionMode.CallBack;Android 6.0以上是悬浮在长按的地方,要new ActionMode.CallBack2。
ActionMode.Callback2 actionModeCallBack2 = new ActionMode.Callback2() { @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { return true; } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { menu.clear(); menu.add(0,0,0,"copy"); menu.add(0,1,0,"translate"); return true; } @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { switch (item.getItemId()){ case 0: if (textView == null) {return false;} int min = 0; int max = textView.length(); if (textView.isFocused()){ final int selStart = textView.getSelectionStart(); final int selEnd = textView.getSelectionEnd(); min = Math.max(0, Math.min(selStart, selEnd)); max = Math.max(0, Math.max(selStart, selEnd)); } String content = String.valueOf(textView.getText().subSequence(min, max)); copyToClipboard(content);//复制到剪切板方法 mode.finish();//执行完销毁上下文菜单 break; case 1: //牵扯到公司业务,此处未实现 mode.finish(); break; } return false; } @Override public void onDestroyActionMode(ActionMode mode) { } @Override public void onGetContentRect(ActionMode mode, View view, Rect outRect) { super.onGetContentRect(mode, view, outRect); } };
-
重写CallBack里面的方法
//代码参见第一步
-
给TextView或者EditText添加回调,如果是TextView则还要设置可被选中
textView = (TextView) findViewById(R.id.text); textView.setTextIsSelectable(true); textView.setCustomSelectionActionModeCallback(actionModeCallBac2);
-
复制到剪切板
public void copyToClipboard(String string){ if (null == mClipboard){ mClipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); } ClipData clipData = ClipData.newPlainText("simple text", string); mClipboard.setPrimaryClip(clipData); Toast.makeText(this, string+"已复制到剪切板",Toast.LENGTH_SHORT).show(); }
运行结果如下:
当按下COPY时会吐司出选中的文本,下面是截图
当执行到这里,我以为终于做好了,然而太天真了,这个办法只能针对TextView和EditText,而也是这个时候我才知道我要实现的这个功能是针对WebView的。沟通是多么重要,当初可没给我说是WebView啊,给我说让我在TextView上实现,如果不行就试试WebView,哎…没办法,继续折腾。
三、接下来开始折腾WebView的ContextMenu
经过我不懈的查资料(百度),终于知道了WebView的ContextMenu应该怎么实现,网上有很多例子,这里也是借鉴了别人的方法,然后做了修改,其实这一块儿我不是完全懂,先整理下来,然后后续慢慢研究吧
大致就是在ActionMode启动时,将它拦截,然后传入本地ActionMode
-
创建一个自定义WebView并继承WebView,然后重写WebView里面的方法,拦截原来的ActionMode,传入本地的ActionMode。
@Override public ActionMode startActionMode(ActionMode.Callback callback) { ActionMode actionMode = super.startActionMode(callback); return resolveMode(actionMode); } @Override public ActionMode startActionMode(ActionMode.Callback callback, int type) { ActionMode actionMode = super.startActionMode(callback, type); return resolveMode(actionMode); }
-
定义本地ActionMode
private ActionMode resolveMode(ActionMode actionMode) { if(actionMode!=null){ final Menu menu = actionMode.getMenu(); menu.clear();//加上这一句之后会清除掉系统的菜单,否则会将自己的菜单添加到系统菜单前,有些手机在系统菜单后,应该每个手机的order值不一样,所以有差距 menu.add(0,11,0,"copy"); menu.add(0,22,0,"translate"); for (int i = 0; i < menu.size(); i++) { menu.getItem(i).setOnMenuItemClickListener(this); } this.mActionMode = actionMode; } return actionMode; }
-
对MenuItem设置Click事件
@Override public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()){ case 11: getSelectedData(COPY);//将要执行的操作传进去,在回调函数中根据不同的操作类型执行不同的操作 releaseActionMode(); break; case 22: getSelectedData(TRANSLATE); releaseActionMode(); break; } return false; }
-
使用js实现对WebView内容的操作
//传入点击后Mark的文本,并一起通过js返回给原生接口 private void getSelectedData(int type) { //因为本身的需求是复制和翻译,所以都需要Mark的文字,这里传入type类型,并一同返回给callback,区分操作 String js = "(function getSelectedText() {" + "var txt;" + "if (window.getSelection) {" + "txt = window.getSelection().toString();" + "} else if (window.document.getSelection) {" + "txt = window.document.getSelection().toString();" + "} else if (window.document.selection) {" + "txt = window.document.selection.createRange().text;" + "}" + "ActionModeJavaScript.callback(txt," + type + ");" + //回调java方法将js获取的结果传递过去 "})()"; evaluateJavascript("javascript:" + js, null); }
-
现在完成了自定义ActionMode的步骤,去MainActivity设置js接口,添加web等操作
public class MainActivity extends Activity { private RelativeLayout relativeLayout; private MyWebView mWebView; private ClipboardManager clipboardManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_webview_actionmode); relativeLayout = findViewById(R.id.rl_container); addWebViews(); } private void addWebViews() { mWebView = new MyWebView(getApplicationContext()); relativeLayout.addView(mWebView); WebSettings settings = mWebView.getSettings(); settings.setJavaScriptEnabled(true);//设置WebView可以与JavaScript交互 mWebView.addJavascriptInterface(new ActionModeJavaScript(),"ActionModeJavaScript");//将java对象绑定到js中 mWebView.setWebViewClient(new WebViewClient());//不使用系统browser时需要覆盖WebViewClient对象 mWebView.loadUrl("file:///android_asset/web/textContent.html");//我的一个测试页面 } @Override protected void onPause() { super.onPause(); mWebView.releaseActionMode(); } private class ActionModeJavaScript { @JavascriptInterface public void callback(String text, int type){ if (type == MyWebView.COPY){ copyToClipboard(text); } else if (type == MyWebView.TRANSLATE){ //牵扯到公司业务,此处未实现 } } } private void copyToClipboard(String string){ if (clipboardManager ==null) { clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); } ClipData clipData = ClipData.newPlainText("simople text", string); clipboardManager.setPrimaryClip(clipData); Toast.makeText(this,"您已复制:"+string, Toast.LENGTH_LONG).show(); } }
-
运行截图
按下copy后会复制Mark的文本到剪切板,手机截图比较大,就不贴在这里了。
这是源码:https://download.csdn.net/download/xixinyoung0129/10780403?utm_source=bbsseo
初入安卓的世界,学术浅陋,难免会出现错误的地方,我会尽可能让自己写的东西是正确的,所有写出来的代码都经过实际测试,但即使如此,也不能保证所有都正确,希望大家发现问题时第一时间批评指正,必定虚心学习。