浅谈RecycleView嵌套RecycleView竖向滑动冲突解决

这个问题的解决思路有两个:

思路一.内部拦截法:

1.自定义一个view继承Recycleview重写它的onTouchEvent方法,

2.在MotionEvent.ACTION_DOWN时调用getParent().requestDisallowInterceptTouchEvent(true)方法将touch事件拦截下来给子recyleview,外部滑动事件禁用

3 .然后在MotionEvent.ACTION_MOVE中做三个判断,

第一判断当前子recycleview显示的最上面和最下面的viewholder的position是否是第一个和最后一个(目的是判断现在子recycleview是否到了最顶部或者最底部区域),

第二调用子recycleview的canScrollVertically(int direction)方法判断当前recycleview向上和向下方向是否能滑动,不过这里有个坑也是看过别人的博客学习到的

这个canScrollVertically方法源码和注释如下:

/**
     * Check if this view can be scrolled vertically in a certain direction.
     *
     * @param direction Negative to check scrolling up, positive to check scrolling down.
     * @return true if this view can be scrolled in the specified direction, false otherwise.
     */
    public boolean canScrollVertically(int direction) {
        final int offset = computeVerticalScrollOffset();
        final int range = computeVerticalScrollRange() - computeVerticalScrollExtent();
        if (range == 0) return false;
        if (direction < 0) {
            return offset > 0;
        } else {
            return offset < range - 1;
        }
    }
 

 注释的翻译是这样的:

检查此视图是否可以在某个方向上垂直滚动,
@param direction负值检查向上滚动,正向检查向下滚动。
@return如果此视图可以在指定方向滚动,则为true,否则为false

然而实际上,把传入的direction设为负值,才是判断手指能否向下滑动,正值是判断手指能否向上滑动。不知道是不是作者对这个“滚动”的对象或者坐标系跟我理解的不同,总之,在我看来,这个注释和代码效果是完全相反的。 

上面的见解来自下面博客,里面写了两个关于这个方面的踩坑解决方案,想了解更多可以进入查看,此处附上出处链接

https://blog.csdn.net/xingchenxuanfeng/article/details/84790299

 接着说第三个判断,就是在MotionEvent.ACTION_DOWN时记录mLastY的值,然后在MotionEvent.ACTION_MOVE方法时不断记录nowY的值,判断过第一第二后最后判断这个nowY和mLastY数值的大小,如果向上滑动则需要满足nowY<mLastY,向下滑动则nowY>mLastY的条件,满足了这三个条件则可以确定子recycleview已经滑动顶部或者底部并且第一个或者最后一个itemview完全显示完整,这个时候再调用

getParent().requestDisallowInterceptTouchEvent(false);

允许外层recycleview处理滑动事件,这样就完美的解决了竖向滑动冲突的问题,其实这个思路是看一个博客学习来的,不过他写的思路是对的,不过在recycleview的canScrollVertically的正副值判断向上还是向下是否可滑动时没有发现里面有坑,所以在用他的时候发现还是会有滑动冲突,不过这个思路好多人都有博客转载,也不知道到底谁是真的原创了,我就把我第一篇看到写这个的哥们的博客地址贴出来给大家学习参考吧,我上面写的思路几乎百分之九十五都来自这篇博客的解决方案   https://blog.csdn.net/jiang19921002/article/details/82527132    , 写这篇博客只是想记录这个思路和解决过程,因为解决这个问题时试了好多博客提供的方案,没有真正的效果 比如下面几个博客   http://www.manongjc.com/article/57580.html       https://blog.csdn.net/w13576267399/article/details/86238822      https://ask.csdn.net/questions/338043   ,下面这个博客  https://www.jianshu.com/p/da961c6d0e3a   的思路是把子recycleview的实际高度全部绘制出来,也是一种解决嵌套滑动冲突的方案,在Scrollview嵌套listview的时候解决比较好,但如果用在外层的layoutmanager是GridLayoutManager,并且不想要瀑布流效果,每个item的recycleview的高度要求统一并且item数量不同时,就不太适用了,所以建议还是从滑动事件方面入手解决。

下面贴一下我改过后这个子recycleview的代码:
/**
 * @Modified by 王骏杰
 * @data 2019/8/18
 * @des 解决RecyclerView嵌套ecyclerView竖向滑动冲突
 */
public class NestRecyclerView extends RecyclerView {

