为什么你应该考虑这个答案而不是接受的答案:
已接受的答案提供了体面且简单的方式来保存滚动位置,但它远非完美。这种方法的问题在于,有时在旋转过程中,您甚至看不到旋转前在屏幕上看到的任何元素。位于屏幕顶部的元素现在可以在旋转后位于底部。通过滚动百分比保存位置不是很准确,在大型文档上这种不准确可能会增加。
所以这是另一种方法:它要复杂得多,但它几乎可以保证您在旋转后看到的元素与在旋转前看到的元素完全相同。在我看来,这会带来更好的用户体验,尤其是在大型文档上。
======
首先,我们将通过 javascript 跟踪当前滚动位置。这将使我们能够准确地知道哪个元素当前位于屏幕顶部以及它滚动了多少。
首先,确保为您的 WebView 启用了 javascript:
webView.getSettings().setJavaScriptEnabled(true);
接下来,我们需要创建一个从 javascript 中接受信息的 java 类:
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");
现在我们需要在 html 页面中插入 javascript 代码。该脚本会将滚动数据发送到java(如果您是生成html,只需附加此脚本;否则,您可能需要通过webView.loadUrl("javascript:document.write(" + script + ")"); 调用document.write()):
<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(对于Activity)或者onCreateView(对于fragment)方法中读取:
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's 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);
}
}
就是这样。屏幕旋转后,位于屏幕顶部的元素现在应该保留在那里。