Android--›打造无限循环的RecyclerView(已更新新方式)

这里写图片描述


难点分析:
1:如何在有限的数据里面, 实现无限个Item呢?
2:如何让滑动的时候, 一个一个的滑动, 而不会一下子滚动多个呢?
3:如何在第一次显示的时候, 就可以左滑呢?


更新于2018-3-8
鉴于之前的时候方式, 有很多问题, 思路也不是特别好. 最近在学习的过程中, 发现了大神写了自定义的ViewPagerLayoutManager支持无限循环, 所以我就打算使用这个LayoutManager重写LoopRecyclerView.

1:继承ViewPagerLayoutManager

public static class LoopLayoutManager extends ViewPagerLayoutManager {

     private float itemWidth = -1;
     private float itemHeight = -1;

     public LoopLayoutManager(Context context) {
         this(context, ViewPagerLayoutManager.HORIZONTAL, false);
     }

     public LoopLayoutManager(Context context, int orientation, boolean reverseLayout) {
         super(context, orientation, reverseLayout);
         setEnableBringCenterToFront(true);
     }

     public void setItemWidthHeight(float itemWidth, float itemHeight) {
         this.itemWidth = itemWidth;
         this.itemHeight = itemHeight;
         requestLayout();
     }

     @Override
     public void setInfinite(boolean enable) {
         if (!enable) {
             int positionOffset = getCurrentPositionOffset();
             if (positionOffset > getItemCount() || positionOffset < 0) {
                 mOffset = 0;
             }
         }
         super.setInfinite(enable);
     }

     /**
      * Item 之间间隔的大小
      * 默认情况下, Item之间是相互叠加显示的, 需要通过此方法, 设置间隔才能显示出线性的效果
      */
     @Override
     protected float setInterval() {
         if (getOrientation() == ViewPagerLayoutManager.VERTICAL) {
             return itemHeight;
         }
         return itemWidth;
     }

     /**
      * 用来控制item属性, 比如各种属性动画, 在滑动的时候出发
      */
     @Override
     protected void setItemViewProperty(View itemView, float targetOffset) {
         //targetOffset 和 itemInterval 密切广西
         //targetOffset 取值范围 -itemInterval/2  0  itemInterval/2
         //L.e("setItemViewProperty() -> " + targetOffset);
     }
 }

上面这个类, 主要作用就是让ViewPagerLayoutManager产生出LinearLayoutManager的水平布局效果, 和支持无限循环布局.

2:继承PagerSnapHelper
让RecyclerView滚动的时候, 模拟出ViewPager的效果, 并且监听页面改变回调.

publicstatic class LoopSnapHelper extends RPagerSnapHelper {

     @Override
     public boolean onFling(int velocityX, int velocityY) {
         super.onFling(velocityX, velocityY);
         //拦截fling操作
         return true;
     }

     @Nullable
     @Override
     public View findSnapView(LayoutManager layoutManager) {
         return super.findSnapView(layoutManager);
     }

     @Override
     public int findTargetSnapPosition(LayoutManager lm, int velocityX, int velocityY) {
         //return super.findTargetSnapPosition(layoutManager, velocityX, velocityY);
         //重写fling操作
         if (lm instanceof LoopLayoutManager) {
         } else {
             return RecyclerView.NO_POSITION;
         }
         RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
         if (adapter == null) {
             return RecyclerView.NO_POSITION;
         }

         final int minFlingVelocity = mRecyclerView.getMinFlingVelocity();

         LoopLayoutManager layoutManager = (LoopLayoutManager) lm;
         int orientation = layoutManager.getOrientation();

         final boolean forwardDirection;
         if (layoutManager.canScrollHorizontally()) {
             forwardDirection = velocityX > 0;
         } else {
             forwardDirection = velocityY > 0;
         }
         final int offsetPosition;
         if (forwardDirection) {
             offsetPosition = 1;
         } else {
             offsetPosition = -1;
         }

         final int currentPosition = layoutManager.getCurrentPosition();
         if ((orientation == ViewPagerLayoutManager.VERTICAL
                 && Math.abs(velocityY) > minFlingVelocity) || (orientation == ViewPagerLayoutManager.HORIZONTAL
                 && Math.abs(velocityX) > minFlingVelocity)) {

             int position = layoutManager.getReverseLayout() ?
                     currentPosition - offsetPosition : currentPosition + offsetPosition;
             mRecyclerView.smoothScrollToPosition(position);
         }

         //不需要默认的fling操作
         return RecyclerView.NO_POSITION;
     }

     @Nullable
     @Override
     public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) {
         RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) targetView.getLayoutParams();
         int position = params.getViewAdapterPosition();
         int left = targetView.getLeft();
         int right = targetView.getRight();
         int top = targetView.getTop();
         int bottom = targetView.getBottom();
         ViewGroup viewGroup = (ViewGroup) targetView.getParent();

         int[] out = new int[]{0, 0};
         boolean isLastItem;
         if (mOrientation == LinearLayoutManager.HORIZONTAL) {
             isLastItem = position == layoutManager.getItemCount() - 1/*最后一个*/ && right == viewGroup.getMeasuredWidth();
             out[0] = left;
             out[1] = 0;
         } else {
             isLastItem = position == layoutManager.getItemCount() - 1/*最后一个*/ && bottom == viewGroup.getMeasuredHeight();
             out[0] = 0;
             out[1] = top;
         }

         if (mOnPageListener != null && mCurrentPosition != position) {
             int currentPosition = mCurrentPosition;
             boolean listener = false;
             if (mOrientation == LinearLayoutManager.HORIZONTAL && (out[0] == 0 || isLastItem)) {
                 listener = true;
             } else if (mOrientation == LinearLayoutManager.VERTICAL && (out[1] == 0 || isLastItem)) {
                 listener = true;
             }

             if (listener) {
                 mCurrentPosition = position;
                 mOnPageListener.onPageSelector(mCurrentPosition);
                 mOnPageListener.onPageSelector(currentPosition, mCurrentPosition);
             }
         }
         return out;
     }
 }

