【问题标题】:How can I catch scrolling of element inside android webview如何在 android webview 中捕捉元素的滚动
【发布时间】:2015-07-30 13:01:33
【问题描述】:

我有一个 SwipeRefreshLayout 视图作为父级。在它里面我使用了一个加载网页的webview。我想通过下拉 SwipeRefreshLayout 来重新加载 webview。这是我的xml:

<android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/refresh_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <WebView
            android:id="@+id/webView"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

</android.support.v4.widget.SwipeRefreshLayout>

和java代码:

mWebView = (WebView) view.findViewById(R.id.webView);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.loadUrl("http://kashirskoe.vegas-city.ru/renter/");

mSwipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.refresh_layout);
    mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            mSwipeRefreshLayout.setRefreshing(true);
            mSwipeRefreshLayout.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mWebView.reload();
                    mSwipeRefreshLayout.setRefreshing(false);
                }
            }, 1000);
        }
    });

乍一看,它很完美。但是当用户向下滚动时,例如网页上的地图控件,它不会滚动。而不是这个 SwipeRefreshLayout 拦截滚动和刷新网页(示例页面 url 在代码上)。

我想在页面上的任何 Web 控件滚动时禁用 SwipeRefreshLayout 滚动。就像它在浏览器 Google Chrome 中工作一样。

我认为我需要在 webview 中注入和执行 JavaScript,以便检测页面上任何滚动控件上的滚动事件。并将其传递给 android 本机代码。我知道如何将它传递给本机代码。

但是我怎样才能在 javascript 中检测到这个滚动事件呢?

