Webview 文本选择未清除 [英] Webview text selection not clearing

查看:27
本文介绍了Webview 文本选择未清除的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经为 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 WebViews 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