Android 使用自定义上下文操作栏进行 WebView 文本选择

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22336903/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 05:50:59  来源:igfitidea点击:

Use a custom contextual action bar for WebView text selection

androidwebviewtouch-event

提问by Sean Beach

I have used this guide from Googleand this tutorialto produce my own contextual action bar.

我已经使用Google 的这份指南本教程来制作我自己的上下文操作栏。

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;
    }
};

This menu is designed to appear when the user selects text, so it overrides the native copy/paste menu. Now I get to my issue.

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

Because I am overriding functions for text selection, I also added a LongClickListenerto a WebViewand implemented the onLongClick(View v)method so I can detect when users make the selection.

因为我要覆盖文本选择的函数,所以我还在 aLongClickListener中添加了aWebView并实现了该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;
        }
    });

When I long click, I see my custom menu appear, but no text is highlighted.
I need to have the text selection functionality; without it, my menu is pointless.

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

How do I override onLongClick(View v), but maintain the text selection provided by Android?
If that is not possible, can I make the call to startActionMode(mActionModeCallback)somewhere else so that text will be selected as normal, but my custom menu will also appear?
If neither of those are possible... help.

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

回答by Sean Beach

THERE IS AN EASIER WAY! See update below :D

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



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

I followed the suggestion according to this answer, with a little more tweaking to more closely match the overridden code:

我根据这个答案遵循了建议,并进行了更多调整以更紧密地匹配覆盖的代码:

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);
    }
}

Essentially, this forces your customized CAB to appear instead of the Android CAB. Now you have to modify your callback so that the text highlight will go away along with the CAB:

本质上,这会强制显示您自定义的 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;
        }
    }
}

That's all there is to it. Be aware that as long as you are using MyWebViewwith the overridden startActionModethere is NO WAY to get the native CAB (the copy/paste menu, in the case of a WebView). It may be possible to implement that sort of behavior, but that is not the way this code works.

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



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

This solution provides less control over the ActionMode, but it requires far less code than the above solution.

此解决方案对 的控制较少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);
    }
}

Here is an example Menu to get you started:

这是一个示例菜单,可帮助您入门:

<!-- 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>

That's it! You're done! Now your custom menu will show up, you don't have to worry about the selection, and you barely have to concern yourself with the ActionModelifecycle.

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

This works nearly flawlessly with a WebViewthat occupies its entire parent Activity. I am not sure how well it will work if there are multiple Views within your Activityat one time. It will likely require some tweaking in that case.

WebView对于占据其整个父级的a 几乎完美无缺Activity。我不确定如果ViewActivity一次有多个s它将如何工作。在这种情况下,它可能需要进行一些调整。

回答by anthonycr

The way I did something similar was to only override the onTouchListener and to invoke a GestureDetector to detect when the WebView was long-pressed and do what I wanted from there. Here's some sample code that allows you to catch long-press events without sacrificing text-selection in the WebView. Hopefully this helps.

我做类似事情的方式是只覆盖 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
    }

}