如何在android中覆盖网页视图文本选择菜单

Posted

技术标签:

【中文标题】如何在android中覆盖网页视图文本选择菜单【英文标题】:How to override web view text selection menu in android 【发布时间】:2018-02-20 22:00:22 【问题描述】:

基本的 android 网络文本选择菜单如下图所示。它具有复制、共享、全选、网络搜索等选项。

我想覆盖这个菜单,并将它们作为我自己的菜单列表,如“标记颜色”、“标记为小鬼”等。我查看了关于堆栈溢出上下文菜单的大多数可用问题。大部分问题与上下文菜单有关,但没有按预期给出结果。我想要下图所示的菜单

当我执行选择时,android 监视器会显示一些视图创建表单 viewRoot,例如

D/ViewRootImpl: #1 mView = android.widget.PopupWindow$PopupDecorView648898f V.E...... ......I. 0,0-0,0
D/ViewRootImpl: #1 mView = android.widget.PopupWindow$PopupDecorViewa66541c V.E...... ......I. 0,0-0,0
D/ViewRootImpl: MSG_RESIZED_REPORT: ci=Rect(0, 0 - 0, 0) vi=Rect(0, 0 - 0, 0) or=1
D/ViewRootImpl: MSG_RESIZED_REPORT: ci=Rect(0, 0 - 0, 0) vi=Rect(0, 0 - 0, 0) or=1

如何实现这样的实现?

我也经历了https://github.com/naoak/WebViewMarker,但没有得到正确的结果。

我已经做了什么?

我扩展了 android 的 WebView,我想支持最低 SDK 19。当我执行长按时,我得到了长按事件,但我无法获得这样的菜单创建 api 调用。

【问题讨论】:

***.com/a/22563790/5672138 @AshishJohn 我已经完成了这个答案,但它没有像第二张图片那样显示菜单。 @YvetteColomb 实际上这个问题不需要任何示例,因为它具有正常的 android 网络视图性质。 知道了。但是我已经添加了我尝试过的github.com/naoak/WebViewMarker 和***.com/questions/17024203/…。所有菜单覆盖问题都显示不同的菜单结构。我会提供一些额外的信息。 看看这个:***.com/questions/22336903/… 【参考方案1】:

你需要的是动作模式,在活动中:

    @Override
    public void onActionModeStarted(ActionMode mode) 
        Menu menu = mode.getMenu();

        // you can remove original menu: copy, cut, select all, share ... or not
        menu.clear();

        // here i will get text selection by user
        menu.add(R.string.action_menu_preview_card)
                .setEnabled(true)
                .setVisible(true)
                .setOnMenuItemClickListener(item -> 
                    if (mWebview != null) 
                        mWebview.evaluatejavascript("window.getSelection().toString()", value -> 
                            value = StringUtil.trimToNull(value);
                            if (value != null) 
                                // do something about user select
                            
                        );
                    
                    // Post a delayed runnable to avoid a race condition
                    // between evaluateScript() result and mode.finish()
                    new Handler().postDelayed(new Runnable() 
                        @Override
                        public void run() 
                            mode.finish();
                        
                    , 200);
                    return true;
                );
        super.onActionModeStarted(mode);
    

我在android 21以上测试过,这个可以处理动作模式菜单点击,而mode.getMenuInflater().inflate(...)做不到。

【讨论】:

能够显示菜单,但是 window.getSelection().toString() 正在返回空字符串 它工作正常,除了 mode.finish() 和 evaluateJavascript() 之间存在竞争条件。发布一个延迟的 Runnable 并在那里调用 mode.finish() 。我将尝试编辑代码示例以反映这一点。【参考方案2】:

此方案不依赖于Activity的Action模式,适用于所有安卓平台

我试图给出答案,但它超出了字符限制,所以我放了一些代码部分

参考链接 1 用于 Web 视图上的选择

https://github.com/btate/BTAndroidWebViewSelection

制作网页视图标记的参考链接2

https://github.com/liufsd/WebViewMarker

以上两个链接确实发挥了重要作用,并由一些出色的开发人员开发。首先需要对参考链接中的TextSelectionSupport类进行一些研究1.我在TextSelectionSupport类中自定义了两行代码,以在此处获取Selection Listener中的选择矩形。

从这里克隆示例项目 https://github.com/ab-cse-2014/WebViewSelection.git

参见CustomWebView的实现和TextSelectionSupport类的使用。

这是我在项目中的网页视图类

 import android.content.Context;
 import android.graphics.Rect;
 import android.os.Build;
 import android.support.annotation.RequiresApi;
 import android.support.v7.app.AppCompatActivity;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.webkit.WebView;
 import android.widget.PopupWindow;
 import android.widget.Toast;

 import com.cse.webviewtextselection.R;
 import com.cse.webviewtextselection.webviewmaker.TextSelectionSupport;

 public class CustomWebView extends WebView 

private final String TAG = this.getClass().getSimpleName();

private Context mContext;

private TextSelectionSupport mTextSelectionSupport;

private PopupWindow mPopupWindow;

private int currentTop;

public CustomWebView(Context context) 
    super(context);
    mContext = context;
    initSetUp();
    preparePopupWindow();


public CustomWebView(Context context, AttributeSet attrs) 
    super(context, attrs);
    mContext = context;
    initSetUp();
    preparePopupWindow();


public CustomWebView(Context context, AttributeSet attrs, int defStyleAttr) 
    super(context, attrs, defStyleAttr);
    mContext = context;
    initSetUp();
    preparePopupWindow();


@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public CustomWebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) 
    super(context, attrs, defStyleAttr, defStyleRes);
    mContext = context;
    initSetUp();
    preparePopupWindow();


