ViewPager 嵌套 ListView,ListView 嵌套横滑的 RecyclerView 的焦点冲突事件

项目里有个需求,类似今日头条新闻主界面,新闻中包含抖音小视频的样式。具体点: 项目主界面时用 ViewPager + Fragment 来实现, Fragment 中 使用了 ListView控件来展示内容,其中 ListView 的一种 item 类型是个横滑的 RecyclerView ,使用默认控件实现了此功能,但有个问题就是 RecyclerView 可以左右滑动,但是手指上下滑动时,外层的 ListView 没有滑动,因为 RecyclerView 把事件给消费了,为了解决这个问题,自定了个 RecyclerView, 重写了事件分发的细节,增加了判断,根据 getParent().requestDisallowInterceptTouchEvent() 这个方法来请求父容器是否拦截触摸事件

RecyclerView :

    int lastX = 0;
    int lastY = 0;

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int x = (int) ev.getRawX();
        int y = (int) ev.getRawY();

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 保证子View能够接收到Action_move事件
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                int dealtX = Math.abs(x - lastX);
                int dealtY = Math.abs(y - lastY);
                // 这里是够拦截的判断依据是左右滑动,读者可根据自己的逻辑进行是否拦截, 加2的原因是为了更倾向于横滑
                if (dealtX >= dealtY / 2) {
                    getParent().requestDisallowInterceptTouchEvent(true);
                } else {
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                getParent().requestDisallowInterceptTouchEvent(false);
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

这样,要求就基本实现了。如果是横滑,然后竖滑,是可以让 ListView 上下滑动的;但如果先是竖滑,ListView 上下滑动了,再横滑,RecyclerView 是不会左右滑动的,原因是在之前的事件分发机制中有介绍。

上述代码初步实现功能,但还有个问题。就是 RecyclerView 左右滑动时,假如滑到了最右边,此刻所在的 Fragment 是ViewPager中的第一个,ViewPager 中还有5个 Fragment,此时RecyclerView 滑到了最右边,手指继续向左滑动,RecyclerView 已经到边了,此时我们想让 ViewPager 滑动,显示出下一个 Fragment,怎么办? 此时就要开动脑筋了,首先要判断是否滑到了 RecyclerView 的最左边或最右边,如果是,则把事件处理权交割给父容器,一旦事件处理权交给父容器,则没自己什么事了。如果没到左右边缘,则还是自己消费事件。 横滑的 RecyclerView 使用的是 LinearLayoutManager,它里面恰好提供了第一个或最后一个完整显示view的索引的方法,即 findFirstCompletelyVisibleItemPosition() 、findLastVisibleItemPosition() 方法

RecyclerView :

    int lastX = 0;
    int lastY = 0;
    int type = 0;

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int x = (int) ev.getRawX();
        int y = (int) ev.getRawY();

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 保证子View能够接收到Action_move事件
                getParent().requestDisallowInterceptTouchEvent(true);
                type = 1;
                lastX = x; // 可以省略
                lastY = y; // 可以省略
                mStartXIntercept = x;
                break;
            case MotionEvent.ACTION_MOVE:
                if (type == 1) {
                    LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager();
                    if (x - lastX > 5 && linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0) {
                        type = 2;
                        getParent().requestDisallowInterceptTouchEvent(false);
                    } else if (x - lastX < -5 && linearLayoutManager.findLastVisibleItemPosition() == getAdapter().getItemCount() - 1) {
                        type = 2;
                        getParent().requestDisallowInterceptTouchEvent(false);
                    } else {
                        type = 0;
                    }
                } else if (type == 0) {
                    int dealtX = Math.abs(x - lastX);
                    int dealtY = Math.abs(y - lastY);
                    // 这里是够拦截的判断依据是左右滑动,读者可根据自己的逻辑进行是否拦截
                    if (dealtX >= dealtY / 2) {
                        getParent().requestDisallowInterceptTouchEvent(true);
                    } else {
                        getParent().requestDisallowInterceptTouchEvent(false);
                    }
                }
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                getParent().requestDisallowInterceptTouchEvent(false);
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

这样,当 RecyclerView 滑到左右边缘时,就会请求父容器拦截触摸事件,从而把触摸事件的处理权交给父容器,这样就达到了滑到边缘,ViewPager 开始左右滑动的功能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值