scwang90 / SmartRefreshLayout

🔥下拉刷新、上拉加载、二级刷新、淘宝二楼、RefreshLayout、OverScroll,Android智能下拉刷新框架,支持越界回弹、越界拖动,具有极强的扩展性,集成了几十种炫酷的Header和 Footer。
https://segmentfault.com/a/1190000010066071
Apache License 2.0
24.8k stars 4.93k forks source link

WebView嵌套滑动冲突终极解决方案 #1287

Open michaellee123 opened 3 years ago

michaellee123 commented 3 years ago

在WebView下拉刷新的时候,如果html中还有自己的下拉,那在SmartRefreshLayout中就会出现各种问题。我解决了这个问题,给大家分享一下解决方案。 首先自定义WebView,代码如下:

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.webkit.WebView;

public class MyWebView extends WebView {

    public interface RefreshStateListener {
        public void refreshState(boolean canRefresh);
    }

    private RefreshStateListener refreshStateListener;

    public void setRefreshStateListener(RefreshStateListener refreshStateListener) {
        this.refreshStateListener = refreshStateListener;
    }

    public MyWebView(Context context) {
        super(context);
    }

    public MyWebView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyWebView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public MyWebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
        super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
        if (refreshStateListener != null) {
            refreshStateListener.refreshState(clampedY);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (refreshStateListener != null && event.getAction() == MotionEvent.ACTION_DOWN) {
            refreshStateListener.refreshState(false);
        }
        return super.onTouchEvent(event);
    }

}

使用非常简单,只需要在你的WebView那里加上一句代码即可。

webView.setRefreshStateListener {
    refreshLayout.setEnableRefresh(it)
}

目前没有发现任何问题,可以尝试使用百度首页测试。

huaxiaolin commented 3 years ago

腾讯webview这样也不行,有没有解决腾讯webview这种问题的

michaellee123 commented 3 years ago

腾讯webview这样也不行,有没有解决腾讯webview这种问题的

你是说X5内核那个东西吗?

huaxiaolin commented 3 years ago

腾讯webview这样也不行,有没有解决腾讯webview这种问题的

你是说X5内核那个东西吗?

是的,X5的这个不知道怎么实现,使用上述方法并不行,但是我看微信和QQ上应该是实现了,说明应该是有方法的,所以看你知道这个方法不?

michaellee123 commented 3 years ago

腾讯webview这样也不行,有没有解决腾讯webview这种问题的

你是说X5内核那个东西吗?

是的,X5的这个不知道怎么实现,使用上述方法并不行,但是我看微信和QQ上应该是实现了,说明应该是有方法的,所以看你知道这个方法不?

我试试吧,我还没用过x5

michaellee123 commented 3 years ago

腾讯webview这样也不行,有没有解决腾讯webview这种问题的

你是说X5内核那个东西吗?

是的,X5的这个不知道怎么实现,使用上述方法并不行,但是我看微信和QQ上应该是实现了,说明应该是有方法的,所以看你知道这个方法不?

你看看这个代码,我刚才试了,是可以的


import android.os.Bundle
import android.view.MotionEvent
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.scwang.smart.refresh.header.MaterialHeader
import com.scwang.smart.refresh.layout.SmartRefreshLayout
import com.tencent.smtt.export.external.TbsCoreSettings
import com.tencent.smtt.export.external.extension.proxy.ProxyWebViewClientExtension
import com.tencent.smtt.sdk.QbSdk
import com.tencent.smtt.sdk.WebView
import com.tencent.smtt.sdk.WebViewCallbackClient
import com.tencent.smtt.sdk.WebViewClient

class MainActivity : AppCompatActivity() {

