简体   繁体   English

Android RecyclerView:如果有很多项目,则假平滑滚动到顶部

[英]Android RecyclerView: Fake smoothScroll to top if many, many items

We have your standard RecyclerView -based Feed screen in our app.我们的应用程序中有您标准的基于RecyclerView的 Feed 屏幕。

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 .如果我的RecyclerView有 100 多个项目并按“转到顶部”快捷方式,则smoothScrollToPosition(0)的默认行为是需要很长时间才能滚动到顶部

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)!它需要多长时间几乎是可笑的 - 如果您已经向下滚动到顶部,则需要 10 秒的快速滚动(这是一个常见的用例)!

We're looking for a way to "fake" the scroll to the top, if the number of items in the RecyclerView > SOME_THRESHOLD .如果RecyclerView > SOME_THRESHOLD的项目数,我们正在寻找一种“伪造”滚动到顶部的SOME_THRESHOLD

I'm not an iOS guy, but our iOS version (as the devs tell me) seems to have such behaviour baked into the control.我不是 iOS 人,但我们的 iOS 版本(正如开发人员告诉我的那样)似乎将这种行为融入了控件中。 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.如果项目太多,它只会做一个超级快速的模糊滚动,这显然会伪造/省略中间的许多项目。

Does the RecyclerView have any such capabilities? RecyclerView 有这样的功能吗?

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.我们想过做一个多部分的事情,我们快速跳转到索引SOME_THRESHOLD处的项目,然后调用smoothScrollToPosition(0) - 你明白了 - 但是我们想到的大多数事情都有缺点。

Help is appreciated, thank you.感谢帮助,谢谢。

Solution for controlling the speed of the scroll, to scroll faster to more distant positions控制滚动速度的解决方案,更快地滚动到更远的位置

A bit late to the party, but here's a Kotlin solution for anyone else looking.聚会有点晚了,但这里有一个 Kotlin 解决方案,供其他人看。

This proves tricky to solve by overriding calculateSpeedPerPixel of the LinearSmoothScroller .这证明通过覆盖LinearSmoothScroller calculateSpeedPerPixel很难解决。 With that attempt for a solution I was getting a lot of "RecyclerView passed over target position" errors.通过尝试解决方案,我收到了很多“RecyclerView 通过目标位置”错误。 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()
    }
}

This is a minimal version of @Vlad's answer.这是@Vlad 答案的最小版本。 If the scroll-distance is smaller than the threshold, it scrolls there, else it jumps there.如果滚动距离小于阈值,则滚动到那里,否则跳到那里。

Usage:用法:

myRecycler.smoothScrollOrJumpTo(
    goalPosition = position,
    firstVisiblePosition = myLayoutManager.findFirstVisibleItemPosition()
)

Code:代码:

private fun RecyclerView.smoothScrollOrJumpTo(
    goalPosition: Int,
    firstVisiblePosition: Int,
    threshold: Int,
){
    val diff = abs(goalPosition - firstVisiblePosition)

    if(diff < threshold){
        smoothScrollToPosition(goalPosition)
    } else {
        scrollToPosition(goalPosition)
    }
}

Ofc you can go further; Ofc你可以走得更远; eg fading out the recyclerview before jumping例如,在跳跃前淡出recyclerview

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM