实现RecycleView横向、竖向无限循坏(基于自定义RecyclerView.LayoutManager)

简介: 实现RecycleView横向、竖向无限循坏(基于自定义RecyclerView.LayoutManager)

实现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)
目录
相关文章
|
Android开发
【RecyclerView】 五、RecyclerView 布局 ( 瀑布流 | 交错网格局管理器 StaggeredGridLayoutManager )
【RecyclerView】 五、RecyclerView 布局 ( 瀑布流 | 交错网格局管理器 StaggeredGridLayoutManager )
688 0
【RecyclerView】 五、RecyclerView 布局 ( 瀑布流 | 交错网格局管理器 StaggeredGridLayoutManager )
RecyclerView GridView模式同一行,使其高度平齐,自动适应高度最大item
RecyclerView GridView模式同一行,使其高度平齐,自动适应高度最大item
292 0
|
Android开发
【RecyclerView】 九、为 RecyclerView 设置不同的布局样式
【RecyclerView】 九、为 RecyclerView 设置不同的布局样式
284 0
【RecyclerView】 九、为 RecyclerView 设置不同的布局样式
RecyclerView 实现纵向,横向,和瀑布流 的滚动布局
RecyclerView 实现纵向,横向,和瀑布流 的滚动布局
RecyclerView 实现纵向,横向,和瀑布流 的滚动布局
实用技巧 | RecyclerView 设置最大高度
实用技巧 | RecyclerView 设置最大高度
|
Android开发
Android HorizontalScrollView 横向滚动自动居中
Android HorizontalScrollView 横向滚动自动居中
SwiftUI—创建一个水平方向上的滚动视图
SwiftUI—创建一个水平方向上的滚动视图
205 0
SwiftUI—创建一个水平方向上的滚动视图
|
Android开发 Java 数据格式