Android 为什么';RecyclerView的平滑滚动是否与某些插值器类配合良好? 背景

Android 为什么';RecyclerView的平滑滚动是否与某些插值器类配合良好? 背景,android,scroll,android-recyclerview,interpolation,Android,Scroll,Android Recyclerview,Interpolation,为了对水平RecyclerView上的项目进行一个简洁的概述,我们希望有一个类似于反弹的动画,从某个位置开始,然后转到RecyclerView的开头(例如,从项目3到项目0) 问题 出于某种原因,我尝试的所有类(插图可用)似乎都不允许项目超出RecyclerView或在其上反弹 更具体地说,我已经尝试过超调器、反弹器和其他一些类似的。我甚至试过预测器。在大多数情况下,它只进行简单的滚动,没有特殊效果。在ExpectedOvershootInterpolator上,它甚至不会滚动 我试过的 以下是

为了对水平RecyclerView上的项目进行一个简洁的概述,我们希望有一个类似于反弹的动画,从某个位置开始,然后转到RecyclerView的开头(例如,从项目3到项目0)

问题 出于某种原因,我尝试的所有类(插图可用)似乎都不允许项目超出RecyclerView或在其上反弹

更具体地说,我已经尝试过超调器、反弹器和其他一些类似的。我甚至试过预测器。在大多数情况下,它只进行简单的滚动,没有特殊效果。在ExpectedOvershootInterpolator上,它甚至不会滚动

我试过的 以下是我制作的POC代码,以显示问题:

MainActivity.kt

class MainActivity : AppCompatActivity() {
    val handler = Handler()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val itemSize = resources.getDimensionPixelSize(R.dimen.list_item_size)
        val itemsCount = 6
        recyclerView.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
            override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
                val imageView = ImageView(this@MainActivity)
                imageView.setImageResource(android.R.drawable.sym_def_app_icon)
                imageView.layoutParams = RecyclerView.LayoutParams(itemSize, itemSize)
                return object : RecyclerView.ViewHolder(imageView) {}
            }

            override fun getItemCount(): Int = itemsCount

            override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
            }
        }
        val itemToGoTo = Math.min(3, itemsCount - 1)
        val scrollValue = itemSize * itemToGoTo
        recyclerView.post {
            recyclerView.scrollBy(scrollValue, 0)
            handler.postDelayed({
                recyclerView.smoothScrollBy(-scrollValue, 0, BounceInterpolator())
            }, 500L)
        }
    }
}
这里有一个动画显示了它是如何寻找BounceInterpolator的,正如你所看到的,它根本不会反弹:

样本POC项目可用

问题 为什么它不能像预期的那样工作,我如何修复它

RecyclerView能否与用于滚动的插值器配合使用



编辑:这似乎是一个bug,因为我不能使用任何“有趣”的插值器来进行RecyclerView滚动,所以我已经报道过了。

我想看看谷歌的支持动画包。具体地

它看起来像:

SpringAnimation(recyclerView, DynamicAnimation.SCROLL_X, 0f)
        .setStartVelocity(1000)
        .start()
更新:

