RecycleView的操作(自定义SnapHelper、ItemDecoration)
设置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) } }