【问题标题】:Use a custom contextual action bar for WebView text selection使用自定义上下文操作栏进行 WebView 文本选择
【发布时间】:2014-04-15 17:23:33
【问题描述】:

我使用this guide from Googlethis tutorial 来制作我自己的上下文操作栏。

private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {

    // Called when the action mode is created; startActionMode() was called
    @Override
    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
        // Inflate a menu resource providing context menu items
        MenuInflater inflater = mode.getMenuInflater();
        inflater.inflate(R.menu.annotation_menu, menu);
        return true;
    }

    // Called each time the action mode is shown.
    // Always called after onCreateActionMode, but
    // may be called multiple times if the mode is invalidated.
    @Override
    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
        return false; // Return false if nothing is done
    }

    // Called when the user selects a contextual menu item
    @Override
    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
        switch (item.getItemId()) {
            case R.id.custom_button:
                // do some stuff
                break;
            case R.id.custom_button2:
                // do some other stuff
                break;
            default:
                // This essentially acts as a catch statement
                // If none of the other cases are true, return false
                // because the action was not handled
                return false;
        }
        finish(); // An action was handled, so close the CAB
        return true;
    }

    // Called when the user exits the action mode
    @Override
    public void onDestroyActionMode(ActionMode mode) {
        mActionMode = null;
    }
};

此菜单旨在在用户选择文本时出现,因此它会覆盖本机复制/粘贴菜单。现在我来解决我的问题。

因为我覆盖了文本选择的函数,所以我还在WebView 中添加了LongClickListener 并实现了onLongClick(View v) 方法,以便我可以检测用户何时进行选择。

    myWebView.setOnLongClickListener(new View.OnLongClickListener() {

        @Override
        public boolean onLongClick(View v) {
            if (mActionMode != null) {
                return false;
            }

            mActionMode = startActionMode(mActionModeCallback);
            v.setSelected(true);
            return true;
        }
    });

当我长按时,我看到我的自定义菜单出现,但没有突出显示文本。
我需要有文本选择功能;没有它,我的菜单毫无意义。

如何覆盖onLongClick(View v),但保留Android 提供的文本选择?
如果无法做到这一点,我可以在其他地方调用startActionMode(mActionModeCallback),以便正常选择文本,但是我的自定义菜单也会出现?
如果这两个都不可能...帮助。

