Выбор текста в веб-просмотре не очищается

Я реализовал ActionMode.Callback для пользовательских функций выбора текста в файле WebView. Проблема, с которой я сталкиваюсь, заключается в том, что состояния выбора и режима действия не совпадают.

Когда я долго нажимаю, все начинается просто отлично.

При длительном нажатии появляется выделение выделения вместе с контекстной панелью действий.

Когда я взаимодействую с одной из кнопок или 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; он был в Android со времен API 1.)

Как это можно исправить?


person Sean Beach    schedule 24.04.2014    source источник
comment
я тоже застрял здесь, чтобы очистить выделение текста в API 4.1.2, пожалуйста, помогите мне..   -  person Irshad Khan    schedule 15.05.2014
comment
@DirtyBeach, как сделать невидимой контекстную панель веб-просмотра? любая концепция/идея?   -  person Tara    schedule 18.07.2018


Ответы (1)


После многочисленных попыток решить эту проблему я, наконец, понял!

Важно понимать, что WebViews до Android 4.4 (KitKat) отличаются от вашего обычного браузера. В игру вступают несколько скрытых классов, которые начинают все портить. Есть WebViewCore, который делает всю тяжелую работу и фактически дает результаты, и есть 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 по событию касания, нам нужно проделать еще немного работы. На этот раз все гораздо проще. Я использую GestureDetector и слушаю одно касание. Когда я получаю это событие, я вызываю finish() на mActionMode, чтобы закрыть 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);
    }

}


И это должно сработать! Мы сделали это!

Вы больше нигде не найдете материал WebViewClassic; вот почему я предоставил так много подробностей о том, что происходит. Отладчику потребовалось много часов, чтобы понять, что происходит. К счастью, класс GestureDetector хорошо документирован и включает несколько руководств. Я получил информацию с веб-сайта разработчиков Android. Я надеюсь, что это помогло тем из вас, кто боролся с этой проблемой так же сильно, как и я. :)

person Sean Beach    schedule 16.05.2014
comment
Спасибо за ваши усилия. Но как я могу получить выделенный текст. - person Sazzad Hissain Khan; 03.09.2014
comment
loadJavascript(javascript:getSelectedTextInfo()); не решен - person Sazzad Hissain Khan; 03.09.2014
comment
getSelectedTextInfo() — это пользовательский метод JS, который я написал. Вам нужно будет написать свой собственный JS для доступа к выбору. Я предлагаю начать с window.getSelection(). Если это не сработает, попробуйте использовать диапазон(ы). Также не гарантируется работа на всех версиях Android; 4.0, в частности, имеет довольно много проблем с JS. В качестве альтернативы представленным здесь решениям см. этот вопрос и его ответы: stackoverflow.com/questions/22336903/ - person Sean Beach; 03.09.2014
comment
можете ли вы привести пример использования getSelectedTextInfo - person Sazzad Hissain Khan; 03.09.2014
comment
В моем конкретном случае я создаю аннотации (например, основные моменты) в epubs. Я использую getSelectedTextInfo(), чтобы получить выбранный текст И его расположение в DOM, чтобы я мог правильно и точно сохранить аннотацию для своих пользователей. - person Sean Beach; 03.09.2014
comment
Будет полезно, если вы поделитесь примером кода. заранее спасибо - person Sazzad Hissain Khan; 04.09.2014