解决BottomSheetDialogFragment中多个fragment滑动冲突

2022-07-26 13:57:29

提出问题

前段时间工作上遇到个UI需求,需要在BottomSheetDialogFragment中嵌套多个fragment,且每个fragment都有个列表需要滑动,但是出现了个问题,弹窗在滑动的时候和列表滑动的时候出现冲突,在外部弹窗完全展开的时候,内部fragment列表无法进行滑动

思考了下,总结分析了需要解决的问题

  • 如何保证在弹窗随手势完全展开的时候,列表继续滑动;在收缩的时候,列表滑动回到顶部后,再收缩弹窗?
  • 因为Fragment中的RecyclerView接收不到滑动事件,导致无法滑动么?

源码分析

然后我们带着问题去分析,同时在网上搜索了大量相关的问题解答,发现基本都是因为BottomSheetBehavior的ViewPager嵌套RecyclerView导致滑动失效,可以举一反三,其实本质的问题都是类似的

因为BottomSheetDialogFragment手势滑动是通过BottomSheetBehavior来实现,通过启发我们去看了下BottomSheetBehavior的源码,它的代码量不

首先看到nestedScrollingChildRef,它是从绑定了BottomSheetBehavior的child中找可以嵌套滑动的控件

nestedScrollingChildRef = new WeakReference<>(findScrollingChild(child));

然后看下findScrollingChild方法,发现它只支持内部有一个可以上下滑动的控件,多个的话就取第一个

 @Nullable
  @VisibleForTesting
  View findScrollingChild(View view) {
    if (ViewCompat.isNestedScrollingEnabled(view)) {
      return view;
    }
    if (view instanceof ViewGroup) {
      ViewGroup group = (ViewGroup) view;
      for (int i = 0, count = group.getChildCount(); i < count; i++) {
        View scrollingChild = findScrollingChild(group.getChildAt(i));
        if (scrollingChild != null) {
          return scrollingChild;
        }
      }
    }
    return null;
  }

而且mNestedScrollingChildRef在处理touch事件的时候会用到,那么我们只要修改为在fragment切换的时候重新设置下这个mNestedScrollingChildRef就可以了,乍一想,好像问题变得简单起来了

解决方案

结合网上大部分的解决方案,我们都需要去重新修改BottomSheetBehavior,因为原来的behavior已经不适用这种情况

这里看到很久之前的git就有人提交过Viewpager导致BottomSheet滑动冲突的问题,所幸就在大佬提供的BottomSheetBehavior基础上去修改,有点小遗憾的是这里的版本有点陈旧,后期需要优化下,保持和现版本的功能大致形同。

其实改动很简单,大致就是如下两点

  • 在上面说的findScrollingChild方法中,找到viewpager拿到当前的子child
  • 然后在viewpager切换页面的时候重新设置下mNestedScrollingChildRef即可
 @VisibleForTesting
    View findScrollingChild(View view) {

        if (ViewCompat.isNestedScrollingEnabled(view)) {
            return view;
        }
        if (view instanceof ViewPager) {
            ViewPager viewPager = (ViewPager) view;
            View currentViewPagerChild = ViewPagerUtils.getCurrentView(viewPager);
            if (currentViewPagerChild == null) {
                return null;
            }

            View scrollingChild = findScrollingChild(currentViewPagerChild);
            if (scrollingChild != null) {
                return scrollingChild;
            }
        } else if (view instanceof ViewGroup) {
            ViewGroup group = (ViewGroup) view;
            for (int i = 0, count = group.getChildCount(); i < count; i++) {
                View scrollingChild = findScrollingChild(group.getChildAt(i));
                if (scrollingChild != null) {
                    return scrollingChild;
                }
            }
        }
        return null;
    }
....
    
mNestedScrollingChildRef = new WeakReference<>(findScrollingChild(child));

看到这里,问题基本就迎刃而解了,其实我只要在切换fragment的时候重新设置mNesetdScrollingChildRef,让它去改变需要滑动的子view,就稍微做了以下改动

首先写了个方法去重新刷新mNesetdScrollingChildRef

 public void invalidateScrollingChild(View scrollingChildView) {
        mNestedScrollingChildRef = new WeakReference<>(scrollingChildView);
    }

然后我另外写了个接口去返回当前可滑动的子view,可以是RecyclerView,ListView等等

interface CallBackScrollChild {
    fun backScrollChild(scrollChild: View)
}

在fragment的onVisible可见方法中调用

 override fun onVisible() {
        super.onVisible()
        binding?.rvFirst?.let { callBackScrollChild?.backScrollChild(it) }
    }

需要在bottomSheetFragment中调用当前behavior中的invalidateScrollingChild方法

 override fun backScrollChild(scrollChild: View) {
        behavior?.invalidateScrollingChild(scrollChild)
    }

可以看下最终效果

test.gif

  • 作者:yihanss
  • 原文链接:https://blog.csdn.net/yihanss/article/details/124887772
    更新时间:2022-07-26 13:57:29