【问题讨论】:

    标签: javascript android webview swiperefreshlayout


    【解决方案1】:

    您可以通过定义扩展 WebView 的自定义 WebView 类并覆盖方法 onOverScrolleddoc here 来检测 WebView 的内容是否正在滚动。

    从禁用 SwipeRefreshLayout 开始,并在 WebView 中的内容不滚动时启用它。然后,您可以通过捕获 WebView 上发生的触摸 DOWN 事件来检测滚动手势开始。

    public class MyWebView extends WebView{
        @Override
        protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
            super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
    
            if( clampedX || clampedY ){
                //Content is not scrolling
                //Enable SwipeRefreshLayout
                ViewParent parent = this.getParent();
                if ( parent instanceof SwipeRefreshLayout) {
                    ((SwipeRefreshLayout)parent).setEnabled(true);
                }
            }
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            super.onTouchEvent(event);
    
            if(event.getActionMasked()==MotionEvent.ACTION_DOWN){
                //Disable SwipeRefreshLayout
                ViewParent parent = this.getParent();
                if ( parent instanceof SwipeRefreshLayout) {
                    ((SwipeRefreshLayout)parent).setEnabled(false);
                }
            }
            return true;
        }
    }
    

    【讨论】:

    • webview 的内容滚动完美。我想检测网页上任何元素的滚动,但不是所有 webview 的内容。例如在页面上除了其他布局地图元素。我可以滚动它。但是在添加 SwipeRefreshLayout 时,会拦截地图元素的滚动。
    • 是的,我正在使用上述解决方案处理类似的情况。你试过了吗?根据我的经验,在您滚动地图之前,您不会收到带有clampedX/Y true 的 onOverScrolled 事件,因此如果 SwipeRefreshLayout 被禁用,它不应该受到影响。反之亦然,当您将页面滚动到边缘时,会收到带有 clampedX/Y 为 true 的 onOverScrolled 事件,您可以启用 SwipeRefreshLayout 来处理刷新手势情况。
    • 它不工作。如果事件子序列被传递给 webview,父容器(SwipeRefreshLayout)不能做刷新工作,直到拉动手势(ACTION_UP)的最后一个事件将被 webview 处理。
    • 这对我有用,只需要在每个方法中调用super
    【解决方案2】:

    webview 的内容滚动完美。我想检测网页上任何元素的滚动,但不是所有 webview 的内容。例如在页面上除了其他布局地图元素。我可以滚动它。但是在添加 SwipeRefreshLayout 时,会拦截地图元素的滚动。

    根据您的评论,我想我已经处理了类似的问题。这是我在 webview 中检测元素滚动的解决方案。

    简而言之,

    该解决方案在 webview 完成加载后将 onTouchStart 侦听器添加到 webview 以记录网页中元素或其祖先的有效可滚动触摸,然后将触摸的必要信息发送回您的本机代码以进行进一步的操作。

    具体来说,

    首先,我们需要开启webview的JavaScript,并为webview添加一个javascript接口来接收触摸信息。在 Kotlin 中会是这样的:

    webview.settings.javaScriptEnabled = true
    webview.addJavascriptInterface(scrollableWebViewJSInterfaceObject, "scrollableWebViewJSInterface") // We name the interface as scrollableWebViewJSInterface 
    

    而在这个scrollableWebViewJSInterfaceObject中,我们需要像这样实现两个函数作为javascript接口回调:

    @JavascriptInterface
    @Suppress("UNUSED") // Used by javascript callback
    fun touchOnScrollableWebElement(
        scrollLeft: Int,
        scrollTop: Int,
        scrollLeftMax: Int,
        scrollTopMax: Int
    ) {
        // May check whether the element can scroll according to its scrolling direction.
        // And if it can scroll, you should swallow the touch here and disallow your SwipeRefreshLayout to scroll. 
        // Otherwise, let SwipeRefreshLayout scroll. 
    }
    
    @JavascriptInterface
    @Suppress("UNUSED") // Used by javascript callback
    fun touchOnNotScrollableWebElement() {
        // If a touch in the web view does not on any scrollable element.
        // Just let SwipeRefreshLayout do whatever it wants.
    }
    

    如果 Web 视图中的触摸是在可滚动元素上,则调用 touchOnScrollableWebElement() 进行进一步检查。否则,将调用touchOnNotScrollableWebElement()

    其次,我们应该在webview加载完页面后在JS中添加触摸监听器。

    override fun onPageFinished(view: WebView, url: String) {
        super.onPageFinished(view, url)
        registerWebElementsTouchCallback(view)
    }
    
    private fun registerWebElementsTouchCallback(view: WebView) {
        // Add a "touchstart" listener to watch all touched elements.
        // If a touch down on an element, we check whether the element is scrollable or anyone of
        // its ancestors is scrollable in order to let the webview's scrollable container know whether it can scroll.
        
        // The elements rendered in the web view are not immediate descendant views of the web view
        // and cannot be accessed directly in android codes. As a result, to determine whether they
        // are scrollable and can be scrolling at the time, we have to add javascript interface and
        // execute javascript codes here.
        val javascript = "function onTouchStart(event) { \n" +
            "if (event.targetTouches.length >= 1) {  \n" +
            "var touch = event.targetTouches[0]; \n" +
            "if (touch.target) { \n" +
            "var node = touch.target; \n" +
            "while(node) { \n" +
            "var scrollLeftMax = node.scrollWidth - node.clientWidth; \n" +
            "var scrollTopMax = node.scrollHeight - node.clientHeight; \n" +
            "if (scrollLeftMax > 0 || scrollTopMax > 0) { \n" +
            "window.scrollableWebViewJSInterface.touchOnScrollableWebElement(node.scrollLeft, " +
            "node.scrollTop, scrollLeftMax, scrollTopMax); \n" +
            "break; \n" +
            "} \n" +
            "node = node.parentNode; \n" +
            "} \n" +
            "if (!node) { \n" +
            "window.scrollableWebViewJSInterface.touchOnNotScrollableWebElement(); \n" +
            "} \n" +
            "} \n" +
            "} \n" +
            "} \n" +
            "window.addEventListener('touchstart', onTouchStart, true);\n"
        view.evaluateJavascript(javascript, null)
    }
    

    可以看到,JS代码通过scrollWidth和clientWidth判断元素是否可以滚动,然后调用相应的JS接口函数。有关这些变量的更多信息可以在文档中找到。我觉得除了字符串格式,代码都很简单。

    最后,如果应用正确,此解决方案应该适用于您的情况。

    希望这可以帮助遇到类似问题的人。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-09-02
      • 2020-08-26
      • 2017-07-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多