【问题讨论】:

    标签: android webview touch-event


    【解决方案1】:

    我做类似事情的方式是只覆盖 onTouchListener 并调用 GestureDetector 来检测 WebView 何时被长按并从那里做我想做的事。这是一些示例代码,可让您在不牺牲 WebView 中的文本选择的情况下捕获长按事件。希望这会有所帮助。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        WebView mWebView = (WebView) findViewById(R.id.myWebView);
        GestureDetector mGestureDetector = new GestureDetector(this, new CustomGestureListener());
        mWebView.setOnTouchListener(new OnTouchListener(){
            @Override
            public boolean onTouch(View view, MotionEvent arg1) {
    
                //Suggestion #1 - this just lets the touch to be handled by the system but allows you to detect long presses
                mGestureDetector.onTouchEvent(arg1);
                return false;
    
                //Suggestion #2 - this code will only let the touch be handled by the system if you don't detect a long press
                return mGestureDetector.onTouchEvent(arg1);
            }
        });
    }
    
    private class CustomGestureListener extends SimpleOnGestureListener {
    
        @Override
        public void onLongPress(MotionEvent e) {
            //do stuff
        }
    
    }
    

    【讨论】:

    • 你的建议没有错;这确实允许长按的附加功能。但是,它并没有完成我想要做的事情。即使我将startActionMode(mActionModeCallback) 放入onLongPress 调用中,仍会出现本机上下文菜单。我想覆盖所述菜单,同时保持文本选择。
    • 啊,这样就更清楚了。我想我可能稍微误解了你在问什么。这个问题看起来和你的很相似,还没有一个公认的解决方案stackoverflow.com/questions/17024203/…
    • 但是这个问题表明有一种方法可以覆盖复制和粘贴显示的内容stackoverflow.com/questions/15370047/…
    • 我用可能有用的东西编辑了我的答案。如果检测到长按,请尝试从 onTouch 返回 true。我以前没用过,所以我不确定它会不会像你想要的那样工作
    • 我听从了the second link you posted 的建议,现在文本选择有效。但是,我现在有一个不同的问题。 (我会为此发布一个新问题。)
    【解决方案2】:

    有一种更简单的方法!请参阅下面的更新:D


    为了完整起见,这是我解决问题的方法:

    我按照this answer 的建议进行了一些调整,以更接近地匹配被覆盖的代码:

    public class MyWebView extends WebView {
    
        private ActionMode mActionMode;
        private mActionMode.Callback mActionModeCallback;
    
        @Override
        public ActionMode startActionMode(Callback callback) {
            ViewParent parent = getParent();
            if (parent == null) {
                return null;
            }
            mActionModeCallback = new CustomActionModeCallback();
            return parent.startActionModeForChild(this, mActionModeCallback);
        }
    }
    

    本质上,这会强制显示您的自定义 CAB 而不是 Android CAB。现在您必须修改您的回调,以便文本突出显示将与 CAB 一起消失:

    public class MyWebView extends WebView {
        ...
        private class CustomActionModeCallback implements ActionMode.Callback {
            ...
            // Everything up to this point is the same as in the question
    
            // Called when the user exits the action mode
            @Override
            public void onDestroyActionMode(ActionMode mode) {
                clearFocus(); // This is the new code to remove the text highlight
                 mActionMode = null;
            }
        }
    }
    

    仅此而已。请注意,只要您将MyWebView 与覆盖的startActionMode 一起使用,就无法获得本机CAB(复制/粘贴菜单,在WebView 的情况下)。有可能实现这种行为,但这不是这段代码的工作方式。


    更新:有一个更简单的方法来做到这一点!上述解决方案效果很好,但这是另一种更简单的方法。

    此解决方案对ActionMode 的控制较少,但与上述解决方案相比,它需要的代码要少得多。

    public class MyActivity extends Activity {
    
        private ActionMode mActionMode = null;
    
        @Override
        public void onActionModeStarted(ActionMode mode) {
            if (mActionMode == null) {
                mActionMode = mode;
                Menu menu = mode.getMenu();
                // Remove the default menu items (select all, copy, paste, search)
                menu.clear();
    
                // If you want to keep any of the defaults,
                // remove the items you don't want individually:
                // menu.removeItem(android.R.id.[id_of_item_to_remove])
    
                // Inflate your own menu items
                mode.getMenuInflater().inflate(R.menu.my_custom_menu, menu);
            }
    
            super.onActionModeStarted(mode);
        }
    
        // This method is what you should set as your item's onClick
        // <item android:onClick="onContextualMenuItemClicked" />
        public void onContextualMenuItemClicked(MenuItem item) {
            switch (item.getItemId()) {
                case R.id.example_item_1:
                    // do some stuff
                    break;
                case R.id.example_item_2:
                    // do some different stuff
                    break;
                default:
                    // ...
                    break;
            }
    
            // This will likely always be true, but check it anyway, just in case
            if (mActionMode != null) {
                mActionMode.finish();
            }
        }
    
        @Override
        public void onActionModeFinished(ActionMode mode) {
            mActionMode = null;
            super.onActionModeFinished(mode);
        }
    }
    

    这里有一个示例菜单可以帮助您入门:

    <!-- my_custom_menu.xml -->
    <?xml version="1.0" encoding="utf-8"?>
    
    <menu xmlns:android="http://schemas.android.com/apk/res/android">
    
        <item
            android:id="@+id/example_item_1"
            android:icon="@drawable/ic_menu_example_1"
            android:showAsAction="always"
            android:onClick="onContextualMenuItemClicked"
            android:title="@string/example_1">
        </item>
    
        <item
            android:id="@+id/example_item_2"
            android:icon="@drawable/ic_menu_example_2"
            android:showAsAction="ifRoom"
            android:onClick="onContextualMenuItemClicked"
            android:title="@string/example_2">
        </item>
    
    </menu>
    

    就是这样!你完成了!现在您的自定义菜单将显示,您不必担心选择,并且您几乎不必关心 ActionMode 生命周期。

    这对于占据其整个父 ActivityWebView 几乎完美无缺。我不确定如果您的Activity 中同时有多个Views,它将如何运作。在这种情况下可能需要进行一些调整。

    【讨论】:

    • 我做了 domethihg 喜欢你的解决方案,但我的 onDestroyActionMode 没有被调用。遇到过这样的问题吗?
    • 你用的是什么版本的安卓?在 4.4 (KitKat) 之前的版本中,是的,我遇到了同样的问题。这是由于一个名为WebViewClassic 的隐藏内部类。在这种情况下,你必须做一些诡计。这很复杂,所以我会在发表大量评论之前等待您的回复。
    • 谢谢。我的项目是4.0版本的。
    • 好收获。我已经进行了相应的编辑。将来,您可以通过简单地编辑自己而不是评论来更加积极主动。编辑问题中的重要拼写错误,例如这个问题,只是成为一名优秀的 SO 公民的一部分。 :)
    • 这将不起作用,如果您选择更多文本(即滚动选择文本光标)然后所有默认菜单再次出现超过 android 版本 M.
    猜你喜欢
    • 2012-05-25
    • 1970-01-01
    • 2012-02-09
    • 2013-02-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-16
    相关资源
    最近更新 更多