    lateinit var webView: WebView
    lateinit var refreshLayout: SmartRefreshLayout
    val callbackClient = CallbackClient()
    val clientExtension = ClientExtension()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initX5()
        webView = findViewById(R.id.webview)
        refreshLayout = findViewById(R.id.refreshLayout)
        refreshLayout.setRefreshHeader(MaterialHeader(this))
        webView.settings.javaScriptEnabled = true
        webView.settings.domStorageEnabled = true
        webView.webViewClient = object : WebViewClient() {
            override fun shouldOverrideUrlLoading(p0: WebView?, p1: String): Boolean {
                p0?.loadUrl(p1)
                return true
            }

            override fun onPageFinished(p0: WebView?, p1: String?) {
                super.onPageFinished(p0, p1)
                refreshLayout.finishRefresh()
            }
        }
        webView.setWebViewCallbackClient(callbackClient)
        webView.webViewClientExtension = clientExtension
        refreshLayout.setEnableRefresh(false)
        refreshLayout.setOnRefreshListener {
            webView.reload()
        }
        webView.loadUrl("https://www.baidu.com")
    }

    fun initX5() {
        val map = HashMap<String, Any>()
        map[TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER] = true
        map[TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE] = true
        QbSdk.initTbsSettings(map)
    }

    inner class CallbackClient : WebViewCallbackClient {
        override fun invalidate() {}
        override fun onTouchEvent(event: MotionEvent, view: View): Boolean {
            return webView.super_onTouchEvent(event)
        }

        override fun overScrollBy(
            deltaX: Int, deltaY: Int, scrollX: Int,
            scrollY: Int, scrollRangeX: Int, scrollRangeY: Int,
            maxOverScrollX: Int, maxOverScrollY: Int,
            isTouchEvent: Boolean, view: View
        ): Boolean {
            return webView.super_overScrollBy(
                deltaX, deltaY, scrollX, scrollY,
                scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY,
                isTouchEvent
            )
        }

        override fun computeScroll(view: View) {
            webView.super_computeScroll()
        }

        override fun onOverScrolled(
            scrollX: Int, scrollY: Int, clampedX: Boolean,
            clampedY: Boolean, view: View
        ) {
            webView.super_onOverScrolled(scrollX, scrollY, clampedX, clampedY)
        }

        override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int, view: View) {
            webView.super_onScrollChanged(l, t, oldl, oldt)
        }

        override fun dispatchTouchEvent(ev: MotionEvent, view: View): Boolean {
            return webView.super_dispatchTouchEvent(ev)
        }

        override fun onInterceptTouchEvent(ev: MotionEvent, view: View): Boolean {
            return webView.super_onInterceptTouchEvent(ev)
        }
    }

    inner class ClientExtension : ProxyWebViewClientExtension() {

        override fun onTouchEvent(event: MotionEvent, view: View): Boolean {
            if (event.action == MotionEvent.ACTION_DOWN) {
                refreshLayout.setEnableRefresh(false)
            }
            return callbackClient.onTouchEvent(event, view)
        }

        override fun onOverScrolled(
            scrollX: Int, scrollY: Int, clampedX: Boolean,
            clampedY: Boolean, view: View
        ) {
            callbackClient.onOverScrolled(scrollX, scrollY, clampedX, clampedY, view)
            refreshLayout.setEnableRefresh(clampedY)
        }

        //主要代码在上面这两个方法里面,但是下面的方法也不能去掉,必须保留

        override fun onInterceptTouchEvent(ev: MotionEvent, view: View): Boolean {
            return callbackClient.onInterceptTouchEvent(ev, view)
        }

        override fun dispatchTouchEvent(ev: MotionEvent, view: View): Boolean {
            return callbackClient.dispatchTouchEvent(ev, view)
        }

        override fun overScrollBy(
            deltaX: Int, deltaY: Int, scrollX: Int, scrollY: Int,
            scrollRangeX: Int, scrollRangeY: Int,
            maxOverScrollX: Int, maxOverScrollY: Int,
            isTouchEvent: Boolean, view: View
        ): Boolean {
            return callbackClient.overScrollBy(
                deltaX, deltaY, scrollX, scrollY,
                scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent, view
            )
        }

        override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int, view: View) {
            callbackClient.onScrollChanged(l, t, oldl, oldt, view)
        }

        override fun computeScroll(view: View) {
            callbackClient.computeScroll(view)
        }

    }

}
huaxiaolin commented 3 years ago

腾讯webview这样也不行,有没有解决腾讯webview这种问题的

你是说X5内核那个东西吗?

是的,X5的这个不知道怎么实现,使用上述方法并不行,但是我看微信和QQ上应该是实现了,说明应该是有方法的,所以看你知道这个方法不?

你看看这个代码,我刚才试了,是可以的

import android.os.Bundle
import android.view.MotionEvent
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.scwang.smart.refresh.header.MaterialHeader
import com.scwang.smart.refresh.layout.SmartRefreshLayout
import com.tencent.smtt.export.external.TbsCoreSettings
import com.tencent.smtt.export.external.extension.proxy.ProxyWebViewClientExtension
import com.tencent.smtt.sdk.QbSdk
import com.tencent.smtt.sdk.WebView
import com.tencent.smtt.sdk.WebViewCallbackClient
import com.tencent.smtt.sdk.WebViewClient

class MainActivity : AppCompatActivity() {

