Webview 文本选择未清除 [英] Webview text selection not clearing
问题描述
我已经为 WebView
中的自定义文本选择功能实现了一个 ActionMode.Callback
.我遇到的问题是选择和操作模式状态不匹配.
当我长按时,一切都很好.
当我与其中一个按钮或 WebView
(不包括实际选择)交互时,ActionMode
应该被销毁,并且选择应该消失.>
在 Android 4.4 KitKat 中,这正是发生的事情.
<小时>然而,这不是在 4.1.1 - 4.3 中发生的事情,Jelly Bean.当我点击其中一个按钮时,选择不会被删除.
当我在选择之外点击时,情况正好相反.选择被移除,但上下文操作栏保留在屏幕上.
<小时>这是我的 CustomWebView
public class CustomWebView extends WebView {私有 ActionMode.Callback mActionModeCallback;@覆盖public ActionMode startActionMode(回调回调){ViewParent parent = getParent();如果(父 == 空){返回空;}mActionModeCallback = new CustomActionModeCallback();return parent.startActionModeForChild(this, mActionModeCallback);}私有类 CustomActionModeCallback 实现 ActionMode.Callback {//创建动作模式时调用;startActionMode() 被调用@覆盖public boolean onCreateActionMode(ActionMode mode, Menu menu) {//扩充一个提供上下文菜单项的菜单资源MenuInflater inflater = mode.getMenuInflater();inflater.inflate(R.menu.contextual_menu, menu);返回真;}//每次显示动作模式时调用.//总是在 onCreateActionMode 之后调用,但是//如果模式无效,可能会被多次调用.@覆盖public boolean onPrepareActionMode(ActionMode mode, Menu menu) {//当车把移动时调用此方法.loadJavascript("javascript:getSelectedTextInfo()");返回假;//如果什么都不做就返回false}//当用户选择上下文菜单项时调用@覆盖public boolean onActionItemClicked(ActionMode mode, MenuItem item) {开关(item.getItemId(){案例 R.id.button_1://做东西休息;...默认:休息;}模式.完成();//选择动作,关闭 CAB返回真;}//当用户退出动作模式时调用@覆盖public void onDestroyActionMode(ActionMode mode) {//TODO 这在 Jelly Bean (API 16 - 18; 4.1.1 - 4.3) 中不起作用.清除焦点();//移除选择高亮和句柄.}}}
正如上面的评论所示,我相信问题出在 clearFocus()
方法上.当我删除该方法时,按下按钮会在 4.4 中留下选择,就像 Jelly Bean 中的行为一样.clearFocus()
给出了 4.4 中的预期行为,但不会转移到早期的 API.(请注意,clearFocus()
对 KitKat 来说并不陌生;它从 API 1 开始就在 Android 中.)
如何解决这个问题?
经过无数次的尝试,终于搞定了!
需要注意的重要一点是,Android 4.4 (KitKat) 之前的 WebView
与您的典型浏览器不同.有一些隐藏的类开始发挥作用,开始把事情搞砸.有 WebViewCore
完成所有繁重的工作并实际产生结果,还有 WebViewClassic
,这是这个问题的罪魁祸首.
解决方案是半黑客,因为您实际上不需要做任何事情来操作底层类,但您必须捕捉问题场景.
WebViewClassic
负责拦截长按并处理它们以进行文本选择,包括选择突出显示和选择手柄的动画,以及启动 ActionMode
填充上下文操作栏 (CAB).不幸的是,由于我们想用我们自己的覆盖 ActionMode
,文本选择和 CAB 变得不同步,因为它们彼此不相关.要解决此问题,请跟踪您自己的自定义ActionMode.Callback
,以及与选择动画关联的ActionMode.Callback
.然后,当您的 ActionMode.Callback
被销毁时,也调用选择的 finish()
方法来销毁该 ActionMode.Callback
.>
好了,聊够了;这是代码.
public class CustomWebView extends WebView {私有动作模式 mActionMode;私有 ActionMode.Callback mActionModeCallback;//添加这个类变量私有 ActionMode.Callback mSelectActionModeCallback;@覆盖public ActionMode startActionMode(回调回调){/* 当运行 Ice Cream Sandwich (4.0) 或 Jelly Bean (4.1 - 4.3) 时,有* 是一个名为WebViewClassic"的隐藏类,用于绘制选择.* 为了清除选择,保存经典的回调* 以便稍后销毁.*///检查类名,因为 WebViewClassic.SelectActionModeCallback//不是公共 API.字符串名称 = callback.getClass().toString();if (name.contains("SelectActionModeCallback")) {mSelectActionModeCallback = 回调;}mActionModeCallback = new CustomActionModeCallback();//我们实际上还没有做任何事情.发送我们的自定义回调//到超类,所以它会显示在屏幕上.返回 super.startActionModeForChild(this, mActionModeCallback);}私有类 CustomActionModeCallback 实现 ActionMode.Callback {//创建动作模式时调用;startActionMode() 被调用@覆盖public boolean onCreateActionMode(ActionMode mode, Menu menu) {//这对第 2 部分很重要.mActionMode = 模式;//扩充一个提供上下文菜单项的菜单资源MenuInflater inflater = mode.getMenuInflater();inflater.inflate(R.menu.contextual_menu, menu);返回真;}//每次显示动作模式时调用.//总是在 onCreateActionMode 之后调用,但是//如果模式无效,可能会被多次调用.@覆盖public boolean onPrepareActionMode(ActionMode mode, Menu menu) {//当车把移动时调用此方法.loadJavascript("javascript:getSelectedTextInfo()");返回假;//如果什么都不做就返回false}//当用户选择上下文菜单项时调用@覆盖public boolean onActionItemClicked(ActionMode mode, MenuItem item) {开关(item.getItemId(){案例 R.id.button_1://做东西休息;...默认:休息;}模式.完成();//选择动作,关闭 CAB返回真;}//当用户退出动作模式时调用@覆盖public void onDestroyActionMode(ActionMode mode) {清除焦点();//移除选择高亮和句柄.//半黑客以清除选择//当运行早于 KitKat 的 Android 时.如果(mSelectActionModeCallback != null){mSelectActionModeCallback.onDestroyActionMode(mode);}//与第 2 部分相关.mActionMode = null;}}}
<小时>信不信由你,我们只完成了一半.上面的代码负责在 CAB 关闭时删除选择.要在触摸事件上关闭 CAB,我们必须做更多的工作.这一次就简单多了.我使用 GestureDetector
并监听单击事件.当我得到那个事件时,我在 mActionMode
上调用 finish()
来关闭 CAB:
public class CustomWebView extends WebView {私有动作模式 mActionMode;私有 ActionMode.Callback mActionModeCallback;私有 ActionMode.Callback mSelectActionModeCallback;//以上代码段...私有类 CustomGestureListener 扩展 GestureDetector.SimpleOnGestureListener {@覆盖公共布尔 onSingleTapUp(MotionEvent e) {如果(mActionMode!= null){mActionMode.finish();返回真;}返回假;}}@覆盖公共布尔 onTouchEvent(MotionEvent 事件){//将事件发送到我们的手势检测器//如果实现了,就会有返回值this.mDetector.onTouchEvent(事件);//如果检测到的手势未实现,则将其发送到超类返回 super.onTouchEvent(event);}}
<小时>应该这样做!我们做到了!
您不会在其他任何地方找到 WebViewClassic
材料;这就是为什么我提供了关于正在发生的事情的如此多的细节.调试器花了好几个小时才弄清楚发生了什么.幸运的是,GestureDetector
类有很好的文档记录,并且包含多个教程.我从 Android 开发者网站获得了我的信息.我希望这能帮助那些和我一样在这个问题上挣扎的人.:)
I have implemented an ActionMode.Callback
for custom text selection functions within a WebView
. The problem that I am having is that the selection and the action mode states do not match.
When I long-press, everything starts out just fine.
When I interact with one of the buttons, or the WebView
(excluding the actual selection) then the ActionMode
should be destroyed, and the selection should disappear.
In Android 4.4, KitKat, this is exactly what happens.
However, this is not what is happening in 4.1.1 - 4.3, Jelly Bean. When I click one of the buttons, the selection is not removed.
When I tap outside the selection, just the opposite happens. The selection is removed, but the contextual action bar remains on the screen.
Here is the code for my
CustomWebView
public class CustomWebView extends WebView {
private ActionMode.Callback mActionModeCallback;
@Override
public ActionMode startActionMode(Callback callback) {
ViewParent parent = getParent();
if (parent == null) {
return null;
}
mActionModeCallback = new CustomActionModeCallback();
return parent.startActionModeForChild(this, mActionModeCallback);
}
private class CustomActionModeCallback implements 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.contextual_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) {
// This method is called when the handlebars are moved.
loadJavascript("javascript:getSelectedTextInfo()");
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.button_1:
// do stuff
break;
...
default:
break;
}
mode.finish(); // Action picked, so close the CAB
return true;
}
// Called when the user exits the action mode
@Override
public void onDestroyActionMode(ActionMode mode) {
// TODO This does not work in Jelly Bean (API 16 - 18; 4.1.1 - 4.3).
clearFocus(); // Remove the selection highlight and handles.
}
}
}
As the comment above shows, I believe the problem is with the clearFocus()
method. When I remove that method, pressing a button leaves the selection behind in 4.4, just like the behavior in Jelly Bean. clearFocus()
gives the expected behavior in 4.4, but is not transferring to earlier APIs. (Do note that clearFocus()
is not new to KitKat; it has been in Android since API 1.)
How can this be fixed?
After numerous attempts at solving this, I have finally got it!
The important thing to realize is that WebView
s before Android 4.4 (KitKat) are different from your typical browser. There are a few hidden classes that come into play that start to mess up things. There's WebViewCore
which does all the heavy lifting and actually produces results, and there's WebViewClassic
, which is the culprit of this problem.
The solution is a semi-hack, as you don't really have to do anything to manipulate the underlying classes, but you do have to catch the problem scenarios.
WebViewClassic
takes care of intercepting long presses and handling them for text selection, including the animation of the selection highlight and the selection handles, as well as starting the ActionMode
that populates the Contextual Action Bar (CAB). Unfortunately, since we want to override that ActionMode
with our own, the text selection and the CAB become out of sync, because they are not associated with each other. To solve this, keep track of your own custom ActionMode.Callback
, as well as the ActionMode.Callback
associated with the selection animation. Then, when your ActionMode.Callback
is destroyed, call the selection's finish()
method to destroy that ActionMode.Callback
, as well.
OK, enough talk; here's the code.
public class CustomWebView extends WebView {
private ActionMode mActionMode;
private ActionMode.Callback mActionModeCallback;
// Add this class variable
private ActionMode.Callback mSelectActionModeCallback;
@Override
public ActionMode startActionMode(Callback callback) {
/* When running Ice Cream Sandwich (4.0) or Jelly Bean (4.1 - 4.3), there
* is a hidden class called 'WebViewClassic' that draws the selection.
* In order to clear the selection, save the callback from Classic
* so it can be destroyed later.
*/
// Check the class name because WebViewClassic.SelectActionModeCallback
// is not public API.
String name = callback.getClass().toString();
if (name.contains("SelectActionModeCallback")) {
mSelectActionModeCallback = callback;
}
mActionModeCallback = new CustomActionModeCallback();
// We haven't actually done anything yet. Send our custom callback
// to the superclass so it will be shown on screen.
return super.startActionModeForChild(this, mActionModeCallback);
}
private class CustomActionModeCallback implements ActionMode.Callback {
// Called when the action mode is created; startActionMode() was called
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// This is important for part 2.
mActionMode = mode;
// Inflate a menu resource providing context menu items
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.contextual_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) {
// This method is called when the handlebars are moved.
loadJavascript("javascript:getSelectedTextInfo()");
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.button_1:
// do stuff
break;
...
default:
break;
}
mode.finish(); // Action picked, so close the CAB
return true;
}
// Called when the user exits the action mode
@Override
public void onDestroyActionMode(ActionMode mode) {
clearFocus(); // Remove the selection highlight and handles.
// Semi-hack in order to clear the selection
// when running Android earlier than KitKat.
if (mSelectActionModeCallback != null) {
mSelectActionModeCallback.onDestroyActionMode(mode);
}
// Relevant to part 2.
mActionMode = null;
}
}
}
Believe it or not, we're only halfway finished. The above code takes care of removing the selection when the CAB closes. To close the CAB on a touch event, we have to do a little more work. This time it's much more straightforward. I use a
GestureDetector
and listen for a single tap event. When I get that event, I call finish()
on mActionMode
to close the CAB:public class CustomWebView extends WebView {
private ActionMode mActionMode;
private ActionMode.Callback mActionModeCallback;
private ActionMode.Callback mSelectActionModeCallback;
// Code from above segment
...
private class CustomGestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onSingleTapUp(MotionEvent e) {
if (mActionMode != null) {
mActionMode.finish();
return true;
}
return false;
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// Send the event to our gesture detector
// If it is implemented, there will be a return value
this.mDetector.onTouchEvent(event);
// If the detected gesture is unimplemented, send it to the superclass
return super.onTouchEvent(event);
}
}
And that should do it! We did it!
You will not find the WebViewClassic
material anywhere else; that's why I provided so much detail as to what's happening. It took many hours with the debugger to figure out what was going on. Fortunately, the GestureDetector
class is well-documented, and includes multiple tutorials. I got my information from the Android Developers website. I hope this helped those of you that struggled with this problem as much as I did. :)
这篇关于Webview 文本选择未清除的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!