public class RPagerSnapHelper extends PagerSnapHelper {

    protected RecyclerView mRecyclerView;
    OnPageListener mOnPageListener;
    int mCurrentPosition = -1;
    /**
     * 默认是横向Pager
     */
    int mOrientation = LinearLayoutManager.HORIZONTAL;
    private RecyclerView.OnScrollListener mScrollListener = new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
                //开始滚动
            } else if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                //结束滚动
            } else if (newState == RecyclerView.SCROLL_STATE_SETTLING) {
                //滑行中
            }
        }
    };

    public RPagerSnapHelper() {
    }

    public RPagerSnapHelper(int mCurrentPosition) {
        this.mCurrentPosition = mCurrentPosition;
    }

    public int getCurrentPosition() {
        return mCurrentPosition;
    }

    public RPagerSnapHelper setCurrentPosition(int currentPosition) {
        mCurrentPosition = currentPosition;
        return this;
    }

    public OnPageListener getOnPageListener() {
        return mOnPageListener;
    }

    /**
     * 页面选择回调监听
     */
    public RPagerSnapHelper setOnPageListener(OnPageListener onPageListener) {
        mOnPageListener = onPageListener;
        return this;
    }

    @Override
    public void attachToRecyclerView(@Nullable RecyclerView recyclerView) throws IllegalStateException {
        super.attachToRecyclerView(recyclerView);
        mRecyclerView = recyclerView;

        if (recyclerView != null) {
            recyclerView.removeOnScrollListener(mScrollListener);
            recyclerView.addOnScrollListener(mScrollListener);

            RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
            if (layoutManager instanceof LinearLayoutManager) {
                mOrientation = ((LinearLayoutManager) layoutManager).getOrientation();
            }
        }
    }

    @Nullable
    @Override
    public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) {
        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) targetView.getLayoutParams();
        int position = params.getViewAdapterPosition();
        int left = targetView.getLeft();
        int right = targetView.getRight();
        int top = targetView.getTop();
        int bottom = targetView.getBottom();
        ViewGroup viewGroup = (ViewGroup) targetView.getParent();

        int[] out = new int[]{0, 0};
        boolean isLastItem;
        if (mOrientation == LinearLayoutManager.HORIZONTAL) {
            isLastItem = position == layoutManager.getItemCount() - 1/*最后一个*/ && right == viewGroup.getMeasuredWidth();
            out[0] = left;
            out[1] = 0;
        } else {
            isLastItem = position == layoutManager.getItemCount() - 1/*最后一个*/ && bottom == viewGroup.getMeasuredHeight();
            out[0] = 0;
            out[1] = top;
        }

        if (mOnPageListener != null && mCurrentPosition != position) {
            int currentPosition = mCurrentPosition;
            boolean listener = false;
            if (mOrientation == LinearLayoutManager.HORIZONTAL && (out[0] == 0 || isLastItem)) {
                listener = true;
            } else if (mOrientation == LinearLayoutManager.VERTICAL && (out[1] == 0 || isLastItem)) {
                listener = true;
            }

            if (listener) {
                mCurrentPosition = position;
                mOnPageListener.onPageSelector(mCurrentPosition);
                mOnPageListener.onPageSelector(currentPosition, mCurrentPosition);
            }
        }
        return out;
    }

    public RPagerSnapHelper setOrientation(int orientation) {
        mOrientation = orientation;
        return this;
    }

    public static abstract class OnPageListener {

        @Deprecated
        public void onPageSelector(int position) {

        }

        public void onPageSelector(int fromPosition, int toPosition) {

        }
    }
}

3:重写LoopRecyclerView
实现界面展示, 实现自动滚动, 封装功能

