參考的文章
感謝這些作者的分享
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版本是給了方法自己控制的:
一開始的思路是讓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和夸克一致。