    lateinit var webView: WebView
    lateinit var refreshLayout: SmartRefreshLayout
    val callbackClient = CallbackClient()
    val clientExtension = ClientExtension()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initX5()
        webView = findViewById(R.id.webview)
        refreshLayout = findViewById(R.id.refreshLayout)
        refreshLayout.setRefreshHeader(MaterialHeader(this))
        webView.settings.javaScriptEnabled = true
        webView.settings.domStorageEnabled = true
        webView.webViewClient = object : WebViewClient() {
            override fun shouldOverrideUrlLoading(p0: WebView?, p1: String): Boolean {
                p0?.loadUrl(p1)
                return true
            }

            override fun onPageFinished(p0: WebView?, p1: String?) {
                super.onPageFinished(p0, p1)
                refreshLayout.finishRefresh()
            }
        }
        webView.setWebViewCallbackClient(callbackClient)
        webView.webViewClientExtension = clientExtension
        refreshLayout.setEnableRefresh(false)
        refreshLayout.setOnRefreshListener {
            webView.reload()
        }
        webView.loadUrl("https://www.baidu.com")
    }

    fun initX5() {
        val map = HashMap<String, Any>()
        map[TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER] = true
        map[TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE] = true
        QbSdk.initTbsSettings(map)
    }

    inner class CallbackClient : WebViewCallbackClient {
        override fun invalidate() {}
        override fun onTouchEvent(event: MotionEvent, view: View): Boolean {
            return webView.super_onTouchEvent(event)
        }

        override fun overScrollBy(
            deltaX: Int, deltaY: Int, scrollX: Int,
            scrollY: Int, scrollRangeX: Int, scrollRangeY: Int,
            maxOverScrollX: Int, maxOverScrollY: Int,
            isTouchEvent: Boolean, view: View
        ): Boolean {
            return webView.super_overScrollBy(
                deltaX, deltaY, scrollX, scrollY,
                scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY,
                isTouchEvent
            )
        }

        override fun computeScroll(view: View) {
            webView.super_computeScroll()
        }

        override fun onOverScrolled(
            scrollX: Int, scrollY: Int, clampedX: Boolean,
            clampedY: Boolean, view: View
        ) {
            webView.super_onOverScrolled(scrollX, scrollY, clampedX, clampedY)
        }

        override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int, view: View) {
            webView.super_onScrollChanged(l, t, oldl, oldt)
        }

        override fun dispatchTouchEvent(ev: MotionEvent, view: View): Boolean {
            return webView.super_dispatchTouchEvent(ev)
        }

        override fun onInterceptTouchEvent(ev: MotionEvent, view: View): Boolean {
            return webView.super_onInterceptTouchEvent(ev)
        }
    }

    inner class ClientExtension : ProxyWebViewClientExtension() {

        override fun onTouchEvent(event: MotionEvent, view: View): Boolean {
            if (event.action == MotionEvent.ACTION_DOWN) {
                refreshLayout.setEnableRefresh(false)
            }
            return callbackClient.onTouchEvent(event, view)
        }

        override fun onOverScrolled(
            scrollX: Int, scrollY: Int, clampedX: Boolean,
            clampedY: Boolean, view: View
        ) {
            callbackClient.onOverScrolled(scrollX, scrollY, clampedX, clampedY, view)
            refreshLayout.setEnableRefresh(clampedY)
        }

        //主要代码在上面这两个方法里面,但是下面的方法也不能去掉,必须保留

        override fun onInterceptTouchEvent(ev: MotionEvent, view: View): Boolean {
            return callbackClient.onInterceptTouchEvent(ev, view)
        }

        override fun dispatchTouchEvent(ev: MotionEvent, view: View): Boolean {
            return callbackClient.dispatchTouchEvent(ev, view)
        }

        override fun overScrollBy(
            deltaX: Int, deltaY: Int, scrollX: Int, scrollY: Int,
            scrollRangeX: Int, scrollRangeY: Int,
            maxOverScrollX: Int, maxOverScrollY: Int,
            isTouchEvent: Boolean, view: View
        ): Boolean {
            return callbackClient.overScrollBy(
                deltaX, deltaY, scrollX, scrollY,
                scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent, view
            )
        }

        override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int, view: View) {
            callbackClient.onScrollChanged(l, t, oldl, oldt, view)
        }

        override fun computeScroll(view: View) {
            callbackClient.computeScroll(view)
        }

    }

}

这种方法可以,但是还是存在X5内核未加载成功的情况下失效的问题,因为X5内核的加载在首次安装的时候,多数情况下是失败的,在这种情况下这种方法就有失效了,要不加个QQ吧 ,578469246 。

michaellee123 commented 3 years ago

@huaxiaolin 没加载成功的就自己看X5的文档了吧,我这个方法其实就是在over scroll的时候去给它设置成可以下拉,一共就两句代码有用,只是需要找一下相应的设置方法就行了。

h-zhouwenjun commented 2 years ago

网页里面弹窗滚动冲突怎么处理

michaellee123 commented 2 years ago

你是说弹窗里面用了scroll之类的控件那种吗?

594238813 commented 1 year ago

为什么我 下拉刷新 ,X5 reload() 之后

onProgressChanged 的 newProgress 进度 一直在 70
shouldOverrideUrlLoading(..) 就不回调了
唉~