Страницы

вторник, 27 сентября 2011 г.

Выделение текста в WebView или как выстрелить себе в ногу.


Понадобилось выделять текст в WebView. Потратил почти два дня поочередных запросов к гуглу, поисков на StackOverflow и изучения исходников стандартного браузера. Решение в итоге нашёл, даже два, но осадок остался. Задача довольно часто нужна и можно было бы описать её решение в документации.



Решение №1. Quick and dirty hack, лежащий на поверхности.
У WebView есть метод emulateShiftHeld(), доступен он только начиная с версии 2.2 (cамое странное, что он был тут же объявлен deprecated), но появился он раньше и можно попробовать достать его через reflection. Этот метод переводит WebView в режим выделения текста, появляется курсор мыши (sic!), и можно выделить текст.

public void selectAndCopyText() {
    try {
        Method m = WebView.class.getMethod("emulateShiftHeld", Boolean.TYPE); 
        m.invoke(BookView.mWebView, false); 
    } catch (Exception e) {
        e.printStackTrace();
        // fallback
        KeyEvent shiftPressEvent = new KeyEvent(0,0,
            KeyEvent.ACTION_DOWN,KeyEvent.KEYCODE_SHIFT_LEFT,0,0);
        shiftPressEvent.dispatch(this);
    }
}

Вызов этого метода можно повесить, например, на лонг тап.
Однако, в стандартном браузере лонг тап вызывает сразу выделение текста, без всяких курсоров. Значит можно как-то улучшить существующее решение.

Решение №2. Это решение мне нравится больше, т.к. имитирует поведение системного браузера и нужно писать меньше кода :) Но есть и минус -- используется private API.
К вечеру второго дня нашёл наконец-таки код в исходниках, который не даёт выделять текст в WebView. Вот он:

public void onSelectionStart(WebView view) {
    // By default we cancel the selection again, thus disabling
    // text selection unless the chrome client supports it.
    view.notifySelectDialogDismissed();
}

В комментариях собственно всё сказано. Однако метод помечен аннотацией @hide, что как бы намекает нам, что использовать его не рекомендуется. Если очень нужно, то можно всё же переопределить этот метод.

setWebChromeClient(new WebChromeClient() {
 public void onSelectionStart(WebView view) {
  // Именно так, без @Override и с пустым телом
 }
});

В дополнение можно переопределить метод onSelectionDone(), чтобы выполнить какие-либо действия после завершения выделения.

Используйте это способ на свой страх и риск. В завершение процитирую, что думает Dianne Hackborn про использование private API (взято отсюда, из комментариев):

DO NOT DO THIS. It's not just "you are not supposed to use them," it is "an app relying on this will randomly break on different devices and platform versions" because there is no guarantee that these private symbols will remain.

To be clear -- when we are doing compatibility testing with new versions of the Android platform, if we have an app that is breaking and see anywhere it is using private APIs, we stop testing it and consider it a broken app, end of story. You have a very good chances of painfully shooting yourself in the foot by using private APIs.

P.S.: Я не претендую на истину в последней инстанции, возможно есть более человеческие способы доступа к выделению в webView, но я их не нашёл. Буду благодарен, если подскажете что-нибудь ещё.

3 комментария:

  1. Странно, на у меня не работает =(

    ОтветитьУдалить
    Ответы
    1. Как бы и не гарантируется работоспособность всегда и везде, ибо это хак. На каком девайсе/версии Андроида не работает?

      Удалить
    2. Я тестировал на андроид 2.3.6 (Samsung galaxi gio).
      На 4.0.3 тоже не работает.
      Я уже в течении месяца пытаюсь решить проблему выделения текста.похоже все попытки обречены =(

      Удалить