本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《
阿里云开发者社区用户服务协议
》和
《
阿里云开发者社区知识产权保护指引
》。如果您发现本社区中有涉嫌抄袭的内容,填写
侵权投诉表单
进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。
最近有个同学要实现如下效果,点击
Tab
,
RecyclerView
会让
Tab
标签对应的第一个
Item
显示在
RecyclerView
的顶部。他通过
RecyclerView.scrollToPositionWithOffset()
实现了该效果,但是UI同学希望有一个平滑滚动效果,说到平滑滚动,大家也都知道
RecyclerView
有
smoothScrollToPosition()
方法。由于
TitleBar
和
Tab
标签栏覆盖在
RecyclerView上
的,所以滚动需要加上偏移量才行。但是
smoothScroll
相关的方法偏偏没有偏移量相关的重载方法。最终效果图如下:
LinearLayoutManager
和
StaggeredGridLayoutManager
有三个方法可以实现滚动到指定位置效果:
scrollToPosition(int position)
scrollToPositionWithOffset(int position, int offset)
smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position)
首先
方法1和方法2的区别在于offset偏移量。scrollToPosition的作用是把position对应的ItemView放置到RecyclerView的顶部。scrollToPositionWithOffset在scrollToPosition的基础上还可以偏移指定的距离,当RecyclerView的顶部被遮挡的时候,我们就需要通过偏移方法来将遮挡的部分露出来。
其次
smoothScrollToPosition与scrollToPosition的区别是,后者根据计算,直接以position为锚点重新布局RecyclerView,给用户的视觉感觉是
非常突兀,没有过渡效果
,smoothScrollToPosition会从当前位置,发出类似fling的动作,fling到目标position处,它的优点是
平滑过渡,用户体验好
,但是它的缺点是
如果当前position离目标position比较远,由于每个Item都需要渲染出来,如果RV优化效果不好,会造成卡顿
。
最后
我们发现smoothScrollToPosition方法没有类似
smoothScrollToPositionWithOffset
的方法。那么我既想要
平滑滚动
又想要
带偏移量滚动
该怎么办呢?
3. 解决方案
private fun RecyclerView.smoothScrollToPositionWithOffset(position: Int, offset: Int) {
val linearSmoothScroller = object : LinearSmoothScroller(context) {
override fun onTargetFound(targetView: View, state: RecyclerView.State, action: Action) {
super.onTargetFound(targetView, state, action)
val dx = calculateDxToMakeVisible(targetView, horizontalSnapPreference)
val dy = calculateDyToMakeVisible(targetView, SNAP_TO_START)
val distance = sqrt((dx * dx + dy * dy).toDouble()).toInt()
val time = calculateTimeForDeceleration(distance)
if (time > 0) {
action.update(-dx, -dy - offset, time, mDecelerateInterpolator)
linearSmoothScroller.targetPosition = position
layoutManager?.startSmoothScroll(linearSmoothScroller)
fun smoothScrollToWithOffset(view: View) {
mRecyclerView.smoothScrollToPositionWithOffset(20, 100);
//recyclerview-1.2.0 LinearLayoutManager.java
@Override
public void scrollToPosition(int position) {
mPendingScrollPosition = position;
mPendingScrollPositionOffset = INVALID_OFFSET;
if (mPendingSavedState != null) {
mPendingSavedState.invalidateAnchor();
requestLayout();
//recyclerview-1.2.0 LinearLayoutManager.java
@Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
int position) {
LinearSmoothScroller linearSmoothScroller =
new LinearSmoothScroller(recyclerView.getContext());
linearSmoothScroller.setTargetPosition(position);
startSmoothScroll(linearSmoothScroller);
scrollToPosition 是通过修改mPendingScrollPosition变量,以该变量为锚点,重新布局,调用栈如下:
➡️LinearLayoutManager.onLayoutChildren
➡️LinearLayoutManager.updateAnchorInfoForLayout
➡️LinearLayoutManager.updateAnchorFromPendingData
smoothScrollToPosition 是通过调用SmoothScroller的start方法,模拟fling操作,动态找寻目标position的view,如果找到了则定位到顶部,调用栈如下:
➡️RecyclerView$SmoothScroller.start()
➡️RecyclerView$ViewFlinger.run()
➡️RecyclerView$SmoothScroller.onAnimation()
➡️RecyclerView$SmoothScroller.onTargetFound()
➡️RecyclerViewAction.update()