欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

RecycleView的操作(自定义SnapHelper、ItemDecoration)

程序员文章站 2022-07-05 09:33:28
设置RecycleView的Item每次滑动的个数通过重写SnapHelper来实现RecycleView每次滑动的Item个数,使用方法: PagerSnapHelper( 6 ).attachToRecyclerView(Rv)import android.view.Viewimport androidx.recyclerview.widget.OrientationHelperimport androidx.recyclerview.widget.RecyclerViewimp...

设置RecycleView的Item每次滑动的个数

通过重写SnapHelper来实现RecycleView每次滑动的Item个数,
使用方法: PagerSnapHelper( 6 ).attachToRecyclerView(Rv)


import android.view.View
import androidx.recyclerview.widget.OrientationHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SnapHelper

/**
 * <pre>
 *  @author: Meng
 * --->time: 2020/11/27
 * ---> dec:
 *@param itemCount 每次滑动Item个数
 * <pre>
 */
class PagerSnapHelper(private val itemCount: Int) : SnapHelper() {
    private val TAG = "PagerSnapHelper"

    //垂直滑动
    private var mVerticalHelper: OrientationHelper? = null

    //水平滑动
    private var mHorizontalHelper: OrientationHelper? = null

    //款
    private var mRecyclerViewWidth = 0

    //高
    private var mRecyclerViewHeight = 0

    //需要滑动距离
    private var mCurrentScrolledX = 0
    private var mCurrentScrolledY = 0

    //滑动距离
    private var mScrolledX = 0
    private var mScrolledY = 0

    //防止惯性滑动
    private var mFlung = false

    /**
     * RecyclerView 滑动监听器 获取滑动总距离  
     */
    private val mScrollListener = object : RecyclerView.OnScrollListener() {
        private var scrolledByUser = false

        override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
            if (newState == RecyclerView.SCROLL_STATE_DRAGGING) scrolledByUser = true
            if (newState == RecyclerView.SCROLL_STATE_IDLE && scrolledByUser) {
                scrolledByUser = false
            }
        }

        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            mScrolledX += dx
            mScrolledY += dy
            if (scrolledByUser) {
                mCurrentScrolledX += dx
                mCurrentScrolledY += dy
            }
        }
    }

    override fun attachToRecyclerView(recyclerView: RecyclerView?) {
        super.attachToRecyclerView(recyclerView)
        recyclerView?.run {
            addOnScrollListener(mScrollListener)
            post {
                println("$TAG RecyclerViewWidth: $width===$height")

                mRecyclerViewWidth = width
                mRecyclerViewHeight = height
            }
        }
    }

    /**
     * Override this method to snap to a particular point within the target view or the container
     * view on any axis.
     * <p>
     * This method is called when the {@link SnapHelper} has intercepted a fling and it needs
     * to know the exact distance required to scroll by in order to snap to the target view.
     *
     * @param layoutManager the {@link RecyclerView.LayoutManager} associated with the attached
     *                      {@link RecyclerView}
     * @param targetView the target view that is chosen as the view to snap
     *
     * @return the output coordinates the put the result into. out[0] is the distance
     * on horizontal axis and out[1] is the distance on vertical axis.
     *
     * 找到需要对齐的 ItemView
     * @TODO out[0] 是水平轴的距离 (x)   out[1] 垂直轴的距离(y)
     *
     */
    override fun calculateDistanceToFinalSnap(
        layoutManager: RecyclerView.LayoutManager,
        targetView: View
    ): IntArray? {
        val out = IntArray(2)
        if (layoutManager.canScrollHorizontally()) {
            out[0] = distanceToStart(targetView, getHorizontalHelper(layoutManager))
            out[1] = 0
        } else if (layoutManager.canScrollVertically()) {
            out[0] = 0
            out[1] = distanceToStart(targetView, getVerticalHelper(layoutManager))
        }
        return out
    }

    private fun distanceToStart(targetView: View, orientationHelper: OrientationHelper): Int {
        return orientationHelper.getDecoratedStart(targetView) - orientationHelper.startAfterPadding
    }

    /**
     * Override to provide a particular adapter target position for snapping.
     *
     * @param layoutManager the {@link RecyclerView.LayoutManager} associated with the attached
     *                      {@link RecyclerView}
     * @param velocityX fling velocity on the horizontal axis
     * @param velocityY fling velocity on the vertical axis
     *
     * @return the target adapter position to you want to snap or {@link RecyclerView#NO_POSITION}
     *         if no snapping should happen
     *
     *计算 snapView 到要对齐的位置之间的距离。
     */
    override fun findTargetSnapPosition(
        layoutManager: RecyclerView.LayoutManager?,
        velocityX: Int,
        velocityY: Int
    ): Int {
        //获取当前滑动到的位置 @TODO targetPosition如果为-1代表滑动到头了  不能在进行滑动
        val targetPosition = getTargetPosition()
        mFlung = targetPosition != RecyclerView.NO_POSITION
        println("$TAG findTargetSnapPosition, pos: $targetPosition")
        return targetPosition
    }

    /**
     *
     * Override this method to provide a particular target view for snapping.
     * <p>
     * This method is called when the {@link SnapHelper} is ready to start snapping and requires
     * a target view to snap to. It will be explicitly called when the scroll state becomes idle
     * after a scroll. It will also be called when the {@link SnapHelper} is preparing to snap
     * after a fling and requires a reference view from the current set of child views.
     * <p>
     * If this method returns {@code null}, SnapHelper will not snap to any view.
     *
     * @param layoutManager the {@link RecyclerView.LayoutManager} associated with the attached
     *                      {@link RecyclerView}
     *
     * @return the target view to which to snap on fling or end of scroll
     *
     * 是用来帮助对齐 ItemView 的
     */
    override fun findSnapView(layoutManager: RecyclerView.LayoutManager?): View? {
        if (mFlung) {
            resetCurrentScrolled()
            mFlung = false
            return null
        }
        if (layoutManager == null) return null
        val targetPosition = getTargetPosition()
        println("$TAG findSnapView, pos: $targetPosition")
        if (targetPosition == RecyclerView.NO_POSITION) return null
        layoutManager.startSmoothScroll(createScroller(layoutManager).apply {
            this?.targetPosition = targetPosition
        })
        return null
    }

    private fun getTargetPosition(): Int {
        println("$TAG getTargetPosition, mScrolledX: $mScrolledX, mCurrentScrolledX: $mCurrentScrolledX")
        /**
         * @param page 获取需要滑动Item个数
         */
        val page = when {
            mCurrentScrolledX > 0 -> mScrolledX / mRecyclerViewWidth + 1
            mCurrentScrolledX < 0 -> mScrolledX / mRecyclerViewWidth
            mCurrentScrolledY > 0 -> mScrolledY / mRecyclerViewHeight + 1
            mCurrentScrolledY < 0 -> mScrolledY / mRecyclerViewHeight
            else -> RecyclerView.NO_POSITION
        }
        resetCurrentScrolled()
        return if (page == RecyclerView.NO_POSITION) RecyclerView.NO_POSITION else page * itemCount
    }

    //重新赋值xy
    private fun resetCurrentScrolled() {
        mCurrentScrolledX = 0
        mCurrentScrolledY = 0
    }

    private fun getVerticalHelper(layoutManager: RecyclerView.LayoutManager): OrientationHelper {
        if (mVerticalHelper == null || mVerticalHelper!!.layoutManager !== layoutManager) {
            mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager)
        }
        return mVerticalHelper!!
    }

    private fun getHorizontalHelper(layoutManager: RecyclerView.LayoutManager): OrientationHelper {
        if (mHorizontalHelper == null || mHorizontalHelper!!.layoutManager !== layoutManager) {
            mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager)
        }
        return mHorizontalHelper!!
    }
}

