#javascript #android #html #webview
Вопрос:
Я создал руководство пользователя для своего приложения, написанное на html. В нем есть страница содержимого со ссылками на другие разделы. Я загружаю html-файл в WebView, и все работает нормально. Что я хочу сделать, так это, когда пользователь переключается с портретной на альбомную, сохранять ту же позицию прокрутки, что и раньше, и наоборот, с альбомной на портретную. Я создал класс WebScrollListener с методом onScrollPositionChange. Это добавляется в веб-представление как JavascriptИнтерфейс, поэтому Javascript задает значения переменных в методе. У меня все работает нормально, переходя от портрета к пейзажу в первый раз, но как только в альбомной ориентации, webscrollistener перестает прослушивать события прокрутки. Вот код для моего фрагмента.
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { int fragmentId = scenarioViewModel.getCurrentFragmentId(); // Inflate the layout for this fragment View root = inflater.inflate(R.layout.fragment_user_manual, container, false); if (savedInstanceState != null) { // orientation changed has occurred initialScrollElement = savedInstanceState.getString("scrollElement"); initialScrollMargin = savedInstanceState.getInt("scrollMargin"); } webview = root.findViewById(R.id.help_text); hashTag = getHashTag(fragmentId); webview.getSettings().setJavaScriptEnabled(true); WebViewClient webViewClient = new MyWebViewClient(); webview.setWebViewClient(webViewClient); scrollListener = new WebScrollListener(); webview.addJavascriptInterface(scrollListener, "WebScrollListener"); webview.loadUrl("file:///android_asset/app-user-manual.html"); return root; } public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); outState.putString("scrollElement", scrollListener.element); outState.putInt("scrollMargin", scrollListener.margin); } private class MyWebViewClient extends WebViewClient { public void onPageFinished(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 lt;scriptgt;...lt;/scriptgt; 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 view.postDelayed(new Runnable() { @Override public void run() { StringBuilder buf=new StringBuilder("javascript:scrollToPosition('" initialScrollElement "','" initialScrollMargin "')"); Log.d("MyWebViewClient", "returned string:" buf.toString()); webview.loadUrl(buf.toString()); initialScrollElement = null; } }, 300); } else { if (url.contains("#") != true) { // We only go into this method if the user has clicked on the help button // and we want to go to a specific hashtag. The page gets reloaded at that hashtag. if (!hashTag.isEmpty()) { String myurl = url hashTag; // this gets loaded again so as to get the position of the hashtag webview.loadUrl(myurl); } } super.onPageFinished(view, url); } } } 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; } }
Javascript включен в html-файл следующим образом:
lt;script type="text/javascript" src="scroll-position.js"gt;lt;/scriptgt;
Вот код javascript:
// 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 lt; 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(' gt; '); }; // 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); }); function scrollToPosition (selectorToRestore, positionToRestore) { var previousTop = 0; var check = function() { var elem = document.querySelector(selectorToRestore); if (!elem) { setTimeout(check, 100); return; } var currentTop = elem.getBoundingClientRect().top; if (currentTop != previousTop) { previousTop = currentTop; setTimeout(check,100); } else { window.scrollBy(0, currentTop - positionToRestore); } } check(); }
Любая помощь будет признательна.
Ответ №1:
onCreate точка входа ваше веб-представление добавляет условие, чтобы все работало нормально, за исключением JavascriptInterface, КОТОРЫЙ НИКОГДА не работает после изменения ориентации :
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { webView = root.findViewById(R.id.webView); if (webView != null) { String URL = (savedInstanceState == null) ? "" : savedInstanceState.getString("URL"); if (URL.equals("")) { webView.loadUrl("yourhome.html"); } else { webView.loadUrl(URL); // this is saved in onSaveInstanceState } } return root; }
Комментарии:
1. Это не сработало. URL-адрес, сохраненный в onSavedInstanceState, — это URL-адрес, который был загружен, но не был прокручен до позиции после загрузки страницы. Поэтому вместо перехода к последней позиции, до которой была прокручена страница, при изменении ориентации она возвращается в положение, в котором находился пользователь при первой загрузке страницы. Проблема заключается в том, что webscrollistener взаимодействует с javascript. При прокрутке в портретном режиме я вижу вывод из Logcat текущего положения прокрутки. Когда я перехожу в альбомную ориентацию и прокручиваю, в Logcat нет вывода, что означает, что webscrollistener не вызывается из javascript
Ответ №2:
Проблема заключалась в функции javascript findSmallAlementOnScreen. Эта функция имела ограничение по размеру 1024, но с помощью отладчика я обнаружил, что в ландшафте это значение было превышено, поэтому верхний элемент так и не был найден.
Этот метод теперь отлично работает всего с двумя строками кода.
var findSmallElementOnScreen = function() { var range = document.caretRangeFromPoint(0,0); // screen coordinates of upper-left corner of a scolled area (in this case screen itself) var element = range.startContainer.parentNode; // this an upper onscreen element return element; };