天天看點

實作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) {

            [email protected] {
                [email protected](
                    0,
                    [email protected](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)
           

參考連結 :https://blog.csdn.net/user11223344abc/article/details/78080671