NestedScrollView内RecyclerView导致BottomSheet误关闭问题修复
解决BottomSheet内RecyclerView滚动与BottomSheet关闭的冲突问题
核心问题是嵌套滚动事件的拦截优先级:默认的BottomSheetBehavior会优先拦截向下滚动事件来关闭自身,导致内部RecyclerView还没滚动到顶部就触发了BottomSheet收起。我们需要调整事件拦截逻辑,让RecyclerView先处理滚动,只有当它到达顶部时,才允许BottomSheet响应关闭操作。
步骤1:移除多余的NestedScrollView
你的布局里用NestedScrollView作为BottomSheet的根容器,但ViewPager2和RecyclerView本身已经支持嵌套滚动,多层滚动容器会导致事件传递混乱。直接把BottomSheet的根布局换成ConstraintLayout或LinearLayout:
<!-- fragment_viability_bottom_sheet_v2.xml --> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/bottom_sheet_root" android:layout_width="match_parent" android:layout_height="wrap_content" app:behavior_peekHeight="120dp" app:layout_behavior="com.yourpackage.CustomBottomSheetBehavior"> <com.google.android.material.tabs.TabLayout android:id="@+id/tab_layout" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent"/> <androidx.viewpager2.widget.ViewPager2 android:id="@+id/view_pager" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintTop_toBottomOf="@id/tab_layout" app:layout_constraintBottom_toBottomOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout>
步骤2:自定义BottomSheetBehavior拦截滚动事件
创建自定义Behavior,重写触摸事件拦截逻辑,判断当前RecyclerView是否处于顶部:
class CustomBottomSheetBehavior(context: Context, attrs: AttributeSet) : BottomSheetBehavior<View>(context, attrs) { private var currentRecyclerView: RecyclerView? = null // 传入当前显示Tab对应的RecyclerView fun setCurrentRecyclerView(recyclerView: RecyclerView?) { currentRecyclerView = recyclerView } override fun onInterceptTouchEvent(parent: CoordinatorLayout, child: View, event: MotionEvent): Boolean { if (event.action == MotionEvent.ACTION_DOWN) { lastY = event.y } else if (event.action == MotionEvent.ACTION_MOVE) { val dy = event.y - lastY // 向下滚动且RecyclerView未到顶时,不拦截事件,交给RecyclerView处理 if (dy > 0 && currentRecyclerView != null && !isRecyclerViewAtTop(currentRecyclerView!!)) { return false } } return super.onInterceptTouchEvent(parent, child, event) } override fun onTouchEvent(parent: CoordinatorLayout, child: View, event: MotionEvent): Boolean { if (event.action == MotionEvent.ACTION_MOVE) { val dy = event.y - lastY if (dy > 0 && currentRecyclerView != null && !isRecyclerViewAtTop(currentRecyclerView!!)) { return false } } return super.onTouchEvent(parent, child, event) } // 判断RecyclerView是否完全滚动到顶部 private fun isRecyclerViewAtTop(recyclerView: RecyclerView): Boolean { val layoutManager = recyclerView.layoutManager as LinearLayoutManager return layoutManager.findFirstCompletelyVisibleItemPosition() == 0 } companion object { private var lastY = 0f fun <V : View> from(view: V): CustomBottomSheetBehavior<V> { val params = view.layoutParams as CoordinatorLayout.LayoutParams val behavior = params.behavior require(behavior is CustomBottomSheetBehavior<V>) { "View未绑定CustomBottomSheetBehavior" } return behavior } } }
步骤3:关联ViewPager2页面切换与Behavior
在MapFragment中监听ViewPager2的页面切换,把当前Tab对应的RecyclerView传给自定义Behavior:
class MapFragment : Fragment() { private lateinit var bottomSheetBehavior: CustomBottomSheetBehavior<ConstraintLayout> private lateinit var viewPager: ViewPager2 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 获取自定义BottomSheetBehavior val bottomSheetRoot = view.findViewById<ConstraintLayout>(R.id.bottom_sheet_root) bottomSheetBehavior = CustomBottomSheetBehavior.from(bottomSheetRoot) viewPager = view.findViewById(R.id.view_pager) viewPager.adapter = YourPagerAdapter(this) // 页面切换时更新当前RecyclerView引用 viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { super.onPageSelected(position) val currentFragment = childFragmentManager.findFragmentByTag("f$position") when (currentFragment) { is XFragment -> bottomSheetBehavior.setCurrentRecyclerView(currentFragment.recyclerView) is YFragment -> bottomSheetBehavior.setCurrentRecyclerView(currentFragment.recyclerView) } } }) } }
步骤4:在Tab Fragment中暴露RecyclerView
在X、Y Fragment中直接暴露RecyclerView的引用,方便MapFragment获取:
class XFragment : Fragment() { lateinit var recyclerView: RecyclerView override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = inflater.inflate(R.layout.fragment_x, container, false) recyclerView = view.findViewById(R.id.recycler_view) // 初始化RecyclerView配置 recyclerView.layoutManager = LinearLayoutManager(context) recyclerView.adapter = YourXAdapter() return view } }
关键注意点
- 用
findFirstCompletelyVisibleItemPosition判断RecyclerView是否到顶,而不是findFirstVisibleItemPosition,避免Item只显示一部分就触发BottomSheet关闭。 - 确保RecyclerView的
nestedScrollingEnabled属性为true(默认开启,无需额外设置,但可在xml中明确声明)。
内容的提问来源于stack exchange,提问作者giordy16
相关产品推荐
相关产品推荐