每次滑动一整屏

和 PagerSnapHelper 使用方法一样。原理也差不多


import androidx.recyclerview.widget.RecyclerView
import kotlin.math.absoluteValue

/**
 * <pre>
 *  @author: Meng
 * --->time: 2020/11/27
 * ---> dec:   每次滑动一整屏
 *   会出现回弹效果  在第一屏滑动快速滑动到第三瓶 会回滚会第二瓶
 * <pre>
 */
class FullPagerSnapHelper {
    private var mRecyclerView: RecyclerView? = null
    private var mHalfItemWidth = 0
    private var mFullPageWidth = 0
    private var mScrolled = false

    private val mScrollListener = object : RecyclerView.OnScrollListener() {
        private var mTotalScrolledX = 0
        private var mTotalScrolledY = 0

        override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
            super.onScrollStateChanged(recyclerView, newState)
            println("tag=== onScrollStateChanged, state: $newState")
            if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
                mScrolled = true
            }
            if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                if (mScrolled) {
                    mScrolled = false
                    if (mTotalScrolledX.absoluteValue > 0 || mTotalScrolledY.absoluteValue > 0) {
                        snapToTargetExistingView(mTotalScrolledX, mTotalScrolledY)
                    }
                }
                mTotalScrolledX = 0
                mTotalScrolledY = 0
            }
        }

        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            mTotalScrolledX += dx
            mTotalScrolledY += dy
        }
    }

    private val onFlingListener = object : RecyclerView.OnFlingListener() {
        //会出现回弹效果  在第一屏滑动快速滑动到第三瓶 会回滚会第二瓶 
        override fun onFling(velocityX: Int, velocityY: Int): Boolean {
            println("tag=== onFling, vX: $velocityX, vY: $velocityY")
            return false
        }
    }

    @Throws(IllegalStateException::class)
    fun attachToRecyclerView(recyclerView: RecyclerView) {
        recyclerView.post {
            mFullPageWidth = recyclerView.measuredWidth
            mHalfItemWidth = recyclerView.measuredWidth / 6
            println("tag=== full: $mFullPageWidth, half: $mHalfItemWidth")
        }

        if (mRecyclerView != null) {
            destroyCallbacks()
        }
        mRecyclerView = recyclerView
        if (mRecyclerView != null) {
            setupCallbacks()
        }
    }

    @Throws(IllegalStateException::class)
    private fun setupCallbacks() {
        mRecyclerView?.addOnScrollListener(mScrollListener)
        mRecyclerView?.onFlingListener = onFlingListener
    }

    private fun destroyCallbacks() {
        mRecyclerView?.removeOnScrollListener(mScrollListener)
        mRecyclerView?.onFlingListener = null
    }

    private fun snapToTargetExistingView(dx: Int, dy: Int) {
        when {
            dx.absoluteValue >= mHalfItemWidth -> mRecyclerView?.smoothScrollBy(
                if (dx > 0) mFullPageWidth - dx else -mFullPageWidth - dx, 0
            )
            dy.absoluteValue >= mHalfItemWidth -> mRecyclerView?.smoothScrollBy(
                0, if (dy > 0) mFullPageWidth - dy else -mFullPageWidth - dy
            )
            else -> mRecyclerView?.smoothScrollBy(-dx, -dy)
        }
    }
}

