Webview&Viewpager滑動沖突解決方案

參考的文章

感謝這些作者的分享

http://m.itdecent.cn/p/24038d957e93
https://developer.android.com/jetpack/androidx/releases/swiperefreshlayout
https://wangyeming.github.io/2017/07/16/use-webview-in-viewpager/

在使用viewpager時,如果某一頁存在webview,則會出現(xiàn)webview中輪播圖或者其他滑動控件,無法滑動的問題。這種情況是可以僅通過app端就解決的。

解決問題的思路

1、如何控制使用webview處理事件還是viewpager處理事件。
2、根據(jù)什么來判斷webview處理事件還是viewpager處理事件。

問題一:如何控制

所有的滑動沖突問題解決的思路就是兩個:

  • 內(nèi)部攔截法
  • 外部攔截法

在這個場景中,在webview外面,可能還包了fragment等眾多viewgroup,并且最終判斷誰處理事件的依據(jù)在webview中,所以這里使用內(nèi)部攔截法更方便。

public class ScrollConflictWebView extends WebView {

    private boolean 判斷依據(jù) = false;

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

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    判斷依據(jù) = false;
                                        //Down時攔截事件保證在move時可以拿到事件。
                    requestDisallowInterceptTouchEvent(true);
                    break;
                case MotionEvent.ACTION_MOVE:
                    //Move時決定是否還要攔截事件
                    requestDisallowInterceptTouchEvent(!判斷依據(jù));
                    break;
                default:
                    requestDisallowInterceptTouchEvent(false);
                                        break;
            }
             return super.onTouchEvent(event);
    }

}

可能你在測試時,會出現(xiàn)無法攔截的情況,我也遇到了,因為使用了SwipeRefreshLayout。下文會說到。

問題2:如何判斷

我找到了兩種思路,第一種

  • 通過js交互實現(xiàn),h5告訴客戶端什么情況下是可以滑動的,或者什么位置是可以滑動的。
  • 通過webview自身的回調(diào):onOverScrolled來判斷。參考了這個文章

顯然,第一種方法是非常麻煩的,涉及到js交互,不具備通用性。因為測試了uc和夸克,發(fā)現(xiàn)在他們的瀏覽器中,都自動解決了滑動沖突,所以必然是有其他可以判斷的依據(jù)的,最終找到了第二種方法。

    @Override
    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
        super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
        isScrollX = clampedX;
    }

這個方法觸發(fā)的時機是webview滑動到邊界時會觸發(fā),如果是橫向滑動,則clamped則為true。這樣的話,我們只要在clamped為true的時候,把事件交給viewpager來處理就行了。

public class ScrollConflictWebView extends WebView {

    private boolean 判斷依據(jù) = false;

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

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    判斷依據(jù) = false;
                                        //Down時攔截事件保證在move時可以拿到事件。
                    requestDisallowInterceptTouchEvent(true);
                    break;
                case MotionEvent.ACTION_MOVE:
                    //Move時決定是否還要攔截事件
                    requestDisallowInterceptTouchEvent(!判斷依據(jù));
                    break;
                default:
                    requestDisallowInterceptTouchEvent(false);
                                        break;
            }
             return super.onTouchEvent(event);
    }

    @Override
    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
        super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
        判斷依據(jù) = clampedX;
    }

}

上面說到了,如果你Webview的父布局中存在SwipeRefreshLayout,會發(fā)現(xiàn),可能disallow無法傳遞到viewpager。理論上,不做任何處理,viewgroup的disallow方法,會挨個往父布局傳遞。但是為什么會傳遞失敗呢?這就得看下SwipeRefreshLayout的源碼(appcompat版本:1.2.0-alpha03)了:

@Override
public void requestDisallowInterceptTouchEvent(boolean b) {
    // if this is a List < L or another view that doesn't support nested
    // scrolling, ignore this request so that the vertical scroll event
    // isn't stolen
    if ((android.os.Build.VERSION.SDK_INT < 21 && mTarget instanceof AbsListView)
            || (mTarget != null && !ViewCompat.isNestedScrollingEnabled(mTarget))) {
        // Nope.
    } else {
        super.requestDisallowInterceptTouchEvent(b);
    }
}

從源碼可以看出,如果SwipeRefreshLayout包裹的布局不支持NestedScroll的話,就不做任何處理。
其實這個是appcompat在1.2.0中才修改的,1.1.0版本是給了方法自己控制的:


image.png

一開始的思路是讓SwipeRefreshLayout下面一層View支持nestedScroll,但是這樣的話,會導致下拉刷新無法觸發(fā)。
那就只能手動去修改disallow方法了:

public class AllowSwipeRefreshLayout extends QFSwipeRefreshLayout {
    public AllowSwipeRefreshLayout(@NonNull @NotNull Context context) {
        super(context, null);
    }

    public AllowSwipeRefreshLayout(@NonNull @NotNull Context context, @Nullable @org.jetbrains.annotations.Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void requestDisallowInterceptTouchEvent(boolean b) {
        getParent().requestDisallowInterceptTouchEvent(b);
        super.requestDisallowInterceptTouchEvent(b);
    }
}

這樣修改以后,disallow就可以正常傳遞給viewpager了。到這里,就完美的解決了webview嵌套在Viewpager中的滑動沖突問題。
最終實現(xiàn)效果與uc和夸克一致。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容