8

我已经ActionMode.CallbackWebView. 我遇到的问题是选择和操作模式状态不匹配。

当我长按时,一切开始都很好。

长按后,选择突出显示与上下文操作栏一起出现。

当我与其中一个按钮或WebView(不包括实际选择)交互时,ActionMode应该销毁,并且选择应该消失。

选择一个按钮,或在选择区域外触摸。 CAB 应该关闭,并且选择应该消失。

在 Android 4.4 KitKat 中,这正是发生的情况。


但是,这不是 4.1.1 - 4.3,Jelly Bean 中发生的事情。当我单击其中一个按钮时,不会删除选择。

点击上下文操作栏中的按钮。 点击按钮后,选择仍然存在。

当我在选择之外点击时,会发生相反的情况。选择被删除,但上下文操作栏仍保留在屏幕上。

选择开始后点击 <code>WebView</code>。 选择被清除,但上下文操作栏仍然存在。


这是我的代码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()方法上。当我删除该方法时,按下按钮会在 4.4 中留下选择,就像 Jelly Bean 中的行为一样。clearFocus()给出了 4.4 中的预期行为,但没有转移到早期的 API。(请注意,这clearFocus()对 KitKat 来说并不新鲜;它自 API 1 以来一直在 Android 中。)

如何解决这个问题?

4

1 回答 1

7

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

需要意识到的重要一点是,WebViewAndroid 4.4 (KitKat) 之前的版本与您的典型浏览器不同。有一些隐藏的课程开始发挥作用,开始把事情搞砸。有WebViewCorewhich 完成了所有繁重的工作并实际产生了结果,还有WebViewClassic,这是这个问题的罪魁祸首。

解决方案是半破解,因为您实际上不需要做任何事情来操作底层类,但您必须抓住问题场景。

WebViewClassic负责截取长按并处理它们以进行文本选择,包括选择突出显示和选择句柄的动画,以及启动ActionMode填充上下文操作栏 (CAB) 的动画。不幸的是,由于我们想ActionMode用我们自己的覆盖它,所以文本选择和 CAB 变得不同步,因为它们彼此没有关联。要解决这个问题,请跟踪您自己的自定义ActionMode.Callback以及ActionMode.Callback选择动画相关的。然后,当你的ActionMode.Callback被销毁时,调用选择的finish()方法来销毁它ActionMode.Callback

好了,说够了;这是代码。

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


信不信由你,我们只完成了一半。上面的代码负责在 CAB 关闭时删除选择。要在触摸事件上关闭 CAB,我们还需要做更多的工作。这次就简单多了。我使用 aGestureDetector并监听单击事件。当我收到该事件时,我呼吁finish()关闭mActionModeCAB:

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

}


那应该这样做!我们做到了!

您不会WebViewClassic在其他任何地方找到该材料;这就是为什么我提供了如此多的细节来说明正在发生的事情。调试器花了好几个小时才弄清楚发生了什么。幸运的是,该GestureDetector课程有详细的文档,并且包含多个教程。我从Android Developers 网站获得了我的信息。我希望这可以帮助那些像我一样努力解决这个问题的人。:)

于 2014-05-16T18:40:59.810 回答