BottomSheetDialogFragment使用时经常出现嵌套多个RecycleView以及ViewPager2

源码分析

BottomSheetBehavior 会在布局时 获取第一个可嵌套滚动的view,并且后续都不会在更换。

public class BottomSheetBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {
   @Nullable WeakReference<View> nestedScrollingChildRef;

  @Override
  public boolean onLayoutChild(
      @NonNull CoordinatorLayout parent, @NonNull final V child, int layoutDirection) {
   
	//....
    nestedScrollingChildRef = new WeakReference<>(findScrollingChild(child));
    return true;
  }
}

这个字段会在触摸事件拦截使用,如果当前触摸区域不在嵌套滚动范围 会直接拦截事件,导致其他滚动组件无法正常使用。

public class BottomSheetBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {
    WeakReference<View> nestedScrollingChildRef;
	 
  public boolean onInterceptTouchEvent(
       CoordinatorLayout parent,  V child,  MotionEvent event) {
   
    View scroll = nestedScrollingChildRef != null ? nestedScrollingChildRef.get() : null;
    return action == MotionEvent.ACTION_MOVE
        && scroll != null
        && !ignoreEvents
        && state != STATE_DRAGGING
        //问题所在isPointInChildBounds
        && !parent.isPointInChildBounds(scroll, (int) event.getX(), (int) event.getY())
        && viewDragHelper != null
        && Math.abs(initialY - event.getY()) > viewDragHelper.getTouchSlop();
  }
}

临时解决方案:
对于简单的布局可以禁用横向滚动的嵌套滚动开关,如ViewPager2中的recyclerview

  // binding.vp是ViewPager2
  binding.vp.children.find { it is RecyclerView }?.let {
            (it as RecyclerView).isNestedScrollingEnabled = false
   }

但对于多个垂直滚动的Recyclerview无法解决,当然你可以不断动态调用isNestedScrollingEnabled,然后重新执行布局,但是效率过差。

最好的解决方案:
我们希望在触发嵌套滚动时BottomSheetBehavior可以动态切换nestedScrollingChildRef,所以笔者直接拷贝了源码然后再 触发嵌套滚动时切换nestedScrollingChildRef.,同事需要在事件拦截的地方判断按下坐标是否切换到了其他嵌套滚动view

public class BottomSheetBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {
    WeakReference<View> nestedScrollingChildRef;
	 
 @Override
    public boolean onStartNestedScroll(
         CoordinatorLayout coordinatorLayout,V child,View directTargetChild,View target,int axes,int type) {
        lastNestedScrollDy = 0;
        nestedScrolled = false;
        boolean ret = (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
        if (ret) {
        	//切换
            nestedScrollingChildRef = new WeakReference<>(target);
        }
        return ret;
    }
 
  }

public class TiyaBottomSheetBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {

 private boolean isVp2Recycler(View view) {
        if (view instanceof RecyclerView) {
            RecyclerView rv = (RecyclerView) view;
            RecyclerView.LayoutManager layoutManager = rv.getLayoutManager();
            if (rv.getParent() instanceof ViewPager2) {
                return true;
            }
        }
        return false;
    }
  @Nullable
    @VisibleForTesting
    View findScrollingChild(CoordinatorLayout parent,View view,int x,int y) {
        if (ViewCompat.isNestedScrollingEnabled(view) ) {
            //如果当前是启用过滤vp2情况
            if (isFilterVp2()&&isVp2Recycler(view)) {

            }else {
                if (parent.isPointInChildBounds(view,x,y)) {
                    return view;
                }
            }
        }
        if (view instanceof ViewGroup) {
            ViewGroup group = (ViewGroup) view;
            for (int i = 0, count = group.getChildCount(); i < count; i++) {
                View scrollingChild = findScrollingChild(parent,group.getChildAt(i),x,y);
                if (scrollingChild != null) {
                    return scrollingChild;
                }
            }
        }
        return null;
    }


 @Override
    public boolean onInterceptTouchEvent(
        @NonNull CoordinatorLayout parent, @NonNull V child, @NonNull MotionEvent event) {
        
        switch (action) {
           //..
            case MotionEvent.ACTION_DOWN:
                int initialX = (int) event.getX();
                initialY = (int) event.getY();
			
                if (enableMulNested) {
                    View scroll =
                        nestedScrollingChildRef != null ? nestedScrollingChildRef.get() : null;
                    //之前的嵌套滚动view是有效的
                    if (scroll!=null&&scroll.isShown()&&parent.isPointInChildBounds(child,initialX,initialY)) {

                    }else{
                        View scrollingChild = findScrollingChild(parent, child, initialX, initialY);
                        if (scrollingChild!=null) {
                            nestedScrollingChildRef= new WeakReference(scrollingChild);
                        }
                    }
                }

            default: // fall out
        }
       
  //...
        return flag;
    }
}    

如果对于viewpager2 切换并且两个两个界面都有recyclerview的情况 需要每次拦截事件的时候重新获取

public class BottomSheetBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {
	 @Override
    public boolean onInterceptTouchEvent(
         CoordinatorLayout parent,  V child, MotionEvent event) {
 
 		//如果当前可嵌套滚动不可见view,那么需要重新获取
        View nestedView = nestedScrollingChildRef.get();
        View rootView = viewRef.get();
        if (nestedView!=null&&!nestedView.isShown()&&rootView!=null) {
            nestedScrollingChildRef = new WeakReference<>( findScrollingChild(rootView));
        }


        return flag;
    }
}
  1. bottomsheetbehavior-with-viewpager2-cant-be-scrolled-down-by-nested-recyclervie
  2. BottomSheetBehavior with two RecyclerView

更多推荐