实现RecycleView横向、竖向无限循坏(基于自定义RecyclerView.LayoutManager)
1、横向循环(代码中有注解)
public class LooperLayoutManager extends RecyclerView.LayoutManager { private static final String TAG = "LooperLayoutManager"; private boolean looperEnable = true; public LooperLayoutManager() {} public void setLooperEnable(boolean looperEnable) { this.looperEnable = looperEnable; } @Override public RecyclerView.LayoutParams generateDefaultLayoutParams() { return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); } @Override public boolean canScrollHorizontally() { return true; } @Override public boolean canScrollVertically() { return false; } @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { if (getItemCount() <= 0) { return; } //preLayout主要支持动画,直接跳过 if (state.isPreLayout()) { return; } //将视图分离放入scrap缓存中,以准备重新对view进行排版 detachAndScrapAttachedViews(recycler); int autualWidth = 0; for (int i = 0; i < getItemCount(); i++) { //初始化,将在屏幕内的view填充 View itemView = recycler.getViewForPosition(i); addView(itemView); //测量itemView的宽高 measureChildWithMargins(itemView, 0, 0); int width = getDecoratedMeasuredWidth(itemView); int height = getDecoratedMeasuredHeight(itemView); //根据itemView的宽高进行布局 layoutDecorated(itemView, autualWidth, 0, autualWidth + width, height); autualWidth += width; //如果当前布局过的itemView的宽度总和大于RecyclerView的宽,则不再进行布局 if (autualWidth > getWidth()) { break; } } } @Override public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) { //1.左右滑动的时候,填充子view int travl = fill(dx, recycler, state); if (travl == 0) { return 0; } Log.e(TAG, "scrollHorizontallyBy: "+travl ); //2.滚动 offsetChildrenHorizontal(travl * -1); //3.回收已经离开界面的 recyclerHideView(dx, recycler, state); return travl; } /** * 左右滑动的时候,填充 */ private int fill(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) { if (dx > 0) { //标注1.向左滚动 View lastView = getChildAt(getChildCount() - 1); if (lastView == null) { return 0; } int lastPos = getPosition(lastView); Log.e(TAG, "scrollHorizontallyBy: lastPos=="+lastPos+"==="+lastView.getRight()+"==="+getWidth() ); //标注2.可见的最后一个itemView完全滑进来了,需要补充新的 if (lastView.getRight() < getWidth()) { View scrap = null; //标注3.判断可见的最后一个itemView的索引, // 如果是最后一个,则将下一个itemView设置为第一个,否则设置为当前索引的下一个 if (lastPos == getItemCount() - 1) { if (looperEnable) { scrap = recycler.getViewForPosition(0); } else { dx = 0; } } else { scrap = recycler.getViewForPosition(lastPos + 1); } if (scrap == null) { return dx; } //标注4.将新的itemViewadd进来并对其测量和布局 addView(scrap); measureChildWithMargins(scrap, 0, 0); int width = getDecoratedMeasuredWidth(scrap); int height = getDecoratedMeasuredHeight(scrap); layoutDecorated(scrap,lastView.getRight(), 0, lastView.getRight() + width, height); Log.e(TAG, "scrollHorizontallyBy: dx==="+dx ); return dx; } } else { //向右滚动 View firstView = getChildAt(0); if (firstView == null) { return 0; } int firstPos = getPosition(firstView); Log.e(TAG, "scrollHorizontallyBy: firstPos="+firstPos ); if (firstView.getLeft() >= 0) { View scrap = null; if (firstPos == 0) { if (looperEnable) { scrap = recycler.getViewForPosition(getItemCount() - 1); } else { dx = 0; } } else { scrap = recycler.getViewForPosition(firstPos - 1); } if (scrap == null) { return 0; } addView(scrap, 0); measureChildWithMargins(scrap,0,0); int width = getDecoratedMeasuredWidth(scrap); int height = getDecoratedMeasuredHeight(scrap); layoutDecorated(scrap, firstView.getLeft() - width, 0, firstView.getLeft(), height); } } Log.e(TAG, "scrollHorizontallyBy: dx=dx=="+dx ); return dx; } /** * 回收界面不可见的view */ private void recyclerHideView(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) { for (int i = 0; i < getChildCount(); i++) { View view = getChildAt(i); if (view == null) { continue; } if (dx > 0) { //向左滚动,移除一个左边不在内容里的view if (view.getRight() < 0) { removeAndRecycleView(view, recycler); Log.d(TAG, "循环: 移除 一个view childCount=" + getChildCount()); } } else { //向右滚动,移除一个右边不在内容里的view if (view.getLeft() > getWidth()) { removeAndRecycleView(view, recycler); Log.d(TAG, "循环: 移除 一个view childCount=" + getChildCount()); } } } } }
2、竖向滑动(代码中有注解)
/** * <pre> * @author: Meng * --->time: 2020/12/22 * ---> dec: 配合自动滑动的Rv 实现的无线循环 * * <pre> */ class CustomLinearLayoutManager : LinearLayoutManager { private var looperEnable = true val TAG = "CustomLinearLayoutManager" constructor(context: Context?) : super(context) {} constructor(context: Context?, orientation: Int, reverseLayout: Boolean) : super( context, orientation, reverseLayout ) { } constructor( context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int ) : super(context, attrs, defStyleAttr, defStyleRes) { } override fun generateDefaultLayoutParams(): RecyclerView.LayoutParams? { return RecyclerView.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT ) } override fun canScrollHorizontally(): Boolean { return false } override fun canScrollVertically(): Boolean { return true } override fun onLayoutChildren( recycler: RecyclerView.Recycler, state: RecyclerView.State ) { if (itemCount <= 0) { return } //preLayout主要支持动画,直接跳过 if (state.isPreLayout) { return } //将视图分离放入scrap缓存中,以准备重新对view进行排版 detachAndScrapAttachedViews(recycler) var autualHeiht = 0 for (i in 0 until itemCount) { //初始化,将在屏幕内的view填充 val itemView = recycler.getViewForPosition(i) addView(itemView) //测量itemView的宽高 measureChildWithMargins(itemView, 0, 0) val width = getDecoratedMeasuredWidth(itemView) val height = getDecoratedMeasuredHeight(itemView) //根据itemView的宽高进行布局 layoutDecorated(itemView, 0, autualHeiht, width, autualHeiht + height) autualHeiht += height //如果当前布局过的itemView的宽度总和大于RecyclerView的宽,则不再进行布局 if (autualHeiht > getHeight()) { break } } } override fun scrollVerticallyBy( dy: Int, recycler: RecyclerView.Recycler, state: RecyclerView.State ): Int { //1.上下滑动的时候,填充子view val travl = fill(dy, recycler, state) if (travl == 0) { return 0 } Log.e( TAG, "scrollHorizontallyBy: $travl" ) //2.滚动 offsetChildrenVertical(travl * -1) //3.回收已经离开界面的 recyclerHideView(dy, recycler, state) return travl } /** * 上下滑动的时候,填充 */ private fun fill( dx: Int, recycler: RecyclerView.Recycler, state: RecyclerView.State ): Int { var dx = dx Log.e( TAG, "fill: $dx" ) if (dx > 0) { //标注1.向上滚动 val lastView = getChildAt(childCount - 1) ?: return 0 val lastPos = getPosition(lastView) Log.e( TAG, "scrollHorizontallyBy: lastPos==" + lastPos + "==" + lastView.bottom + "==----==" + lastView.top + "===" + height ) //标注2.可见的最后一个itemView完全滑进来了,需要补充新的 if (lastView.top < height) { var scrap: View? = null //标注3.判断可见的最后一个itemView的索引, // 如果是最后一个,则将下一个itemView设置为第一个,否则设置为当前索引的下一个 if (lastPos == itemCount - 1) { if (looperEnable) { scrap = recycler.getViewForPosition(0) } else { dx = 0 } } else { scrap = recycler.getViewForPosition(lastPos + 1) } if (scrap == null) { return dx } //标注4.将新的itemViewadd进来并对其测量和布局 addView(scrap) measureChildWithMargins(scrap, 0, 0) val width = getDecoratedMeasuredWidth(scrap) val height = getDecoratedMeasuredHeight(scrap) layoutDecorated( scrap, 0, lastView.bottom, width, lastView.bottom + height ) return dx } } else { //向下滚动 val firstView = getChildAt(0) ?: return 0 val firstPos = getPosition(firstView) if (firstView.top >= 0) { Log.e( TAG, "scrollHorizontallyBy: firstPos=" + firstPos + "==" + firstView.top + "==" + firstView.bottom ) var scrap: View? = null if (firstPos == 0) { if (looperEnable) { scrap = recycler.getViewForPosition(itemCount - 1) } else { dx = 0 } } else { scrap = recycler.getViewForPosition(firstPos - 1) } if (scrap == null) { return 0 } addView(scrap, 0) measureChildWithMargins(scrap, 0, 0) val width = getDecoratedMeasuredWidth(scrap) val height = getDecoratedMeasuredHeight(scrap) layoutDecorated( scrap, 0, firstView.top - height, width, firstView.top ) } } Log.e( TAG, "scrollHorizontallyBy: dx=dx==$dx" ) return dx } /** * 回收界面不可见的view */ private fun recyclerHideView( dx: Int, recycler: RecyclerView.Recycler, state: RecyclerView.State ) { Log.e( TAG, "recyclerHideView: $dx" ) for (i in 0 until childCount) { val view = getChildAt(i) ?: continue Log.e( TAG, "recyclerHideView: " + view.top + "===" + view.bottom ) if (dx > 0) { //向上滚动,移除一个左边不在内容里的view if (view.bottom < 0) { removeAndRecycleView(view, recycler) Log.d( TAG, "循环: 移除 一个view childCount=$childCount" ) } } else { //向下滚动,移除一个右边不在内容里的view if (view.top > height) { removeAndRecycleView(view, recycler) Log.d( TAG, "循环: 移除 一个view childCount=$childCount" ) } } } } override fun smoothScrollToPosition( recyclerView: RecyclerView, state: RecyclerView.State, position: Int ) { val linearSmoothScroller: LinearSmoothScroller = object : LinearSmoothScroller(recyclerView.context) { private val MILLISECONDS_PER_INCH = 200f override fun computeScrollVectorForPosition(targetPosition: Int): PointF? { return this@CustomLinearLayoutManager .computeScrollVectorForPosition(targetPosition) } override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics): Float { return MILLISECONDS_PER_INCH / displayMetrics.densityDpi } } linearSmoothScroller.targetPosition = position startSmoothScroll(linearSmoothScroller) } }
3、自动滚动的RV
/** * <pre> * @author: Meng * --->time: 2020/12/22 * ---> dec: 自动滚动的Rv * 实现的 LifecycleObserver 自动管理 countDownTimer 的 状态 * <pre> */ class AutoPollRecyclerView(context: Context, @Nullable attrs: AttributeSet?) : RecyclerView(context, attrs), LifecycleObserver { private val TAG = "AutoPollRecyclerView" @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) private fun resume() { countDownTimer.start() } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) private fun stop() { countDownTimer.cancel() } private val countDownTimer = object : CountDownTimer(Integer.MAX_VALUE.toLong(), 2000) { override fun onFinish() { } override fun onTick(millisUntilFinished: Long) { this@AutoPollRecyclerView.post { this@AutoPollRecyclerView.smoothScrollBy( 0, this@AutoPollRecyclerView.getChildAt(0).height ) } } } override fun dispatchTouchEvent(ev: MotionEvent): Boolean { when (ev.action) { MotionEvent.ACTION_DOWN -> { stop() } MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_OUTSIDE -> { resume() } } return super.dispatchTouchEvent(ev) } // 实现渐变效果 var mPaint: Paint? = null private var layerId = 0 private var linearGradient: LinearGradient? = null private var preWidth = 0 // Recyclerview宽度动态变化时,监听每一次的宽度 fun doTopGradualEffect(itemViewWidth: Int) { mPaint = Paint() // dst_in 模式,实现底层透明度随上层透明度进行同步显示(即上层为透明时,下层就透明,并不是上层覆盖下层) val xfermode: Xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN) mPaint!!.setXfermode(xfermode) addItemDecoration(object : ItemDecoration() { override fun onDrawOver(canvas: Canvas, parent: RecyclerView, state: State) { super.onDrawOver(canvas, parent, state) // 当linearGradient为空即第一次绘制 或 Recyclerview宽度发生改变时,重新计算透明位置 if (linearGradient == null || preWidth != parent.width) { // 透明位置从最后一个 itemView 的一半处到 Recyclerview 的最右边 linearGradient = LinearGradient( (parent.width - itemViewWidth / 2).toFloat(), 0.0f, parent.width.toFloat(), 0.0f, intArrayOf(Color.BLACK, 0), null, Shader.TileMode.CLAMP ) preWidth = parent.width } mPaint?.setXfermode(xfermode) mPaint?.setShader(linearGradient) canvas.drawRect( 0.0f, 0.0f, parent.right.toFloat(), parent.bottom.toFloat(), mPaint!! ) mPaint?.setXfermode(null) canvas.restoreToCount(layerId) } override fun onDraw(c: Canvas, parent: RecyclerView, state: State) { super.onDraw(c, parent, state) // 此处 Paint的参数这里传的null, 在传入 mPaint 时会出现第一次打开黑屏闪现的问题 // 注意 saveLayer 不能省也不能移动到onDrawOver方法里 layerId = c.saveLayer( 0.0f, 0.0f, parent.width.toFloat(), parent.height.toFloat(), null, Canvas.ALL_SAVE_FLAG ) } override fun getItemOffsets( outRect: Rect, view: View, parent: RecyclerView, state: State ) { super.getItemOffsets(outRect, view, parent, state) } }) } }
4、代码调用
val scrollSpeedLinearLayoutManger = CustomLinearLayoutManager(this) parentJoinClassStepRv.adapter = mAdp parentJoinClassStepRv.layoutManager =scrollSpeedLinearLayoutManger //生命周期关联 lifecycle.addObserver(parentJoinClassStepRv)