RecyclerView smoothScrollToPosition知多少?

简介: RecyclerView smoothScrollToPosition知多少?

1.前言


最近有个同学要实现如下效果,点击TabRecyclerView会让Tab标签对应的第一个Item显示在RecyclerView的顶部。他通过RecyclerView.scrollToPositionWithOffset()实现了该效果,但是UI同学希望有一个平滑滚动效果,说到平滑滚动,大家也都知道RecyclerViewsmoothScrollToPosition()方法。由于TitleBarTab标签栏覆盖在RecyclerView上的,所以滚动需要加上偏移量才行。但是smoothScroll相关的方法偏偏没有偏移量相关的重载方法。最终效果图如下:

image.png


2.问题分析


LinearLayoutManagerStaggeredGridLayoutManager有三个方法可以实现滚动到指定位置效果:

  1. scrollToPosition(int position)
  2. scrollToPositionWithOffset(int position, int offset)
  3. 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. 解决方案



图片版本代码

640.png

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);
}

效果如下,平滑地将Item20滚动到RV顶部,并且留出了100px的offset:

640.gif


4. 深入分析


Android系统提供的三个scrollToPosition相关的方法加上自己实现的

smoothScrollToPositionWithOffset。我们得到了四个scrollToPosition方法


方法
scrollToPosition
scrollToPositionWithOffset
smoothScrollToPosition
smoothScrollToPositionWithOffset


那么他们之间的区别是什么呢?我将从以下两个维度简单分析一下:

  1. 是否开启smooth效果
  2. 是否开启offset效果


4.1 是否开启smooth效果


//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

640.png

更多关于RecyclerView布局原理请查看深入理解RecyclerView布局原理

640.png

smoothScrollToPosition 是通过调用SmoothScroller的start方法,模拟fling操作,动态找寻目标position的view,如果找到了则定位到顶部,调用栈如下:


➡️RecyclerView$SmoothScroller.start()

➡️RecyclerView$ViewFlinger.run()

➡️RecyclerView$SmoothScroller.onAnimation()

➡️RecyclerView$SmoothScroller.onTargetFound()

➡️RecyclerViewAction.update()

640.png

该方法作用:


  1. 计算位置,滚动
  2. 如果找到了目标view,调用onTargetFound


mTargetView赋值时机是,RecyclerView滚动过程中调用LayoutManager.addViewInt方法时。

640.png


4.1 是否开启offset效果


是否开启offset的区别是:


如果开启了offset,目标position的view无论是否在屏幕内,无论是在当前位置的上方还是下方,都会滚动到屏幕的顶部,而如果调用的是scrollToPosition,如果view已经在屏幕内,则不会有任何效果。如果目标position在屏幕下方,布局会从屏幕底部开始,如果目标position在屏幕上方,布局会从屏幕顶部开始。

相关文章
|
Android开发 前端开发
Android应用开发—RecyclerView绘制蒙层
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/voidreturn/article/details/77718470 背景:如何在跨越两个或两个以上的item绘制一个view,该view需要跟随recyclerView的滑动而整体移动。
1146 0
|
Android开发
Android RecyclerView实现吸顶动态效果,详细分析
Android RecyclerView实现吸顶动态效果,详细分析
自己动手写RecyclerView的上拉加载
自己动手写RecyclerView的上拉加载
|
XML 存储 Java
Fragment 知识梳理, FragmentPagerAdapter ,RecyclerView 知识梳理,sharepreference,IntentSer
Fragment 知识梳理, FragmentPagerAdapter ,RecyclerView 知识梳理,sharepreference,IntentSer
105 0
Fragment 知识梳理, FragmentPagerAdapter ,RecyclerView 知识梳理,sharepreference,IntentSer
|
缓存 Android开发
RecyclerView高级进阶之优雅地解决瀑布流的两个神坑
RecyclerView高级进阶之优雅地解决瀑布流的两个神坑
RecyclerView高级进阶之优雅地解决瀑布流的两个神坑
|
算法 UED
了解AppBarLayout应该从这几个方面入手
了解AppBarLayout应该从这几个方面入手
还在为ScrollView嵌套RecyclerView而发愁吗?
还在为ScrollView嵌套RecyclerView而发愁吗?
182 0