爲什麼你應該考慮這個答案比接受的答案:
接受的答案提供體面和簡單的方式來保存滾動位置,但它遠非完美。該方法的問題在於,有時在旋轉之前,甚至不會看到旋轉前在屏幕上看到的任何元素。旋轉後,位於屏幕頂部的元素現在可以位於底部。通過滾動百分比來保存位置不是很準確,而且在大型文檔中,這種不準確性會加起來。
所以這裏是另一種方法:它的方式更復雜,但它幾乎可以保證你會看到旋轉之前看到完全相同的元素。在我看來,這會帶來更好的用戶體驗,特別是在大型文檔上。
======
首先,我們將跟蹤通過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頁面。該腳本將滾動數據發送到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讀它(對活動)或onCreateView(對於片段)方法:
if (savedInstanceState != null) {
webView.restoreState(savedInstanceState);
initialScrollElement = savedInstanceState.getString("scrollElement");
initialScrollMargin = savedInstanceState.getInt("scrollMargin");
}
我們還需要添加WebViewClient
我們的web視圖,並覆蓋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);
}
}
就是這樣。屏幕旋轉後,位於屏幕頂部的元素現在應保留在那裏。
@ ol_v-er感謝您的信息。我有興趣嘗試介紹延遲,因爲你看到'scrollTo'工作(我很驚訝,需要延遲,但無論如何...)。終於通過JavaScript解決方案滾動工作(我將很快發佈),我有了滾動百分比的想法,我還沒有試圖去看看結果。 – PJL
事實上,當onPageFinished回調被調用時,WebView就開始繪製。延遲是確保WebView已完成繪圖(並具有正確的高度)。期待您的JavaScript解決方案。 –
謝謝,添加了我的JavaScript解決方案。 – PJL