Подтвердить что ты не робот

Ведение содержимого прокрутки содержимого WebView при изменении ориентации

Браузеры в Android 2.3+ отлично справляются с сохранением прокрученной позиции содержимого при изменении ориентации.

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

Я протестировал изменение манифеста (андроид: configChanges = "orientation | keyboardHidden" ), чтобы избежать активного отдыха на ротации, однако из-за разных пропорций положение прокрутки не устанавливается так, как я хочу. Кроме того, это не решение для меня, поскольку мне нужно иметь разные макеты в портретной и альбомной ориентации.

Я попытался сохранить состояние WebView и восстановить его, но это снова возвращает содержимое, отображаемое наверху.

Кроме того, попытка прокрутки в onPageFinished с помощью scrollTo не работает, даже если высота WebView в данный момент не равна нулю.

Любые идеи? Заранее спасибо. Питер.

Частичное решение:

Моему коллеге удалось прокрутить работу с помощью javascript-решения. Для простой прокрутки в том же вертикальном положении положение прокрутки WebView "Y" сохраняется в состоянии onSaveInstanceState. Затем в onPageFinished добавляется следующее:

public void onPageFinished(final WebView view, final String url) {
    if (mScrollY > 0) {
        final StringBuilder sb = new StringBuilder("javascript:window.scrollTo(0, ");
        sb.append(mScrollY);
        sb.append("/ window.devicePixelRatio);");
        view.loadUrl(sb.toString());
    }
    super.onPageFinished(view, url);
}

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

4b9b3361

Ответ 1

Чтобы восстановить текущую позицию WebView во время изменения ориентации, я боюсь, что вам придется делать это вручную.

Я использовал этот метод:

  • Рассчитать фактический процент прокрутки в WebView
  • Сохраните его в onRetainNonConfigurationInstance
  • Восстановить положение WebView при его воссоздании

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

Шаг за шагом:

1) Рассчитать фактический процент прокрутки в WebView

// Calculate the % of scroll progress in the actual web page content
private float calculateProgression(WebView content) {
    float positionTopView = content.getTop();
    float contentHeight = content.getContentHeight();
    float currentScrollPosition = content.getScrollY();
    float percentWebview = (currentScrollPosition - positionTopView) / contentHeight;
    return percentWebview;
}

2) Сохраните его в onRetainNonConfigurationInstance

Сохранить прогресс непосредственно перед изменением ориентации

@Override
public Object onRetainNonConfigurationInstance() {
    OrientationChangeData objectToSave = new OrientationChangeData();
    objectToSave.mProgress = calculateProgression(mWebView);
    return objectToSave;
}

// Container class used to save data during the orientation change
private final static class OrientationChangeData {
    public float mProgress;
}

3) Восстановить положение WebView при его воссоздании

Получите прогресс от данных изменения ориентации

private boolean mHasToRestoreState = false;
private float mProgressToRestore;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);

    mWebView = (WebView) findViewById(R.id.WebView);
    mWebView.setWebViewClient(new MyWebViewClient());
    mWebView.loadUrl("http://stackoverflow.com/");

    OrientationChangeData data = (OrientationChangeData) getLastNonConfigurationInstance();
    if (data != null) {
        mHasToRestoreState = true;
        mProgressToRestore = data.mProgress;
    }
}

Чтобы восстановить текущую позицию, вам придется ждать перезагрузки страницы ( этот метод может быть проблематичным, если ваша страница занимает много времени для загрузки)

private class MyWebViewClient extends WebViewClient {
    @Override
    public void onPageFinished(WebView view, String url) {
        if (mHasToRestoreState) {
            mHasToRestoreState = false;
            view.postDelayed(new Runnable() {
                @Override
                public void run() {
                    float webviewsize = mWebView.getContentHeight() - mWebView.getTop();
                    float positionInWV = webviewsize * mProgressToRestore;
                    int positionY = Math.round(mWebView.getTop() + positionInWV);
                    mWebView.scrollTo(0, positionY);
                }
            // Delay the scrollTo to make it work
            }, 300);
        }
        super.onPageFinished(view, url);
    }
}

Во время моего теста я сталкиваюсь с тем, что вам нужно немного подождать после вызова метода onPageFinished, чтобы заставить свиток работать. 300 мс должно быть нормально. Эта задержка заставит дисплей отображаться (сначала отобразится при прокрутке 0, затем перейдите в правильное положение).

Возможно, есть и другой лучший способ сделать это, но я не знаю.

Ответ 2

onPageFinished не может быть вызван, потому что вы не перезагружаете страницу, вы просто меняете ориентацию, не уверен, что это вызывает перезагрузку или нет.

