Изборът на текст в Webview не се изчиства

Внедрил съм 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
В моя конкретен случай създавам анотации (като акценти) в epub файлове. Използвам getSelectedTextInfo(), за да получа текста, който е избран, И неговото местоположение с DOM, така че да мога правилно и точно да запазя анотацията за моите потребители. - person Sean Beach; 03.09.2014
comment
Ще бъде полезно, ако споделите примерен код. Благодаря предварително - person Sazzad Hissain Khan; 04.09.2014