    private int lastVisibleItemPosition;
    private int firstVisibleItemPosition;
    private float mLastY = 0;// 记录上次Y位置
    private boolean isTopToBottom = false;
    private boolean isBottomToTop = false;
    public NestRecyclerView(Context context) {
        this(context, null);
    }

    public NestRecyclerView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public NestRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
 
    /*@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                getParent().requestDisallowInterceptTouchEvent(false);
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }*/

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mLastY = event.getY();
                //不允许父View拦截事件
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                float nowY = event.getY();
                isIntercept(nowY);
                if (isBottomToTop||isTopToBottom){
                    getParent().requestDisallowInterceptTouchEvent(false);
                    return false;
                }else{
                    getParent().requestDisallowInterceptTouchEvent(true);
                }
                mLastY = nowY;
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                getParent().requestDisallowInterceptTouchEvent(false);
                break;
        }
        return super.onTouchEvent(event);
    }

    private void isIntercept(float nowY){

        isTopToBottom = false;
        isBottomToTop = false;

        RecyclerView.LayoutManager layoutManager = getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {
            //得到当前界面,最后一个子视图对应的position
            lastVisibleItemPosition = ((GridLayoutManager) layoutManager)
                    .findLastVisibleItemPosition();
            //得到当前界面,第一个子视图的position
            firstVisibleItemPosition = ((GridLayoutManager) layoutManager)
                    .findFirstVisibleItemPosition();
        }else if (layoutManager instanceof LinearLayoutManager){
            //得到当前界面,最后一个子视图对应的position
            lastVisibleItemPosition = ((LinearLayoutManager) layoutManager)
                    .findLastVisibleItemPosition();
            //得到当前界面,第一个子视图的position
            firstVisibleItemPosition = ((LinearLayoutManager) layoutManager)
                    .findFirstVisibleItemPosition();
        }
        //得到当前界面可见数据的大小
        int visibleItemCount = layoutManager.getChildCount();
        //得到RecyclerView对应所有数据的大小
        int totalItemCount = layoutManager.getItemCount();
        Log.d("nestScrolling","onScrollStateChanged");
        if (visibleItemCount>0) {
            
            if (lastVisibleItemPosition == totalItemCount - 1) {
                //最后视图对应的position等于总数-1时,说明上一次滑动结束时,触底了
                Log.d("nestScrolling", "触底了");

                /**
                 * 注意这里有非常关键的两点,也是我修改完善之前哥们博客的有坑的两点,
                 * 第一点是canScrollVertically传的正负值问题,判断向上用正值1,向下则反过来用负值-1,
                 * 第二点是canScrollVertically返回值的问题,true时是代表可以滑动,false时才代表划到顶部或者底部不可以再滑动了,所以这个判断前要加逻辑非!运算符
                 * 补充了这两点基本效果就很完美了。
                 */
                if (!NestRecyclerView.this.canScrollVertically(1) && nowY < mLastY) {
                    // 不能向上滑动
                    Log.d("nestScrolling", "不能向上滑动");
                    isBottomToTop = true;
                } else {
                    Log.d("nestScrolling", "向下滑动");
                }
            } else if (firstVisibleItemPosition == 0) {
                //第一个视图的position等于0,说明上一次滑动结束时,触顶了
                Log.d("nestScrolling", "触顶了");
                if (!NestRecyclerView.this.canScrollVertically(-1) && nowY > mLastY) {
                    // 不能向下滑动
                    Log.d("nestScrolling", "不能向下滑动");
                    isTopToBottom = true;
                } else {
                    Log.d("nestScrolling", "向上滑动");
                }
            }
        }
    }
 
}

 最后想说解决和创新思路才是最重要的,很感激写出这个内部拦截思路方案的哥们的开源贡献的博主的!

思路二:外部事件拦截,就是从外层的recycleview入手解决了

这个有点像一个论坛里面这个哥们的思路:

这个思路我还不是太清晰,我照着这个论坛博主的方方法自定义了一个外层的recycleview,在toucu事件的

down和up中返回了false,但并不起作用,所以这个只能以后再作完善补充了。本篇到此为止,用15分钟记录一个坑,以后会避免再跳进同样的坑!拜拜。

发布了89 篇原创文章 · 获赞 231 · 访问量 62万+

猜你喜欢

转载自blog.csdn.net/wjj1996825/article/details/99700345