Попробуйте использовать scrollTo в методе onConfigurationChanged вашей активности.

Ответ 3

Изменение формата, скорее всего, всегда приведет к тому, что текущее местоположение в вашем веб-браузере не будет подходящим местом для прокрутки. Вы могли бы подкрасться и определить самый верхний наиболее заметный элемент в WebView и после имплантации изменения ориентации anchor в этот момент в источнике и перенаправить пользователь к нему...

Ответ 4

чтобы вычислить правильный процент WebView, важно также подсчитать mWebView.getScale(). Фактически, возвращаемое значение getScrollY() соответствует mWebView.getContentHeight() * mWebView.getScale().

Ответ 5

Как нам удалось достичь этого, чтобы иметь локальную ссылку на WebView в наших фрагментах.

/**
* WebView reference
*/
private WebView webView;

Затем setRetainInstance до true в onCreate

@Override
public void onCreate(final Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setRetainInstance(true);

    // Argument loading settings
}

Затем, когда вызывается onCreateView, верните существующий локальный экземпляр WebView, в противном случае создайте новую копию, настроив содержимое и другие настройки. Ключевым этапом он является то, что он повторно присоединяет WebView, чтобы удалить родительский элемент, который вы можете увидеть в следующем разделе ниже.

@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
        final Bundle savedInstanceState) {

    final View view;
    if (webView == null) {

        view = inflater.inflate(R.layout.webview_dialog, container);

        webView = (WebView) view.findViewById(R.id.dialog_web_view);

        // Setup webview and content etc... 
    } else {
        ((ViewGroup) webView.getParent()).removeView(webView);
        view = webView;
    }

    return view;
}

Ответ 6

Я использовал частичное решение, описанное в исходном сообщении, но вместо этого разместил фрагмент javascript внутри onPageStarted() вместо onPageFinished(). Кажется, работает для меня без прыжков.

Мой пример использования немного отличается: я пытаюсь сохранить горизонтальную позицию после обновления пользователем страницы.

Но я смог использовать ваше решение, и он отлично работает для меня:)

Ответ 7

В частичном решении, описанном в исходном сообщении, вы можете улучшить решение, если изменить следующий код

private float calculateProgression(WebView content) {
    float contentHeight = content.getContentHeight();
    float currentScrollPosition = content.getScrollY();
    float percentWebview = currentScrollPosition/ contentHeight;
    return percentWebview;
}

из-за верхней позиции компонента, когда вы используете content.getTop(), не требуется; Таким образом, это то, что он добавляет к фактическому положению положения прокрутки Y, где компонент расположен по отношению к нему родительского элемента, и заканчивает контент. Надеюсь, мои разъяснения понятны.

Ответ 8

Почему вы должны рассмотреть этот ответ по принятому ответу:

Принятый ответ обеспечивает достойный и простой способ сохранить положение прокрутки, однако он далек от совершенства. Проблема с этим подходом заключается в том, что иногда во время вращения вы даже не увидите никаких элементов, которые вы видели на экране перед вращением. Элемент, который находился в верхней части экрана, теперь может быть внизу после вращения. Сохранение позиции через процент прокрутки не очень точно, и на больших документах эта погрешность может складываться.

Итак, вот еще один способ: более сложный, но он почти гарантирует, что вы увидите ровно тот же самый элемент после вращения, который вы видели перед вращением. На мой взгляд, это приводит к значительно лучшему пользовательскому опыту, особенно в больших документах.

======

Прежде всего, мы будем отслеживать текущую позицию прокрутки с помощью javascript. Это позволит нам точно узнать, какой элемент находится в верхней части экрана и сколько прокручивается.

Во-первых, убедитесь, что javascript включен для вашего WebView:

webView.getSettings().setJavaScriptEnabled(true);

Далее нам нужно создать класс java, который будет принимать информацию из javascript:

public class WebScrollListener {

    private String element;
    private int margin;

    @JavascriptInterface
    public void onScrollPositionChange(String topElementCssSelector, int topElementTopMargin) {
        Log.d("WebScrollListener", "Scroll position changed: " + topElementCssSelector + " " + topElementTopMargin);
        element = topElementCssSelector;
        margin = topElementTopMargin;
    }

}

Затем мы добавим этот класс в WebView:

scrollListener = new WebScrollListener(); // save this in an instance variable
webView.addJavascriptInterface(scrollListener, "WebScrollListener");

