Android RecyclerView:假smooth如果有很多项目,请滚动到顶部 [英] Android RecyclerView: Fake smoothScroll to top if many, many items

查看:108
本文介绍了Android RecyclerView:假smooth如果有很多项目,请滚动到顶部的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们的应用程序中有基于标准 RecyclerView 的Feed屏幕.

We have your standard RecyclerView-based Feed screen in our app.

如果我在 RecyclerView 中有100多个项目,然后按转到顶部"快捷方式,则 smoothScrollToPosition(0)的默认行为是使用很长一段时间才能滚动到顶部.

If I have like 100+ items in my RecyclerView and press the 'go to top' shortcut, the default behaviour for smoothScrollToPosition(0) is to take a very long time to scroll to the top.

要花多长时间几乎是可笑的-如果您走得足够远(这是一个常见的用例),则需要10秒的快速滚动到顶部!

It's almost comical how long it can take - 10 seconds of intensely fast scrolling to the top if you've gone down far enough (which is a common use case)!

如果 RecyclerView > SOME_THRESHOLD 中的项目数,我们正在寻找一种将滚动伪造"到顶部的方法.

We're looking for a way to "fake" the scroll to the top, if the number of items in the RecyclerView > SOME_THRESHOLD.

我不是iOS专家,但我们的iOS版本(如开发人员告诉我的那样)似乎已将此类行为植入控件中.如果物品太多,它只会进行超快速的模糊滚动,从而明显地伪造/遗漏了中间的许多物品.

I'm not an iOS guy, but our iOS version (as the devs tell me) seems to have such behaviour baked into the control. If there are too many items, it'll just do a super quick blurry scroll-up which clearly fakes/omits many of the items in the middle.

RecyclerView有任何这样的功能吗?

Does the RecyclerView have any such capabilities?

我们已经考虑过做一个多部分的事情,我们可以快速跳转到索引 SOME_THRESHOLD 的项目,然后 then 调用 smoothScrollToPosition(0)-您知道了-但是我们想到的大多数东西都有缺点.

We've thought of doing a multi-part thing where we quickly jump to the item at index SOME_THRESHOLD and then call smoothScrollToPosition(0) - you get the idea - but there are drawbacks to most of the things that we've thought of.

感谢您的帮助.

推荐答案

控制滚动速度,更快滚动到更远位置的解决方案

聚会晚了一点,但这是任何其他人的Kotlin解决方案.

A bit late to the party, but here's a Kotlin solution for anyone else looking.

事实证明,通过覆盖 LinearSmoothScroller calculateSpeedPerPixel 可以解决棘手的问题.通过尝试解决方案,我遇到了许多"RecyclerView通过目标位置" 错误.如果有人知道如何解决这些问题,请分享.

This proves tricky to solve by overriding calculateSpeedPerPixel of the LinearSmoothScroller. With that attempt for a solution I was getting a lot of "RecyclerView passed over target position" errors. If anyone has an idea how to solve those please share.

在此解决方案中,我采用了另一种方法:首先跳转到更接近目标位置的位置,然后平滑滚动:

I took a different approach with this solution: first jump to a position closer to the target position and then smooth scroll:

/**
 * Enables still giving an impression of difference in scroll depending on how far the item scrolled to is,
 * while not having that tedious huge linear scroll time for distant items.
 * 
 * If scrolling to a position more than minJumpPosition diff away from current position, then jump closer first and then smooth scroll.
 * The distance how far from the target position to jump to is determined by a logarithmic function,
 * which in our case is y=20 at x=20 and for all practical purposes never goes over a y=100 (@x~1000) (so max distance is 100).
 * 
 * If the diff is under 20 there is no jump - for diff 15 the scroll distance is 15 items.
 * If the diff (x) is 30, jumpDiff (y) is around 28, so jump 2 and scroll 28 items.
 * If the diff (x) is 65, jumpDiff (y) is around 44, so jump 21 and scroll 44 items.
 * If the diff (x) is 100, jumpDiff (y) is around 53, so jump 47 and scroll 53 items.
 * If the diff (x) is 380, jumpDiff (y) is around 80, so jump 300 and scroll 80 items.
 * If the diff (x) is 1000, jumpDiff (y) is around 100 items scroll distance.
 * If the diff (x) is 5000, jumpDiff (y) is around 133 items scroll distance.
 * If the diff (x) is 8000, jumpDiff (y) is around 143 items scroll distance.
 * If the diff (x) is 10000, jumpDiff (y) is around 147 items scroll distance.
 *
 * You can play with the parameters to change the:
 *  - minJumpPosition: change when to start applying the jump
 *  - maxScrollAllowed: change speed change rate (steepness of the curve)
 *  - maxPracticalPosition: change what is the highest expected number of items
 * You might find it easier with a visual tool:
 * https://www.desmos.com/calculator/auubsajefh
 */
fun RecyclerView.jumpThenSmoothScroll(smoothScroller: SmoothScroller, position: Int,
                                      delay: Long = 0L,
                                      doAfterScrolled: (() -> Unit)? = null) {
    smoothScroller.targetPosition = position

    val layoutManager = layoutManager as LinearLayoutManager

    fun smoothScrollAndDoAfter() {
        layoutManager.startSmoothScroll(smoothScroller)
        doAfterScrolled?.let { post { postDelayed({ doAfterScrolled() }, max(0L, delay)) } }
    }

    val firstVisiblePosition = layoutManager.findFirstVisibleItemPosition()

    val diff = abs(position - firstVisiblePosition).toFloat()

    // Position from which to start applying "jump then scroll".
    val minJumpPosition = 20f

    if (diff > minJumpPosition) {
        // On the logarithmic function graph, 
        // y=minJumpPosition when x=minJumpPosition, and y=maxScrollAllowed when x=maxPracticalPosition.
        // So we are using two points to determine the function: 
        // (minJumpPosition, minJumpPosition) and (maxPracticalPosition, maxScrollAllowed)
        // In our case (20, 20) and (1000, 100)

        // Max practical possible items (max diff between current and target position).
        // It is OK for this to be high as logarithmic function is long approaching this value.
        val maxPracticalPosition = 1000
        // Never scroll more than this number of items. 
        // Scroll will be from 0 to maxScrollAllowed for all practical purposes 
        // ("practical" as determined by maxPracticalPosition).
        val maxScrollAllowed = 100

        // b = (x2/x1)^(1f/(y2-y1))
        val logBase = (maxPracticalPosition / minJumpPosition).pow (1f / (maxScrollAllowed - minJumpPosition))
        // s = (log(b)x1) - y1
        val logShift = log(minJumpPosition, logBase) - minJumpPosition

        val jumpDiff = (log(diff, logBase) - logShift).toInt() // y: 20 to 100 (for diff x: 20 to 1000)
        val jumpDiffDirection = if (position < firstVisiblePosition) 1 else -1
        val jumpPosition = position + (jumpDiff * jumpDiffDirection)

        // First jump closer
        layoutManager.scrollToPositionWithOffset(jumpPosition, 0)
        // Then smooth scroll
        smoothScrollAndDoAfter()
    } else {
        smoothScrollAndDoAfter()
    }
}

这篇关于Android RecyclerView:假smooth如果有很多项目,请滚动到顶部的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