实现WebView的上下文菜单遇到的坑

上下文菜单遇到的坑

需求是:长按文字,弹出context menu,有复制、翻译等操作,等于是自定义系统的这个contextMenu,这算是初入android要实现的第一个功能,接到这个任务时,给我方向是学习一下ContextMenu,然后实现复制功能,翻译功能待续。

一、 使用contextMenu遇到的问题

contextMenu的使用方法大致如下:

  1. 先使用registerForContextMenu(mView);方法将要弹出上下文菜单的view注册进ContextMenu。

    registerForContextMenu(textView);
    
  2. 设置TextView可以被选中

    textView.setTextIsSelectable(true);
    
  3. 重写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);
    }
    
  4. 重写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);
    }
    

运行结果如下:
image

咋一看,觉得这么简单,就这么实现了,再写个复制功能不就完美了么?于是继续写复制功能,然后才发现有问题了,我到底要复制啥呢?这个ContextMenu和系统的好像不太一样,没有mark文字,也不能自由mark,这个context menu是针对整个view显示的,于是又查资料(其实就是百度),查自定义contextMenu如何选中文字。然后查了一整天无果,但是找到了一篇介绍ContextMenu底层是如何实现的,这篇博客给了我思路

传送门:https://blog.csdn.net/huawuque183/article/details/78559605

思路就是使用setCustomSelectionActionModeCallback();对view设置回调,然后在回调接口里实现自己的菜单以及功能,下来开始了对ActionMode的折腾

二、 ActionMode遇到的问题

ActionMode的使用也分为三步,分别是:

  1. 定义一个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);
        }
    };
    
  2. 重写CallBack里面的方法

     //代码参见第一步
    
  3. 给TextView或者EditText添加回调,如果是TextView则还要设置可被选中

     textView = (TextView) findViewById(R.id.text);
     textView.setTextIsSelectable(true);
     textView.setCustomSelectionActionModeCallback(actionModeCallBac2);    
    
  4. 复制到剪切板

    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会弹出对话框

当按下COPY时会吐司出选中的文本,下面是截图
image

当执行到这里,我以为终于做好了,然而太天真了,这个办法只能针对TextView和EditText,而也是这个时候我才知道我要实现的这个功能是针对WebView的。沟通是多么重要,当初可没给我说是WebView啊,给我说让我在TextView上实现,如果不行就试试WebView,哎…没办法,继续折腾。

三、接下来开始折腾WebView的ContextMenu

经过我不懈的查资料(百度),终于知道了WebView的ContextMenu应该怎么实现,网上有很多例子,这里也是借鉴了别人的方法,然后做了修改,其实这一块儿我不是完全懂,先整理下来,然后后续慢慢研究吧

大致就是在ActionMode启动时,将它拦截,然后传入本地ActionMode

  1. 创建一个自定义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);
    }
    
  2. 定义本地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;
    }
    
  3. 对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;
     }
    
  4. 使用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);
    }
    
  5. 现在完成了自定义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();
        }
    
    }
    
  6. 运行截图
    image

按下copy后会复制Mark的文本到剪切板,手机截图比较大,就不贴在这里了。
这是源码:https://download.csdn.net/download/xixinyoung0129/10780403?utm_source=bbsseo

初入安卓的世界,学术浅陋,难免会出现错误的地方,我会尽可能让自己写的东西是正确的,所有写出来的代码都经过实际测试,但即使如此,也不能保证所有都正确,希望大家发现问题时第一时间批评指正,必定虚心学习。

猜你喜欢

转载自blog.csdn.net/xixinyoung0129/article/details/83996264