处理多层ViewPage嵌套产生滑动冲突的问题
场景
在项目中我们经常会遇到一下类似的嵌套效果,ViewPager从主页可以一直右滑经过每个页面。
1 2 3 4 5 6 7 8 9
| graph LR A(MainActivity: BottomNavigationView + ViewPager)-->B1(主页 : TabLayout + ViewPager) A-->B2(发现 : TabLayout + ViewPager) A-->B3(我的 : 普通布局) B1-->C1(tab1 : TabLayout + ViewPager)-->RecycleView1-->items1 B1-->C2(tab1 : TabLayout + ViewPager)-->RecycleView2-->items2 B1-->C3(tab1 : TabLayout + ViewPager)-->RecycleView3-->items3 B2-->D1(tab1....) B3-->E1(内容)
|
效果图
实际开发的时候,我们往往第一时间想到嵌套以上类似布局直接实现想要的效果。但总是会出现莫名奇妙的闪退效果,抛数组越界异常,其实问题就出现在嵌套的ViewPager左右滑动时,子Viewpager划到最右一页继续滑动时,Viewpager并不会吧触摸事件交给父ViewPager响应,而此时的子ViewPager已经到了末尾一页,继续右滑调用下一页当然会抛越界异常。
解决
其实这个问题很好解决,核心思想就是重写子ViewPager的触摸监听事件,当子ViewPager在首页向左滑动或末尾页向右滑动时,触摸响应交给父ViewPager来处理。
将原本的子ViewPager改为自定义的 ChildViewPager.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
|
public class ChildViewPager extends ViewPager {
int startX; int startY;
public ChildViewPager(@NonNull Context context) { super(context); }
public ChildViewPager(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); }
@Override public boolean dispatchTouchEvent(MotionEvent ev) { int currentPosition; int count = Objects.requireNonNull(getAdapter()).getCount();
switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: startX = (int) ev.getRawX(); getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: int endX = (int) ev.getRawX(); int disX = endX - startX;
currentPosition = this.getCurrentItem();
if (currentPosition == count - 1 && disX < 0) { getParent().requestDisallowInterceptTouchEvent(false); } else if (currentPosition == 0 && disX > 0) { getParent().requestDisallowInterceptTouchEvent(false); }
else { getParent().requestDisallowInterceptTouchEvent(true); } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: getParent().requestDisallowInterceptTouchEvent(false); break; }
return super.dispatchTouchEvent(ev); } }
|
其实核心想法就是,子ViewPager
在需要的时候让 父Viewpager
拦截触摸事件
关键语句:
1
| getParent().requestDisallowInterceptTouchEvent(Boolean);
|
使用
使用方式就按上述简单的思维导图来嵌套就OK了
最外层 activity_main.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".main.MainActivity"> <androidx.viewpager.widget.ViewPager android:id="@+id/viewpager" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_above="@+id/bottom_nav"/>
<com.google.android.material.bottomnavigation.BottomNavigationView android:id="@+id/bottom_nav" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:background="#fcfcfc" app:itemIconTint="@color/bottom_nav_color_selector" app:itemTextColor="@color/bottom_nav_color_selector" app:menu="@menu/nav_tab_menu"/> </RelativeLayout>
|
第一层ViewPager fragement_home.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:context="XXX.XXX.XXX">
<com.google.android.material.appbar.AppBarLayout android:id="@+id/appBar" android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_scrollFlags="enterAlways|scroll" app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
<com.google.android.material.tabs.TabLayout android:id="@+id/tabs" android:layout_width="match_parent" android:layout_height="wrap_content" app:tabIndicatorColor="@color/colorPrimary" app:tabMode="scrollable"/> </com.google.android.material.appbar.AppBarLayout>
<com.liuguoping.pretty_gank.gank.view.ChildViewPager android:id="@+id/viewpager" android:layout_width="match_parent" android:layout_height="match_parent" android:focusable="true" app:layout_behavior="@string/appbar_scrolling_view_behavior" android:focusableInTouchMode="true"/> </androidx.coordinatorlayout.widget.CoordinatorLayout>
|
第二层ViewPager fragment_home_tab.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="XXX.XXX.XXX">
<com.google.android.material.tabs.TabLayout android:id="@+id/tabs_child" android:layout_width="match_parent" android:layout_height="wrap_content" app:tabIndicatorColor="@color/colorPrimary" app:tabMode="scrollable" />
<com.liuguoping.pretty_gank.gank.view.ChildViewPager android:id="@+id/viewpager_child" android:layout_width="match_parent" android:layout_height="match_parent" android:focusable="true" android:focusableInTouchMode="true" /> </LinearLayout>
|
继续嵌套最里层的 RecycleView
所在的 Fragment
fragment_home_tab_list.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
<com.scwang.smartrefresh.layout.SmartRefreshLayout android:id="@+id/smartRefresh" android:layout_width="match_parent" android:layout_height="match_parent"> <com.scwang.smartrefresh.layout.header.ClassicsHeader android:layout_width="match_parent" android:layout_height="wrap_content"/> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_gank_list" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="4dp" /> <com.scwang.smartrefresh.layout.footer.ClassicsFooter android:layout_width="match_parent" android:layout_height="wrap_content"/> </com.scwang.smartrefresh.layout.SmartRefreshLayout>
</LinearLayout>
|
总结
将控制 父ViewPager
触摸事件的拦截与否交给 子ViewPager
的好处是这样可以灵活处理当前的滑动触摸事件,不用担心下一层是否继续嵌套了ViewPager。