【日常小问题】解决BottomSheetDialogFragment中多个fragment滑动冲突

提出问题

前段时间工作上遇到个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

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值