public class RExLoopRecyclerView extends RRecyclerView {

    private LoopLayoutManager mLoopLayoutManager;
    private boolean mInfinite = true;
    private RPagerSnapHelper.OnPageListener mOnPageListener;

    public RExLoopRecyclerView(Context context) {
        this(context, null);
    }

    public RExLoopRecyclerView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RExLoopRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void initView(Context context) {
        super.initView(context);

        autoScrollRunnable = new Runnable() {
            @Override
            public void run() {
                curScrollPosition = getCurrentPosition();

                scrollToNext();

                if (enableScroll) {
                    postDelayed(autoScrollRunnable, autoScrollTimeInterval);
                }
            }
        };

        mLoopLayoutManager = new LoopLayoutManager(getContext(), ViewPagerLayoutManager.HORIZONTAL, false);
        setInfinite(true);
        setLayoutManager(mLoopLayoutManager);

        new RExLoopRecyclerView.LoopSnapHelper().setOnPageListener(new RPagerSnapHelper.OnPageListener() {
            @Override
            public void onPageSelector(int fromPosition, int toPosition) {
                super.onPageSelector(fromPosition, toPosition);
                //L.e("onPageSelector() -> " + fromPosition + " to " + toPosition);
                if (mOnPageListener != null) {
                    mOnPageListener.onPageSelector(fromPosition, toPosition);
                }
            }
        }).attachToRecyclerView(this);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (mLoopLayoutManager != null) {
            mLoopLayoutManager.setItemWidthHeight(w, h);
        }
    }

    /**
     * 打开无限循环
     */
    public void setInfinite(boolean enable) {
        mInfinite = enable;
        if (mLoopLayoutManager != null) {
            mLoopLayoutManager.setInfinite(enable);
        }
    }

    /**
     * @see ViewPagerLayoutManager#setOrientation(int)
     */
    public void setOrientation(int orientation) {
        if (mLoopLayoutManager != null) {
            mLoopLayoutManager.setOrientation(orientation);
        }
    }

    /**
     * 滚动到下一个
     */
    public void scrollToNext() {
        scrollTo(true);
    }

    /**
     * 滚动到上一个
     */
    public void scrollToPrev() {
        scrollTo(false);
    }

    public void scrollTo(boolean forwardDirection) {
        if (getLayoutManager() instanceof LoopLayoutManager && getAdapter() != null) {
            final int offsetPosition;
            if (forwardDirection) {
                offsetPosition = 1;
            } else {
                offsetPosition = -1;
            }

            final int currentPosition = getCurrentPosition();

            int position = ((LoopLayoutManager) getLayoutManager()).getReverseLayout() ?
                    currentPosition - offsetPosition : currentPosition + offsetPosition;
            smoothScrollToPosition(position);
        }
    }

    public int getCurrentPosition() {
        if (getLayoutManager() instanceof LoopLayoutManager) {
            return ((LoopLayoutManager) getLayoutManager()).getCurrentPosition();
        } else {
            return RecyclerView.NO_POSITION;
        }
    }

    public void setOnPageListener(RPagerSnapHelper.OnPageListener onPageListener) {
        mOnPageListener = onPageListener;
    }
 }

RRecyclerView 是我自己封装的RecyclerView类, 没有特殊之处. 就是多了一个 自动滚动的触发机制.
如果RExLoopRecyclerView编译有错误, 可以自行实现对应功能, 或者删除报错代码.


以下是老的实现方式:


针对以上问题, 接下来一一解决:
问题1:
只需要在RecyclerView.Adapter的方法中:

@Override
public int getItemCount() {
    return Integer.MAX_VALUE;
}

可能距离真正无限个Item还是有差距, 但是达到效果还是可以的;

问题2:
其实谷歌已经帮我们实现了.

new PagerSnapHelper().attachToRecyclerView(this);

这样之后, 就可以一个一个的滚动Item了, 一行代码就实现了类似ViewPager的效果,
你还有什么理由使用ViewPager?

但是有一个缺点, 就是没有像ViewPager那样的OnPageChangeListener事件监听.
不过, 完全可以自己动手, 添加这个事件.

实现起来也是非常简单. 项目中有源码.

问题3:

@Override
public void setAdapter(Adapter adapter) {
    super.setAdapter(adapter);
    scrollToPosition(getAdapter().getItemRawCount() * 10000);//开始时的偏移量
}

只需要让开始的时候, 产生一定的位置偏移就行了.

其实思路很简单, 大家多动动脑, 动动手. 完全无压力的!
开源地址: https://github.com/angcyo/LoopRecyclerView


也许你还想学习更多, 来我的群吧, 我写代码的能力, 远大于写文章的能力:

联系作者

点此快速加群

请使用QQ扫码加群, 小伙伴们都在等着你哦!

关注我的公众号, 每天都能一起玩耍哦!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值