Почему WebView не добавляет интерфейс JavascriptInterface в альбомной ориентации

#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;  };