web视图文本选择不清除视图、文本、web

2023-09-04 09:19:58 作者:不交电费瞎发啥光i

我有一个的WebView 中实现的 ActionMode.Callback 自定义文本选择功能。那我遇到的问题是,选择和动作模式状态不匹配。

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.

当我长preSS,一切都开始出来就好了。

When I long-press, everything starts out just fine.

当我与一个按钮,或的WebView (不包括实际的选择)交互则 ActionMode 应销毁,并选择将消失。

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.

在Android 4.4系统,奇巧,这正是发生了什么。

In Android 4.4, KitKat, this is exactly what happens.

不过,这是不是发生什么事4.1.1 - 4.3果冻豆。当我点击一个按钮,选择不会被删除。

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.

这里是code我的 CustomWebView

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.

        }
    }
}

如上图的评论,我相信这个问题是与 clearFocus()方法。当我删除的方法,pressing一个按钮离开,选择4.4的背后,就像果冻豆的行为。 clearFocus()给出了4.4的预期的行为,但不转移到更早的API。 (请注意, clearFocus()是不是新的奇巧;因为API 1它已经在安卓)

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

这怎么能解决吗?

推荐答案

经过无数次的尝试解决这个问题,我终于明白了!

After numerous attempts at solving this, I have finally got it!

要认识到的重要一点是,的WebView 的Andr​​oid 4.4(奇巧)之前,是从你的典型的浏览器不同。有几个隐藏的类,开始发挥作用,启动弄乱的东西。有 WebViewCore 这做所有繁重的任务和实际产生的结果,并有 WebViewClassic ,这是这一问题的罪魁祸首

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 需要截取长presses和处理他们的文本选择,包括选择高亮的动画和选择手柄,以及开始照顾 ActionMode 的填充上下文操作栏(CAB)。不幸的是,因为我们要覆盖 ActionMode 用我们自己的,文本选择和CAB不同步的,因为他们没有相互关联。为了解决这个问题,你自己的自定义 ActionMode.Callback 的跟踪,以及的 ActionMode.Callback 与选择动画相关联。然后,当你的 ActionMode.Callback 被破坏,调用选择的完成()方法来销毁 ActionMode.Callback ,以及

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,少废话;这里的code。

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

信不信由你,我们只是完成了一半。上述code采用去除选择CAB关闭时的照顾。要关闭CAB上的触摸事件,我们必须做一些更多的工作。这一次,它的更简单。我用的是 GestureDetector 并监听一个水龙头事件。当我得到这个事件,我称之为 mActionMode 完成()关闭CAB:

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!

您将无法找到 WebViewClassic 料其他地方;这就是为什么我提供了这么多的细节来发生了什么。它花了很多时间与调试器弄清楚发生了什么事情。幸运的是, GestureDetector 类是证据充分的,并包括多个教程。我从 Android开发者网站我的信息。我希望这有助于那些你这个问题,我一样挣扎着。 :)

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. :)