看来这也不行。我查看了RecyclerView的一些源代码,反弹插值器不起作用的原因是因为RecyclerView没有正确使用插值器。调用
computeScrollDuration
调用插值器,然后获取原始动画时间(以秒为单位),而不是总动画时间的%值。这个值也不是完全可以预测的,我测试了一些值,看到了100ms到250ms的范围。无论如何,从我看到的情况来看,你有两个选择(我已经测试了两个)

  • 使用另一个库,例如

  • 实现您自己的属性并使用spring动画:

  • 然后在反弹中,将对
    smoothScrollBy
    的调用替换为以下变量:

    SpringAnimation(recyclerView, ScrollXProperty())
        .setSpring(SpringForce()
                .setFinalPosition(0f)
                .setStiffness(SpringForce.STIFFNESS_LOW)
                .setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY))
        .start()
    
    更新:

    第二个解决方案是开箱即用的,不会对您的RecyclerView进行任何更改,是我编写并完全测试的解决方案

    关于插值器的更多信息,
    smoothScrollBy
    与插值器不兼容(可能是一个bug)。使用插值器时,基本上将0-1值映射到另一个值,该值是动画的乘数。示例:t=0,interp(0)=0表示动画开始时的值应与开始时的值相同,t=.5,interp(.5)=.25表示元素将以1/4的方式进行动画制作,等等。反弹插值器基本上在某个点返回值>1,并振荡约1,直到t=1时最终稳定在1

    解决方案#2正在使用spring animator,但需要更新scroll。SCROLL_X不起作用的原因是RecyclerView实际上没有滚动(这是我的错误)。它根据不同的计算计算视图的位置,这就是为什么需要调用
    computeHorizontalScrollOffset
    ScrollXProperty
    允许您更改RecyclerView的水平滚动,就好像您在ScrollView中指定了
    scrollX
    属性一样,它基本上是一个适配器。RecyclerViews不支持滚动到特定的像素偏移,只支持平滑滚动,但是SpringAnimation已经为您平滑地完成了,所以我们不需要这样做。相反,我们希望滚动到一个离散的位置。看

    更新:

    这是我用来测试的代码

    更新:

    使用插值器时得到了相同的概念:

    class ScrollXProperty : Property<RecyclerView, Int>(Int::class.java, "horozontalOffset") {
        override fun get(`object`: RecyclerView): Int =
                `object`.computeHorizontalScrollOffset()
    
        override fun set(`object`: RecyclerView, value: Int) {
            `object`.scrollBy(value - get(`object`), 0)
        }
    }
    
    ObjectAnimator.ofInt(recycler_view, ScrollXProperty(), 0).apply {
        interpolator = BounceInterpolator()
        duration = 500L
    }.start()
    
    <string-array name="interpolators">
        <item>AccelerateDecelerate</item>
        <item>Accelerate</item>
        <item>Anticipate</item>
        <item>AnticipateOvershoot</item>
        <item>Bounce</item>
        <item>Cycle</item>
        <item>Decelerate</item>
        <item>Linear</item>
        <item>Overshoot</item>
    </string-array>
    
    GitHub项目现在包括带有以下插值器的演示:

    class ScrollXProperty : Property<RecyclerView, Int>(Int::class.java, "horozontalOffset") {
        override fun get(`object`: RecyclerView): Int =
                `object`.computeHorizontalScrollOffset()
    
        override fun set(`object`: RecyclerView, value: Int) {
            `object`.scrollBy(value - get(`object`), 0)
        }
    }
    
    ObjectAnimator.ofInt(recycler_view, ScrollXProperty(), 0).apply {
        interpolator = BounceInterpolator()
        duration = 500L
    }.start()
    
    <string-array name="interpolators">
        <item>AccelerateDecelerate</item>
        <item>Accelerate</item>
        <item>Anticipate</item>
        <item>AnticipateOvershoot</item>
        <item>Bounce</item>
        <item>Cycle</item>
        <item>Decelerate</item>
        <item>Linear</item>
        <item>Overshoot</item>
    </string-array>
    
    
    加速加速
    加快
    预料
    预期反冲
    反弹
    周期
    减速
    线性的
    超过
    
    正如我所说,我不希望一个发行候选版本能够正常工作,而不会引起任何问题,因为它已经在开发中了

    • 使用第三方库可能会起作用,但我并不这么认为,因为它们刚刚引入了
      androidx
      依赖项,并且它们已经在开发中,不够稳定,无法供其他开发人员使用
    更新谷歌开发者的答案:

    我们已将此消息传递给开发团队,并将更新此问题 提供更多信息


    因此,最好等待新的更新

    您需要以某种方式启用overscroll反弹

    可能的解决方案之一是使用

    将其包含在项目中

    implementation 'com.chauthai.overscroll:overscroll-bouncy:0.1.1'
    
    然后在您的POV中,将
    activity\u main.xml
    中的
    RecyclerView
    更改为
    com.chauthai.overscroll.RecyclerServicewBouncy

    <?xml version="1.0" encoding="utf-8"?>
    <com.chauthai.overscroll.RecyclerViewBouncy
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="@dimen/list_item_size"
        android:orientation="horizontal"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
    
    
    

    更改后,您将在应用程序中看到bounce。

    您列出的插值器可能会滚动到RecyclerView中的最后一个元素之外。如果使用像
    AccelerateInterpolator
    DeccelerateInterpolator
    这样的插值器,或者其他无法滚动到目标位置的插值器,
    smoothScrollby
    是否能像预期的那样工作?它们似乎可以工作,但这就是问题所在……正如您所知,
    recyclerview:1.0.0-rc02
    意味着
    发布候选版本
    ,所以我认为这是一个可以与其他版本进行比较以检查的bug…@ઽ૯ท 问题是,更有趣的插值器没有按预期工作。遗憾的是,这会使
    recyclerView
    本身产生动画效果,而不会使其项目滚动。请解释。这些解决方案如何与
    smoothScrollBy
    配合使用,或者
    implementation 'com.chauthai.overscroll:overscroll-bouncy:0.1.1'
    
    <?xml version="1.0" encoding="utf-8"?>
    <com.chauthai.overscroll.RecyclerViewBouncy
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="@dimen/list_item_size"
        android:orientation="horizontal"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>