相关文章推荐
腹黑的墨镜  ·  cmd | Microsoft Learn·  4 周前    · 
腹黑的饭卡  ·  android ...·  1 年前    · 
本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《 阿里云开发者社区用户服务协议 》和 《 阿里云开发者社区知识产权保护指引 》。如果您发现本社区中有涉嫌抄袭的内容,填写 侵权投诉表单 进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

最近有个同学要实现如下效果,点击 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()