设置RecycleView每一个Item距离

重写ItemDecoration 原理:就是给RV添加了一条没有颜色的分割线
使用方法:Rv.addItemDecoration(SpaceItemDecoration(SizeUtils.dp2px(50f)))


import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ItemDecoration


/**
 * <pre>
 *  @author: Meng
 *  @time: 2020/11/26
 *  @dec:   Rv设置每一个Item间隔的
 *   @param mSpace 间隔距离 现在是px 开发中需要转成dp
 * <pre>
 */
 class SpaceItemDecoration(var mSpace: Int) : ItemDecoration() {


    override fun getItemOffsets(
        outRect: Rect,
        view: View,
        parent: RecyclerView,
        state: RecyclerView.State
    ) {
        super.getItemOffsets(outRect, view, parent, state)
        outRect.left = mSpace
        outRect.right = mSpace
        outRect.bottom = mSpace
        if (parent.getChildAdapterPosition(view) == 0) {
            outRect.top = mSpace
        }
    }

}

Grid布局分割线


import android.content.Context
import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.view.View
import androidx.recyclerview.widget.RecyclerView

/**
 * <pre>
 *  @author: Meng
 * --->time: 2020/11/27
 * ---> dec:    Grid 布局分割线
 *
 * <pre>
 */
class GridItemDecoration(context: Context) : RecyclerView.ItemDecoration() {
    private val mBounds = Rect()
    private var mDivider: Drawable = ColorDrawable(context.resources.getColor(android.R.color.holo_purple))
    private var mDividerWidth = 10

    /**
     * @param divider 设置分割线样式
     * @param dividerWidth 设置分割线宽度
     */
    fun setDivider(divider: Drawable = mDivider, dividerWidth: Int = mDividerWidth) {
        mDivider = divider
        mDividerWidth = dividerWidth
    }

    override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
        if (parent.layoutManager!!.canScrollHorizontally()) {
            drawHorizontal(c, parent)
        } else if (parent.layoutManager!!.canScrollVertically()) {
            drawVertical(c, parent)
        }
    }

    private fun drawHorizontal(c: Canvas, parent: RecyclerView) {
        c.save()
        val count = parent.childCount
        for (i in 0 until count) {
            val child = parent.getChildAt(i)
            parent.getDecoratedBoundsWithMargins(child, mBounds)
            //画竖线
            mDivider.setBounds(mBounds.right - mDividerWidth, mBounds.top, mBounds.right, mBounds.bottom)
            mDivider.draw(c)
            //画横线
            mDivider.setBounds(mBounds.left, mBounds.bottom - mDividerWidth, mBounds.right, mBounds.bottom)
            mDivider.draw(c)
        }
        c.restore()
    }

    private fun drawVertical(c: Canvas, parent: RecyclerView) {
        c.save()
        val count = parent.childCount
        for (i in 0 until count) {
            val child = parent.getChildAt(i)
            parent.getDecoratedBoundsWithMargins(child, mBounds)
            //画横线
            mDivider.setBounds(mBounds.left, mBounds.bottom - mDividerWidth, mBounds.right, mBounds.bottom)
            //画竖线
            mDivider.setBounds(mBounds.right - mDividerWidth, mBounds.top, mBounds.right, mBounds.bottom)
            mDivider.draw(c)
        }
        c.restore()
    }

    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
        outRect.set(0, 0, mDividerWidth, mDividerWidth)
    }
}

结束

本文地址:https://blog.csdn.net/weixin_41620505/article/details/110219315