Android API23+中BottomSheet内ListView无法独立滚动问题求助
解决BottomSheet中ListView滚动时Sheet跟随滚动的问题
我之前在适配API23+版本时也碰到过完全一样的问题!核心原因是BottomSheet的触摸事件拦截优先级高于ListView,导致滚动手势被BottomSheet抢先处理,而没有传递给ListView自身。下面是两个亲测有效的解决方案:
方案一:自定义ListView拦截触摸事件
通过重写ListView的触摸事件方法,让ListView在能滚动时抢占事件,只有当它滚到顶/底无法继续滚动时,才把事件交还给BottomSheet。
创建自定义ListView类:
public class BottomSheetListView extends ListView { private float startY; public BottomSheetListView(Context context) { super(context); } public BottomSheetListView(Context context, AttributeSet attrs) { super(context, attrs); } public BottomSheetListView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: startY = ev.getY(); // 告诉父View(BottomSheet)不要拦截当前触摸事件 getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: float currentY = ev.getY(); // 判断ListView是否已经无法继续滚动:向上滚到顶 或者 向下滚到底 boolean canScrollList = !( (currentY > startY && getFirstVisiblePosition() == 0 && getChildAt(0).getTop() == 0) || (currentY < startY && getLastVisiblePosition() == getCount() - 1 && getChildAt(getChildCount() - 1).getBottom() <= getHeight()) ); getParent().requestDisallowInterceptTouchEvent(canScrollList); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: // 触摸结束,恢复父View的拦截权限 getParent().requestDisallowInterceptTouchEvent(false); break; } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_MOVE: float currentY = ev.getY(); // 如果ListView无法滚动,就让事件传递给BottomSheet处理 if ((currentY > startY && getFirstVisiblePosition() == 0 && getChildAt(0).getTop() == 0) || (currentY < startY && getLastVisiblePosition() == getCount() - 1 && getChildAt(getChildCount() - 1).getBottom() <= getHeight())) { return false; } break; } return super.onTouchEvent(ev); } }
然后在XML布局中替换原生ListView:
<com.yourpackage.BottomSheetListView android:id="@+id/list_view" android:layout_width="match_parent" android:layout_height="match_parent" />
方案二:调整BottomSheetBehavior的滚动拦截逻辑
如果你使用的是AndroidX的BottomSheetBehavior,可以通过设置参数或监听回调来控制事件拦截,但这个方法兼容性稍差,不如方案一直接。
在代码中获取BottomSheetBehavior后:
BottomSheetBehavior<View> behavior = BottomSheetBehavior.from(bottomSheetView); // 禁止BottomSheet完全隐藏(可选,视需求而定) behavior.setHideable(false); // 或者通过回调调整拦截逻辑 behavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { @Override public void onStateChanged(@NonNull View bottomSheet, int newState) {} @Override public void onSlide(@NonNull View bottomSheet, float slideOffset) { // 当BottomSheet处于展开状态时,允许ListView处理滚动 if (behavior.getState() == BottomSheetBehavior.STATE_EXPANDED) { bottomSheet.requestDisallowInterceptTouchEvent(true); } else { bottomSheet.requestDisallowInterceptTouchEvent(false); } } });
额外注意事项
- 如果ListView的Item包含可点击控件(比如Button、CheckBox),确保这些控件的
android:focusable="true"属性设置正确,避免触摸事件被Item抢占导致ListView无法滚动。 - 测试时重点关注API24和25版本,这两个版本的触摸事件分发逻辑和后续版本略有差异,方案一的自定义View可以完美适配。
我当时用方案一就解决了问题,你可以先试试这个!
内容的提问来源于stack exchange,提问作者Nic2352
相关产品推荐
相关产品推荐

