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