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在屏幕上方,布局会从屏幕顶部开始。

相关文章
|
数据采集 Python
Python爬虫:实现爬取、下载网站数据的几种方法
Python爬虫:实现爬取、下载网站数据的几种方法
1408 1
|
5月前
|
XML 编解码 Android开发
非常经典的Android开发问题-mipmap图标目录和drawable图标目录的区别和适用场景实战举例-优雅草卓伊凡
非常经典的Android开发问题-mipmap图标目录和drawable图标目录的区别和适用场景实战举例-优雅草卓伊凡
436 0
非常经典的Android开发问题-mipmap图标目录和drawable图标目录的区别和适用场景实战举例-优雅草卓伊凡
|
运维 Java 虚拟化
《docker基础篇:1.Docker简介》,包括Docker是什么、容器与虚拟机比较、能干嘛、去哪下
《docker基础篇:1.Docker简介》,包括Docker是什么、容器与虚拟机比较、能干嘛、去哪下
474 12
|
数据挖掘 数据处理 Python
Pandas去掉所有的百分号并转换成小数
在数据处理中,特别是在财务和统计分析中,经常会遇到带有百分号的数据。为了进行准确的计算,需要将这些百分比数据转换为小数形式。本文介绍了如何在Pandas DataFrame中快速去除所有百分号,并将这些值转换为小数,以便进行进一步的数值计算和分析。
690 2
|
Android开发 开发者 Kotlin
Android 多进程情况下判断应用是否处于前台或者后台
本文介绍在多进程环境下判断Android应用前后台状态的方法。通过`ActivityManager`和服务信息`RunningAppProcessInfo`可有效检测应用状态,优化资源使用。提供Kotlin代码示例,帮助开发者轻松集成。
1298 8
|
Java 调度 Android开发
Android面试题之Kotlin中async 和 await实现并发的原理和面试总结
本文首发于公众号“AntDream”,详细解析了Kotlin协程中`async`与`await`的原理及其非阻塞特性,并提供了相关面试题及答案。协程作为轻量级线程,由Kotlin运行时库管理,`async`用于启动协程并返回`Deferred`对象,`await`则用于等待该对象完成并获取结果。文章还探讨了协程与传统线程的区别,并展示了如何取消协程任务及正确释放资源。
472 0
|
Java
新版minio依赖对putObject进行了修改
本文介绍了MinIO客户端在新版中对`putObject`方法的修改,从旧版的直接传参到新版的链式调用构建器模式。
748 2
|
JavaScript 前端开发 测试技术
精通Selenium:从基础到高级的网页自动化测试策略
【10月更文挑战第6天】随着Web应用变得越来越复杂,手动进行功能和兼容性测试变得既耗时又容易出错。自动化测试因此成为了现代软件开发不可或缺的一部分。Selenium是一个强大的工具集,它支持多种编程语言(包括Python),允许开发者编写脚本来模拟用户与Web页面的交互。本文将带领读者从Selenium的基础知识出发,逐步深入到高级的应用场景,通过丰富的代码示例来展示如何高效地进行网页自动化测试。
2200 5
|
测试技术 API Android开发
Android经典实战之简化 Android 相机开发:CameraX 库的全面解析
CameraX是Android Jetpack的一个组件,旨在简化相机应用开发,提供了易于使用的API并支持从Android 5.0(API级别21)起的设备。其主要特性包括广泛的设备兼容性、简洁的API、生命周期感知、简化实现及方便的集成与测试。通过简单的几个步骤即可实现如拍照、视频录制等功能。此外,还提供了最佳实践指导以确保应用的稳定性和性能。
624 0
|
缓存 自然语言处理 监控
elasticsearch过滤器filter:原理及使用
elasticsearch过滤器filter:原理及使用