Tại sao bạn nên cân nhắc câu trả lời này qua câu trả lời được chấp nhận:
câu trả lời chấp nhận cung cấp đàng hoàng và đơn giản cách để tiết kiệm di chuyển vị trí, tuy nhiên nó còn xa mới hoàn hảo. Vấn đề với cách tiếp cận đó là đôi khi trong quá trình quay, bạn thậm chí sẽ không thấy bất kỳ phần tử nào bạn nhìn thấy trên màn hình trước khi quay. Phần tử ở đầu màn hình giờ có thể ở dưới cùng sau khi xoay. Lưu vị trí thông qua phần trăm cuộn không phải là rất chính xác và trên các tài liệu lớn không chính xác này có thể tăng lên. Vì vậy, đây là một phương pháp khác: nó là cách phức tạp hơn, nhưng nó gần như đảm bảo rằng bạn sẽ thấy chính xác cùng một yếu tố sau khi quay mà bạn đã thấy trước khi quay. Theo tôi, điều này dẫn đến trải nghiệm người dùng tốt hơn nhiều, đặc biệt là trên một tài liệu lớn.
======
Trước hết, chúng tôi sẽ theo dõi vị trí cuộn hiện tại qua javascript. Điều này sẽ cho phép chúng tôi biết chính xác phần tử nào hiện đang ở đầu màn hình và phần tử được cuộn.
Thứ nhất, đảm bảo rằng javascript được kích hoạt cho WebView của bạn:
webView.getSettings().setJavaScriptEnabled(true);
Tiếp theo, chúng ta cần tạo lớp java mà sẽ chấp nhận thông tin từ bên trong 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;
}
}
Sau đó chúng ta thêm lớp này tới WebView:
scrollListener = new WebScrollListener(); // save this in an instance variable
webView.addJavascriptInterface(scrollListener, "WebScrollListener");
Bây giờ, chúng tôi cần chèn mã javascript vào trang html. Kịch bản này sẽ gửi dữ liệu cuộn để java (nếu bạn là thế hệ html, chỉ cần nối thêm kịch bản này, nếu không, bạn có thể cần phải nghỉ mát để gọi document.write()
qua 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>
Nếu bạn chạy ứng dụng của bạn vào thời điểm này, bạn sẽ thấy các thông báo trong logcat cho bạn biết rằng vị trí cuộn mới được nhận.
Bây giờ chúng ta cần phải lưu trạng thái WebView:
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
webView.saveState(outState);
outState.putString("scrollElement", scrollListener.element);
outState.putInt("scrollMargin", scrollListener.margin);
}
Sau đó, chúng ta đọc nó trong onCreate (đối với hoạt động) hoặc onCreateView (đối với đoạn) phương pháp:
if (savedInstanceState != null) {
webView.restoreState(savedInstanceState);
initialScrollElement = savedInstanceState.getString("scrollElement");
initialScrollMargin = savedInstanceState.getInt("scrollMargin");
}
Chúng tôi cũng cần thêm WebViewClient
vào webView của chúng tôi và ghi đè phương thức 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);
}
}
Đây là nó. Sau khi xoay màn hình, phần tử ở đầu màn hình của bạn sẽ vẫn ở đó.
@ ol_v-er Cảm ơn thông tin. Tôi quan tâm để thử giới thiệu sự chậm trễ như bạn mô tả để xem công việc 'scrollTo' (Tôi ngạc nhiên rằng một sự chậm trễ là cần thiết nhưng anyway ...). Cuối cùng đã có cuộn để làm việc thông qua một giải pháp javascript (mà tôi sẽ đăng lên sớm) Tôi đã có ý tưởng về tỷ lệ phần trăm cuộn mà tôi đã không có xung quanh để cố gắng chưa thấy kết quả. – PJL
Trong thực tế khi gọi lại onPageFinished được gọi, WebView chỉ bắt đầu vẽ. Độ trễ là đảm bảo WebView đã vẽ xong (và có chiều cao chính xác). Mong cho giải pháp javascript của bạn. –
Cảm ơn, đã thêm giải pháp javascript của tôi. – PJL