private void initSetUp() 

    mTextSelectionSupport = TextSelectionSupport.support((AppCompatActivity) mContext, this);
    mTextSelectionSupport.setSelectionListener(new TextSelectionSupport.SelectionListener() 
        @Override
        public void startSelection() 

        

        @Override
        public void selectionChanged(String text, Rect rect) 
            Toast.makeText(mContext, text, Toast.LENGTH_SHORT).show();
            showPopAtLocation(mPopupWindow, rect.left, rect.top);
        

        @Override
        public void endSelection() 
            if (mPopupWindow != null) 
                mPopupWindow.dismiss();
            
        
    );


private void preparePopupWindow() 

    LayoutInflater layoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    View customPopupView =  layoutInflater.inflate(R.layout.custom_popup_layout, null);
    mPopupWindow = new PopupWindow(customPopupView, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, true);
    mPopupWindow.setAnimationStyle(android.R.style.Animation_Dialog);



private void showPopAtLocation(PopupWindow mPopupWindow, int x, int y) 

    if (mPopupWindow != null) 

        if (currentTop != 0 || currentTop > ((AppCompatActivity)mContext).getWindow().getDecorView().getHeight()) 

                if (y > currentTop) 

                    y -= currentTop;

                

        

        Log.d("Current Top : ", String.valueOf(currentTop));
        Log.d("Y : ", String.valueOf(y));

        //mPopupWindow.showAtLocation(((AppCompatActivity)mContext).findViewById(R.id.parentRelativeLayout), Gravity.NO_GRAVITY, x, y);
        mPopupWindow.showAtLocation(((AppCompatActivity)mContext).getWindow().getDecorView(), Gravity.NO_GRAVITY, x, y);


    



@Override
protected void onScrollChanged(int newLeft, int newTop, int oldLeft, int oldTop) 

    currentTop = newTop;


    super.onScrollChanged(newLeft, newTop, oldLeft, oldTop);

 

自定义弹出菜单 XML 类似 androids 智能文本选择(custom_popup_layout.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/myCustomMenuLinearLayout"
android:layout_
android:layout_
android:orientation="horizontal"
android:background="@android:color/transparent">

<LinearLayout
    android:layout_
    android:layout_
    android:orientation="horizontal"
    android:background="@android:color/white"
    android:elevation="5dp"
    android:layout_margin="12dp">

    <TextView
        android:id="@+id/menuOne"
        android:layout_
        android:layout_
        android:text="Mark With Color"
        android:textColor="@android:color/black"
        android:padding="10dp"
        android:maxLines="1"/>

    <TextView
        android:id="@+id/menuTwo"
        android:layout_
        android:layout_
        android:text="Mark As Important"
        android:textColor="@android:color/black"
        android:padding="10dp"
        android:maxLines="1"/>

    <TextView
        android:id="@+id/menuThree"
        android:layout_
        android:layout_
        android:text="Show More"
        android:textColor="@android:color/black"
        android:padding="10dp"
        android:maxLines="1"/>

</LinearLayout>



</LinearLayout>

输出屏幕截图

【讨论】:

当我们使用“loadDataWithBaseURL”时此解决方案不起作用 我没有在上述答案的任何地方使用“loadDataWithBaseURL”。你想达到什么目标? 我使用了上面的代码,当我使用 webView.loadUrl("file:///android_asset/XXX.html");当我使用 webview.loadDataWithBaseUrl(baseUrl, data,mimeType,encoding,historyUrl) 时,控制台出现错误“Uncaught ReferenceError: android is not defined” @YerramNaveen - 在 webview.loadDataWithBaseUrl(baseUrl, data,mimeType,encoding,historyUrl) 中用作参数的证明值。 我解决了我们需要添加的问题 在 html 头部.非常感谢【参考方案3】:

我认为here可以帮助你。

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

我根据这个答案遵循了建议,并进行了一些调整以更接近地匹配被覆盖的代码:

公共类 MyWebView 扩展 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 一起消失:

公共类 MyWebView 扩展 WebView ... 私有类 CustomActionModeCallback 实现 ActionMode.Callback ... // 到目前为止的一切都和问题中的一样

    // 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 的控制较少,但所需代码远少于上述解决方案。

公共类 MyActivity 扩展 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);

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

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

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

对于占据其整个父 Activity 的 WebView,这几乎可以完美地工作。如果您的活动中一次有多个视图,我不确定它会如何工作。在这种情况下可能需要进行一些调整。

【讨论】:

需要更新。不适用于编译 SDK 26 及更高版本。【参考方案4】:

您需要覆盖活动的操作菜单

您可以阅读更多信息:https://developer.android.com/guide/topics/ui/menus.html

如何覆盖:

@Override
public void onActionModeStarted(android.view.ActionMode mode) 
    mode.getMenu().clear();
    Menu menus = mode.getMenu();
    mode.getMenuInflater().inflate(R.menu.highlight,menus);
    super.onActionModeStarted(mode);

高亮

    <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/impclick"
        android:title="Mark As Important"
      />
    <item android:id="@+id/colorclick"
        android:title="Mark with color" />
</menu>

【讨论】:

能否请您告诉我您需要在哪个 android 版本上执行此操作,以及其他活动或紧凑活动 你能在这里分享活动捕捉的代码吗,因为它对我来说很好用。可能您在活动级别上做错了什么 需要更新您的答案...不适用于 compile SDK 26 及更高版本。

以上是关于如何在android中覆盖网页视图文本选择菜单的主要内容,如果未能解决你的问题,请参考以下文章

在Android的WebView中选择文本时如何覆盖上下文操作栏?

如何在双击中隐藏文本选择工具菜单?

如何在 Android 的文本视图中添加网页链接 url?

Android:禁用网页视图中的文本选择

如何在 Android 的 web 视图中禁用文本选择?

如何在 Android 中更改菜单项的文本颜色?