Теперь нам нужно вставить javascript-код в html-страницу. Этот script отправит данные прокрутки в java (если вы являетесь генератором html, просто добавьте этот script, в противном случае вам может потребоваться позвонить document.write() через webView.loadUrl("javascript:document.write(" + script + ")");):

<script>
    // We will find first visible element on the screen 
    // by probing document with the document.elementFromPoint function;
    // we need to make sure that we dont just return 
    // body element or any element that is very large;
    // best case scenario is if we get any element that 
    // doesn't contain other elements, but any small element is good enough;
    var findSmallElementOnScreen = function() {
        var SIZE_LIMIT = 1024;
        var elem = undefined;
        var offsetY = 0;
        while (!elem) {
            var e = document.elementFromPoint(100, offsetY);
            if (e.getBoundingClientRect().height < SIZE_LIMIT) {
                elem = e;
            } else {
                offsetY += 50;
            }
        }
        return elem;
    };

    // Convert dom element to css selector for later use
    var getCssSelector = function(el) {
        if (!(el instanceof Element)) 
            return;
        var path = [];
        while (el.nodeType === Node.ELEMENT_NODE) {
            var selector = el.nodeName.toLowerCase();
            if (el.id) {
                selector += '#' + el.id;
                path.unshift(selector);
                break;
            } else {
                var sib = el, nth = 1;
                while (sib = sib.previousElementSibling) {
                    if (sib.nodeName.toLowerCase() == selector)
                       nth++;
                }
                if (nth != 1)
                    selector += ':nth-of-type('+nth+')';
            }
            path.unshift(selector);
            el = el.parentNode;
        }
        return path.join(' > ');    
    };

    // Send topmost element and its top offset to java
    var reportScrollPosition = function() {
        var elem = findSmallElementOnScreen();
        if (elem) {
            var selector = getCssSelector(elem);
            var offset = elem.getBoundingClientRect().top;
            WebScrollListener.onScrollPositionChange(selector, offset);
        }
    }

    // We will report scroll position every time when scroll position changes,
    // but timer will ensure that this doesn't happen more often than needed
    // (scroll event fires way too rapidly)
    var previousTimeout = undefined;
    window.addEventListener('scroll', function() {
        clearTimeout(previousTimeout);
        previousTimeout = setTimeout(reportScrollPosition, 200);
    });
</script>

Если вы запустите свое приложение в этот момент, вы уже должны видеть сообщения в logcat, сообщающие вам, что получена новая позиция прокрутки.

Теперь нам нужно сохранить состояние webView:

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    webView.saveState(outState);
    outState.putString("scrollElement", scrollListener.element);
    outState.putInt("scrollMargin", scrollListener.margin);
}

Затем мы читаем его в методе onCreate (for Activity) или onCreateView (для фрагмента):

if (savedInstanceState != null) {
    webView.restoreState(savedInstanceState);
    initialScrollElement = savedInstanceState.getString("scrollElement");
    initialScrollMargin = savedInstanceState.getInt("scrollMargin");
}

Нам также нужно добавить WebViewClient в наш webView и переопределить метод onPageFinished:

@Override
public void onPageFinished(final WebView view, String url) {
    if (initialScrollElement != null) {
        // It very hard to detect when web page actually finished loading;
        // At the time onPageFinished is called, page might still not be parsed
        // Any javascript inside <script>...</script> tags might still not be executed;
        // Dom tree might still be incomplete;
        // So we are gonna use a combination of delays and checks to ensure
        // that scroll position is only restored after page has actually finished loading
        webView.postDelayed(new Runnable() {
            @Override
            public void run() {
                String javascript = "(function ( selectorToRestore, positionToRestore ) {\n" +
                        "       var previousTop = 0;\n" +
                        "       var check = function() {\n" +
                        "           var elem = document.querySelector(selectorToRestore);\n" +
                        "           if (!elem) {\n" +
                        "               setTimeout(check, 100);\n" +
                        "               return;\n" +
                        "           }\n" +
                        "           var currentTop = elem.getBoundingClientRect().top;\n" +
                        "           if (currentTop !== previousTop) {\n" +
                        "               previousTop = currentTop;\n" +
                        "               setTimeout(check, 100);\n" +
                        "           } else {\n" +
                        "               window.scrollBy(0, currentTop - positionToRestore);\n" +
                        "           }\n" +
                        "       };\n" +
                        "       check();\n" +
                        "}('" + initialScrollElement + "', " + initialScrollMargin + "));";

                webView.loadUrl("javascript:" + javascript);
                initialScrollElement = null;
            }
        }, 300);
    }
}

Это он. После поворота экрана элемент, который был наверху вашего экрана, должен оставаться там.