2024年RecyclerView的smooth scroller -- 诸多案例_snap_to_start,字节跳动三面是什么

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

(这时自然5项是在最底下).
–> PO一般都是想说要滑到最开头

2). 当前页面展示的是7-10项, 这时SS到5, 结果也是滑动到第5项刚好可见
这时项5自然在最开头
–> 综合1)与2), 发现就是表相是滑动方向不同
(一个往下滑到第5项, 一个往上滑到第5项)导致了这个问题.
里子却是一一个"最少滑动量, 导致target项完全显示就停了, 不再SS了"的原则

3). 当前页面展示3-7项时, 这时SS到5, 结果是: 什么都没发生!
是的, 若已完全可见, 那调用smoothScroll都不会有任何事发现.

–> PO一般是想把项5放到最开头去`

总结下, 那就是rv.smoothScrollToPosition(pos)遵循的是滑动最少的原则, 按不同的滑动方向, 只要target position那项item已经完全可见了, 就马上停止滑动; 要是target position已经可见了, 那根本不滑动.

他们的方案都是针对上面的三种做法, 分别调用smoothScroll或是scrollBy来强制变更SS行为, 甚至还要添加OnScrollListener来保证滑动的持续性. (代码可见上面链接; 这里的贴图只是给出个大概的意思)

这样肯定能成功, 但我仍是在想有没有更简单的方案.

经过我的查找, 果然找到了更好的方案, 而且代码只有简单的方案. 其实就是要指定LSS中的snap mode, 我们只要指定其为snap_to_start, 那SS就会自动强制地将target position那项item给放顶端.

具体代码如下:

    private fun RecyclerView.smoothScrollAndSnapStartTo(pos: Int) {
        val scroller = object : LinearSmoothScroller(context) {
            // 若不指定这个SNAP_TO_START, 那默认就是 rv.smoothScrollTo(position)的效果, 即仍有RvScrollToPosition_Issue_Page.kt中所说的几个问题
            override fun getVerticalSnapPreference() = LinearSmoothScroller.SNAP_TO_START
        }
        scroller.targetPosition = pos
        layoutManager?.startSmoothScroll(scroller)
    }

备注: 要是你的rv是一个水平rv, 那就请重写getHorizontalSnapPreference() = SNAP_TO_START

案例四: snap_to_start还有一点点偏移量

好吧, 现在我们的UX给的设计稿是, RV上方(Z轴上)还有一层导航条. 这个导航条占住了rv的top一些部分

<FrameLayout>
     <RecyclerView top = 0/>
     <Buttons top = 0/>

类似这样的效果(即4个button代表的导航条, 位于rv之上):

那这时的SS, 如跳到第5项就不太如意, 因为被遮住了一部分. UX想让target position项能完全展示出来:

SS的原理

这时其实就是要看RV是如何SS的了.

  • 当我们指定要SS到target position去, 那RV就会一直滑动
  • 直到当target position出现了, 上述的普通滑动就完结了, 这时就开始进入了减速期
  • 在减速期里, 滑动(速度与时间)跟刚刚的滑动不一样, 而且要滑动多少能让target position的item正好completely visible, 这些都在LSS的onTargetFound方法的源码里:
// LSS的源码
@override
protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
    final int dx = calculateDxToMakeVisible(targetView, getHorizontalSnapPreference());
    final int dy = calculateDyToMakeVisible(targetView, getVerticalSnapPreference());
    final int distance = (int) Math.sqrt(dx * dx + dy * dy);
    final int time = calculateTimeForDeceleration(distance);
    if (time > 0) {
        action.update(-dx, -dy, time, mDecelerateInterpolator);
    }
}

也就是说, 在上面第二步中, target item出现了时, 这时就马上计算距离最终还要滑动多少, 以及要减速所花的时间. 而这个"要滑动多少", 则在于calculateDxToMakeVisible, calculateDyToMakeVisible两个方法里

计算减速期的滑动距离

calculateDxToMakeVisible, calculateDyToMakeVisible两个方法里其实都是在调用calculateDtToFit方法, 只不过参数不同而已.

其实就可以理解calculateDtToFit方法就是一个计算在SS减速期, 到底还要滑动多少距离的函数.

下面来看LSS的源码, 就能理解calculateDtToFit的使用场景了:

// LSS源码

// 下面的view参数, 就是指rv中某一item
    public int calculateDxToMakeVisible(View view, int snapPreference) {
        ...
        return calculateDtToFit(viewLeft, viewRight, rvLeft, rvRight, snapPreference);
    }
    
    public int calculateDyToMakeVisible(View view, int snapPreference) {
        ...
        return calculateDtToFit(viewTop, viewBottom, rvTop, rvBottom, snapPreference);
    }    

解决方案

现在看完这些源码就知道了, 我们只要在snap_to_start的基础上, 再在calculateDtToFit中提供一定的offset偏移量就行了.

   private fun RecyclerView.smoothScrollWithOffsetTo(pos: Int) {
        val topButtonsHeight = topButtonsLayout.height //=> 126

        val scroller = object : LinearSmoothScroller(context) {

            override fun getVerticalSnapPreference() = LinearSmoothScroller.SNAP_TO_START
            
            override fun calculateDtToFit(viewStart: Int, viewEnd: Int, boxStart: Int, boxEnd: Int, snapPreference: Int): Int {
                val computed = super.calculateDtToFit(viewStart, viewEnd, boxStart, boxEnd, snapPreference)
                if (snapPreference == SNAP_TO_START) {
                    return computed + topButtonsHeight
                }
                return computed
            }
        }
        scroller.targetPosition = pos
        layoutManager?.startSmoothScroll(scroller)
    }

这样当调用rv.smoothScrollWithOffsetTo(7)时, 结果就是:

案例五: 始终让SS的target item出现在页面中间

有时我们确实有这种需求, 比如说你用RV做一个WheelSelection时, 需求就是让selected项居于中间:

可惜的是LSS并没有snap_to_center的preference. 不过也不麻烦, 同样地我们只要重写calculateDtToFit即可:

    private fun RecyclerView.smoothScrollInCenterTo(pos: Int) {
        val scroller = object : LinearSmoothScroller(context) {
            override fun calculateDtToFit(viewStart: Int, viewEnd: Int, boxStart: Int, boxEnd: Int, snapPreference: Int): Int {
                return (boxStart + (boxEnd - boxStart) / 2) - (viewStart + (viewEnd - viewStart) / 2)
            }
        }
        scroller.targetPosition = pos
        layoutManager?.startSmoothScroll(scroller)
    }

这里的参数就是找到中间点的位置, 也就是rv与item的位置一比较, 即得出了中间点.

总结

通过上面四个案例, 我们了解了如何调整Smooth Scroll的速度, 最终位置等. 主要就是重写LinearSmoothScroll的这几个方法:

  • calculateSpeedPerPixel()
  • getVerticalSnapPreference()
  • getHorizontalSnapPreference()
  • calculateDtToFit()

以及了解了LinearSmoothScroll到底是如何进行smooth scroll的, 即 “普通smooth scroll + 当发现了target时就开始减速smooth”. 这个能帮助我们理解何时要重写哪些方法.

Android 学习笔录

Android 性能优化篇:https://qr18.cn/FVlo89
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

44597752)]
[外链图片转存中…(img-gzYS6sp0-1715644597753)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值