如何在BottomSheet中实现多个可滚动RecyclerView并添加顶部按钮
解决BottomSheet中多RecyclerView滚动及顶部按钮点击问题
我来分享两个问题的可行解决方案,都是实际项目中验证过的实用技巧:
一、让BottomSheet支持多个RecyclerView滚动
BottomSheetBehavior默认只会识别第一个可滚动的子视图,所以我们需要自定义Behavior来让它检测所有可滚动的RecyclerView:
方案1:自定义BottomSheetBehavior(推荐,性能最优)
继承官方的BottomSheetBehavior,重写canScrollVertically方法,遍历所有子View判断是否有RecyclerView可以滚动:
class MultiScrollBottomSheetBehavior<V : View>(context: Context, attrs: AttributeSet) : BottomSheetBehavior<V>(context, attrs) { override fun canScrollVertically(coordinatorLayout: CoordinatorLayout, child: V, direction: Int): Boolean { // 遍历所有子View,检查是否有RecyclerView可以滚动 val recyclerViews = findAllRecyclerViews(child) for (rv in recyclerViews) { if (direction > 0) { // 向下滚动时,检查RecyclerView是否还有未显示的内容(能向上滚) if (rv.canScrollVertically(1)) { return true } } else { // 向上滚动时,检查RecyclerView是否不在顶部(能向下滚) if (rv.canScrollVertically(-1)) { return true } } } // 没有可滚动的RecyclerView时,沿用默认逻辑 return super.canScrollVertically(coordinatorLayout, child, direction) } // 递归查找所有RecyclerView子View private fun findAllRecyclerViews(view: View): List<RecyclerView> { val result = mutableListOf<RecyclerView>() if (view is RecyclerView) { result.add(view) } else if (view is ViewGroup) { for (i in 0 until view.childCount) { result.addAll(findAllRecyclerViews(view.getChildAt(i))) } } return result } }
然后在布局文件中使用这个自定义Behavior:
<LinearLayout android:id="@+id/bottom_sheet" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_behavior="com.yourpackage.MultiScrollBottomSheetBehavior"> <!-- 这里放置你的多个RecyclerView --> <RecyclerView android:id="@+id/rv1" ... /> <RecyclerView android:id="@+id/rv2" ... /> </LinearLayout>
方案2:嵌套在NestedScrollView(适合数据量小的场景)
如果你的RecyclerView数据不多,可以把它们放在NestedScrollView里,但需要解决RecyclerView高度测量的问题(默认NestedScrollView里RecyclerView只会显示一行):
首先自定义一个WrapContentRecyclerView:
class WrapContentRecyclerView(context: Context, attrs: AttributeSet?) : RecyclerView(context, attrs) { override fun onMeasure(widthSpec: Int, heightSpec: Int) { val heightSpecCustom = MeasureSpec.makeMeasureSpec( Int.MAX_VALUE shr 2, MeasureSpec.AT_MOST ) super.onMeasure(widthSpec, heightSpecCustom) val params = layoutParams params.height = measuredHeight } }
然后布局结构:
<LinearLayout android:id="@+id/bottom_sheet" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"> <androidx.core.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <com.yourpackage.WrapContentRecyclerView ... /> <com.yourpackage.WrapContentRecyclerView ... /> </LinearLayout> </androidx.core.widget.NestedScrollView> </LinearLayout>
二、在BottomSheet可触摸顶部添加可点击按钮
常规方法失效的原因通常是BottomSheet的拖拽事件拦截了按钮的点击,我们可以通过让按钮区域跳过BottomSheet的拖拽拦截来解决:
解决方案:自定义Behavior拦截触摸事件
基于上面的MultiScrollBottomSheetBehavior,再重写onInterceptTouchEvent方法,判断触摸点是否在按钮区域,如果是就不拦截事件,让按钮处理点击:
class MultiScrollBottomSheetBehavior<V : View>(context: Context, attrs: AttributeSet) : BottomSheetBehavior<V>(context, attrs) { // 保存顶部按钮的引用(也可以通过id在内部查找) private var topButton: View? = null fun setTopButton(button: View) { this.topButton = button } override fun onInterceptTouchEvent( coordinatorLayout: CoordinatorLayout, child: V, event: MotionEvent ): Boolean { topButton?.let { button -> val rect = Rect() button.getGlobalVisibleRect(rect) // 如果触摸点在按钮范围内,不拦截事件,让按钮处理点击 if (rect.contains(event.rawX.toInt(), event.rawY.toInt())) { return false } } // 否则沿用默认的拖拽拦截逻辑 return super.onInterceptTouchEvent(coordinatorLayout, child, event) } // 之前的canScrollVertically和findAllRecyclerViews方法保持不变 }
然后在Activity/Fragment中给Behavior设置按钮引用:
val bottomSheet = findViewById<LinearLayout>(R.id.bottom_sheet) val behavior = MultiScrollBottomSheetBehavior.from(bottomSheet) val topButton = findViewById<Button>(R.id.top_button) behavior.setTopButton(topButton)
布局里把按钮放在最顶部:
<LinearLayout android:id="@+id/bottom_sheet" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" app:layout_behavior="com.yourpackage.MultiScrollBottomSheetBehavior"> <!-- 顶部按钮 --> <Button android:id="@+id/top_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="顶部操作按钮"/> <!-- 下面是多个RecyclerView --> <RecyclerView ... /> <RecyclerView ... /> </LinearLayout>
这样按钮就能正常响应点击,同时BottomSheet的拖拽功能在按钮以外的区域正常工作。
内容的提问来源于stack exchange,提问作者TheMatrix
相关产品推荐
相关产品推荐

