【问题标题】:Why is WebView not adding the JavascriptInterface when in landscape orientation为什么WebView在横向时不添加JavascriptInterface
【发布时间】:2021-12-14 09:05:52
【问题描述】:

我已经为我的应用程序创建了一个用 html 编写的用户手册。它有一个内容页面,其中包含指向其他部分的链接。我将 html 文件加载到 WebView 中,一切正常。我想要做的是当用户从纵向切换到横向时,保持与以前相同的滚动位置,反之亦然,从横向切换回纵向。我用 onScrollPositionChange 方法创建了一个类 WebScrollListener。这作为 JavascriptInterface 添加到 WebView,因此 Javascript 设置方法中变量的值。我第一次从纵向到横向运行良好,但是一旦进入横向,WebScrollListener 就会停止监听滚动事件。这是我的 Fragment 的代码。

 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 <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
                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 文件中,如下所示:

<script type="text/javascript" src="scroll-position.js"></script>

这里是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 < 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);
  });

  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();
  }

任何帮助将不胜感激。

【问题讨论】:

    标签: javascript android html webview


    【解决方案1】:

    onCreate 入口点您的 webview 添加条件一切正常,除了 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;
     }
    

    【讨论】:

    • 这不起作用。 onSavedInstanceState 中保存的 URL 是已加载的 URL,但不是页面加载后滚动到的位置。因此,当方向改变时,它不会回到滚动到的最后一个位置,而是回到用户第一次加载页面时所处的位置。问题在于 WebScrollListener 与 javascript 的接口。纵向滚动时,我可以看到 Logcat 当前滚动位置的输出。当我转到横向和滚动时,Logcat 中没有输出,这意味着没有从 javascript 调用 WebScrollListener
    【解决方案2】:

    问题出在 javascript 函数 findSmallAlementOnScreen 中。此函数的 SIZE_LIMIT 为 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;
      };
    

    【讨论】:

      猜你喜欢
      • 2021-01-03
      • 1970-01-01
      • 2021-03-19
      • 1970-01-01
      • 2013-01-17
      • 2019-09-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多