如何覆盖 android 中的 Web 视图文本选择菜单
How to override web view text selection menu in android
基本的android网页文字selection菜单如下图所示。它具有复制、共享、select 全部、网络搜索等选项。
我想超越这个菜单,并希望它们作为我自己的菜单列表,如 "mark colour"、"mark as imp" 等。我查看了有关堆栈溢出上下文菜单的大部分可用问题。大多数问题与上下文菜单有关,但没有给出预期的结果。我想要如下图的菜单
当我执行 selection android 监视器显示一些视图创建表单 viewRoot 像
D/ViewRootImpl: #1 mView = android.widget.PopupWindow$PopupDecorView{648898f V.E...... ......I. 0,0-0,0}
D/ViewRootImpl: #1 mView = android.widget.PopupWindow$PopupDecorView{a66541c 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 调用.
您需要覆盖 activity
的操作菜单
您可以阅读更多信息: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>
我认为here可以帮到你。
为了完整起见,以下是我解决问题的方法:
我按照这个答案的建议进行了一些调整,以更接近于覆盖的代码:
public class 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 一起消失:
public class MyWebView 扩展 WebView {
...
私有 class 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(copy/paste 菜单,在 WebView 的情况下)。实现这种行为是可能的,但这不是这段代码的工作方式。
更新:有一种更简单的方法可以做到这一点!上述解决方案效果很好,但这里有一个更简单的替代方法。
此解决方案对 ActionMode 的控制较少,但所需代码远少于上述解决方案。
public class 我的Activity 扩展 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,这几乎完美无缺。如果您的 Activity 中同时有多个视图,我不确定它的效果如何。在这种情况下可能需要进行一些调整。
此解决方案不依赖于 Activity 的操作模式,适用于所有 android 平台
I tried to give answer but it exceeds the character limits so I am putting some code part
参考Link 1 Web 视图选择
参考Link2 用于制作网页视图标记
以上两个链接确实起着重要作用,并且由一些很棒的开发人员开发。首先需要对 TextSelectionSupport 进行一些研究 Class 来自参考 Link 1. 我在 TextSelectionSupport class 中自定义了两行代码以在此处的选择侦听器中获取选择矩形。
Clone the Sample Project from here
https://github.com/ab-cse-2014/WebViewSelection.git
See The implementation of CustomWebView and Use Of TextSelectionSupport class.
This is my web view class in project
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);
}
}
Custom Popup Menu XML like androids smart text selection(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_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@android:color/transparent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@android:color/white"
android:elevation="5dp"
android:layout_margin="12dp">
<TextView
android:id="@+id/menuOne"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Mark With Color"
android:textColor="@android:color/black"
android:padding="10dp"
android:maxLines="1"/>
<TextView
android:id="@+id/menuTwo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Mark As Important"
android:textColor="@android:color/black"
android:padding="10dp"
android:maxLines="1"/>
<TextView
android:id="@+id/menuThree"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Show More"
android:textColor="@android:color/black"
android:padding="10dp"
android:maxLines="1"/>
</LinearLayout>
</LinearLayout>
输出屏幕截图
你需要的是动作模式,在activity:
@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);
}
我在上面测试过android21,这个可以处理action mode menu click,而mode.getMenuInflater().inflate(...)做不到
基本的android网页文字selection菜单如下图所示。它具有复制、共享、select 全部、网络搜索等选项。
我想超越这个菜单,并希望它们作为我自己的菜单列表,如 "mark colour"、"mark as imp" 等。我查看了有关堆栈溢出上下文菜单的大部分可用问题。大多数问题与上下文菜单有关,但没有给出预期的结果。我想要如下图的菜单
当我执行 selection android 监视器显示一些视图创建表单 viewRoot 像
D/ViewRootImpl: #1 mView = android.widget.PopupWindow$PopupDecorView{648898f V.E...... ......I. 0,0-0,0}
D/ViewRootImpl: #1 mView = android.widget.PopupWindow$PopupDecorView{a66541c 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 调用.
您需要覆盖 activity
的操作菜单您可以阅读更多信息: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>
我认为here可以帮到你。
为了完整起见,以下是我解决问题的方法:
我按照这个答案的建议进行了一些调整,以更接近于覆盖的代码:
public class 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 一起消失:
public class MyWebView 扩展 WebView { ... 私有 class 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(copy/paste 菜单,在 WebView 的情况下)。实现这种行为是可能的,但这不是这段代码的工作方式。 更新:有一种更简单的方法可以做到这一点!上述解决方案效果很好,但这里有一个更简单的替代方法。
此解决方案对 ActionMode 的控制较少,但所需代码远少于上述解决方案。
public class 我的Activity 扩展 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,这几乎完美无缺。如果您的 Activity 中同时有多个视图,我不确定它的效果如何。在这种情况下可能需要进行一些调整。
此解决方案不依赖于 Activity 的操作模式,适用于所有 android 平台
I tried to give answer but it exceeds the character limits so I am putting some code part
参考Link 1 Web 视图选择
参考Link2 用于制作网页视图标记
以上两个链接确实起着重要作用,并且由一些很棒的开发人员开发。首先需要对 TextSelectionSupport 进行一些研究 Class 来自参考 Link 1. 我在 TextSelectionSupport class 中自定义了两行代码以在此处的选择侦听器中获取选择矩形。
Clone the Sample Project from here https://github.com/ab-cse-2014/WebViewSelection.git
See The implementation of CustomWebView and Use Of TextSelectionSupport class.
This is my web view class in project
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);
}
}
Custom Popup Menu XML like androids smart text selection(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_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@android:color/transparent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@android:color/white"
android:elevation="5dp"
android:layout_margin="12dp">
<TextView
android:id="@+id/menuOne"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Mark With Color"
android:textColor="@android:color/black"
android:padding="10dp"
android:maxLines="1"/>
<TextView
android:id="@+id/menuTwo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Mark As Important"
android:textColor="@android:color/black"
android:padding="10dp"
android:maxLines="1"/>
<TextView
android:id="@+id/menuThree"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Show More"
android:textColor="@android:color/black"
android:padding="10dp"
android:maxLines="1"/>
</LinearLayout>
</LinearLayout>
输出屏幕截图
你需要的是动作模式,在activity:
@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);
}
我在上面测试过android21,这个可以处理action mode menu click,而mode.getMenuInflater().inflate(...)做不到