You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 08:17:57