内容简介:我们在开发一个列表的时候,有时候会需要实现列表整页滑动的效果。列表的实现大家应该都会使用我们就借助
我们在开发一个列表的时候,有时候会需要实现列表整页滑动的效果。列表的实现大家应该都会使用 RecyclerView
,但 RecyclerView
原生是不支持整页滑动的。最近 RecyclerView
添加了 SnapHelper
的 API,它是用来帮助实现 ItemView
的对齐,SDK 默认实现了 LinearSnapHelper
和 PagerSnapHelper
,分别用来实现居中对齐和每次滑动一个 ItemView
的效果。
我们就借助 SnapHelper
的原理来实现一个可以整页滑动的 RecyclerView
。效果如下所示:
SnapHelper
介绍
SnapHelper
是用来帮助对齐 ItemView
的,继承 SnapHelper
我们需要实现三个方法,分别是
View findSnapView(RecyclerView.LayoutManager layoutManager) 复制代码
找到需要对齐的 ItemView
,我们这里称为 snapView
。
int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) 复制代码
计算 snapView
到要对齐的位置之间的距离。
int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) 复制代码
找到要对齐的 ItemView
在 Adapter
里面的 position
。
如上图所示,假设我们每次需要滑动一页: snapView
就是需要对齐的 ItemView
,对应 findSnapView()
的返回值; SnapView
在 Adapter
中的 position
就是需要对齐的位置,对应 findTargetSnapPosition()
的返回值; snap distance
就是对齐需要滑动的距离,对应 calculateDistanceToFinalSnap()
的返回值。
PagerSnapHelper
实现
我们要实现一个可以整页滑动的 SnapHelper
。
- 首先我们需要找到需要对齐的
ItemView
:
override fun findSnapView(layoutManager: RecyclerView.LayoutManager?): View? { if (mFlung) { resetCurrentScrolled() mFlung = false return null } if (layoutManager == null) return null // 首先找到需要对齐的 ItemView 在 Adapter 中的位置 val targetPosition = getTargetPosition() println("$TAG findSnapView, pos: $targetPosition") if (targetPosition == RecyclerView.NO_POSITION) return null // 正常情况下,我们在这里通过 layoutManager.findViewByPosition(int pos) 返回 view 即可,但会存在两个问题: // 1. 如果这个位置的 view 还没有 layout 的话,会返回 null,达不到对齐的效果; // 2. 即使 view 不为空,但滑动速度会不一致,后面会讲到; // 所以在这里,我们把 position 传递给 LinearSmoothScroller,让它帮助我们滑动到指定位置。 layoutManager.startSmoothScroll(createScroller(layoutManager).apply { this?.targetPosition = targetPosition }) return null } 复制代码
LinearSmoothScroller
可以设置一个 targetPosition
,然后调用 layoutManager.startSmoothScroll(LinearSmoothScroller scroller)
,它会帮助我们自动把 targetPosition
对应的 ItemView
对齐到边界,默认是左对齐,和我们需求一致。
private fun getTargetPosition(): Int { println("$TAG getTargetPosition, mScrolledX: $mScrolledX, mCurrentScrolledX: $mCurrentScrolledX") val page = when { mCurrentScrolledX > 0 -> mScrolledX / mRecyclerViewWidth + 1 mCurrentScrolledX < 0 -> mScrolledX / mRecyclerViewWidth else -> RecyclerView.NO_POSITION } resetCurrentScrolled() return (if (page == RecyclerView.NO_POSITION) RecyclerView.NO_POSITION else page * itemCount) } 复制代码
getTargetPosition()
就是根据 RecyclerView
滑动的距离和方向,找出滑动一页后,需要对齐的 ItemView
的位置。
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 } } } 复制代码
mScrolledX
、 mScrolledY
就是 RecyclerView
滑动的总距离, mCurrentScrolledX
、 mCurrentScrolledY
就是 RecyclerView
本次滑动的距离,用来判断 RecyclerView
滑动的方向。
- 找出需要对齐的
ItemView
在Adapter
中的位置:
override fun findTargetSnapPosition( layoutManager: RecyclerView.LayoutManager?, velocityX: Int, velocityY: Int ): Int { val targetPosition = getTargetPosition() mFlung = targetPosition != RecyclerView.NO_POSITION println("$TAG findTargetSnapPosition, pos: $targetPosition") return targetPosition } 复制代码
很简单,就是 getTargetPosition()
返回的值。解释一点, findTargetSnapPosition()
方法只有在 RecyclerView
触发 fling
的时候才会调用。 SnapHelper
内部也是使用的 LinearSmoothScroller
实现的滑动,设置的 targetPosition
就是 getTargetPosition()
的返回值。这也解释了我们为什么不在 findSnapView()
方法中直接返回 snapView
,就是为了保持滑动速度的一致。
- 计算需要滑动的距离
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 } 复制代码
解释一点, OrientationHelper
可以很方便地帮助我们计算 ItemView
的位置。
本文章对应的示例已上传到 GitHub
, 点击这里查看
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。