Android WebView 使用JS扩选的问题:我的需求是当用户选择webview的文本内容的时候,首次的长按的时候帮助用户选择整段,后续用户扩选的时候,自动帮用户选择整句。
我的思路是当原生这边创建系统菜单的时候(也就是用户开始选择之后),调用js,通过js的Selection API去做一个扩选。比如使用了下面的代码:
function modify() {
function forwardWord(selection) {
var focusNode = selection.focusNode;
var range = selection.getRangeAt(0);
range.setEnd(range.endContainer, focusNode.textContent.length);
selection.addRange(range);
}
let selection = window.getSelection();
selection.modify("extend", "backward", "paragraphboundary");
forwardWord(selection);
console.log("selection.toString()", selection.toString());
return selection.toString();
}
调用了该Js之后,发现是能扩选成功的,但是系统的菜单就不再出现了,光标也不展示了。
其中,系统菜单是在长按的时候出现的那些例如:复制,全选,分享之类的系统自带的。
我是通过重写WebView的startActionMode(ActionMode.Callback callback)
和startActionMode(ActionMode.Callback callback, int type)
方法,创建自己的Callback代理,例如:
fun proxyWebMenuCallback(callback: ActionMode.Callback, noticeH5: () -> Unit): ActionMode.Callback {
return if (SdkVersion.maxThanM()) {
WebViewMenuCallback2(callback, noticeH5)
} else {
WebViewMenuCallback(callback, noticeH5)
}
}
@SuppressLint("NewApi")
class WebViewMenuCallback2(
private val callback: ActionMode.Callback,
private val noticeH5: () -> Unit
) : ActionMode.Callback2() {
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
return callback.onCreateActionMode(mode, menu).also {
LogUtil.d("WebViewMenuCallback2", "onCreateActionMode:$it")
}
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
noticeH5()
return callback.onPrepareActionMode(mode, menu).also {
LogUtil.d("WebViewMenuCallback2", "onPrepareActionMode:$it")
}
}
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
return callback.onActionItemClicked(mode, item).also {
LogUtil.d("WebViewMenuCallback2", "onActionItemClicked:$it")
}
}
override fun onDestroyActionMode(mode: ActionMode?) {
callback.onDestroyActionMode(mode).also {
LogUtil.d("WebViewMenuCallback2", "onDestroyActionMode:$it")
}
}
override fun onGetContentRect(mode: ActionMode?, view: View?, outRect: Rect?) {
if (callback is ActionMode.Callback2) {
callback.onGetContentRect(mode, view, outRect)
} else {
super.onGetContentRect(mode, view, outRect)
}
}
}
class WebViewMenuCallback(
private val callback: ActionMode.Callback,
private val noticeH5: () -> Unit
) : ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
return callback.onCreateActionMode(mode, menu)
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
noticeH5()
return callback.onPrepareActionMode(mode, menu)
}
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
return callback.onActionItemClicked(mode, item)
}
override fun onDestroyActionMode(mode: ActionMode?) {
callback.onDestroyActionMode(mode)
}
}
我的想法是在创建菜单的监听中去执行JS代码进行扩选,但是无论我把noticeH5
回调放在onPrepareActionMode
还是onCreateActionMode
中调用,还是noticeH5
的实现是会通过postDelay
去调用JS,结果都是扩选成功,但是菜单却被隐藏了,光标不见了。
然后我尝试在执行JS完成之后,例如:
evaluateJavascript("javascript:" + js, value -> {
postDelay(1000){
//performLongClick();
//showContextMenu();
}
}
);
都不起效,无论是否一起调用。
后面我尝试在自定义菜单也不可以。
还尝试了自定义菜单,然后在执行完成JS之后,调用startActionMode
方法,发现是会出现菜单的,但是菜单的位置出现不正确,而且光标也不见了,同时该方法还只能去设置执行一次,否则会陷入死循环,因为调用该方法之后,Callback里面的方法又开始执行了。
最后尝试了全部逻辑交由JS实现,结果就是js的菜单位置,扩选啥的都能直接做到,但是却不能自由选择了,光标没有了。
我的想法是这是否是Chrome的Bug,即在JS扩展选区的时候不能同步到原生端?因为扩选选区之后是会回调Callback的onGetContentRect
方法的,我还在给官方提了bug,但是暂时也没有回应。When JavaScript expand the selection,WebView ContextMenu aoto dimiss
请问应该如何实现JS扩展选区,扩选之后菜单能保持在正确的位置。
PS: 小米浏览器有一个扩选的功能,而且扩选之后是可以正常展示菜单和光标的。或者有知道小米的是如何实现的吗?
最终是修改了方案实现,用户可以自由复制,端上通过自定义Callback实现全自定义菜单。通过开源方案选择完整句子get-selection-more,对开源的方案进行了优化,增加了换行补充。