代码之家  ›  专栏  ›  技术社区  ›  android developer

为什么RecyclerView的平滑滚动不能很好地与一些插值器类配合使用?

  •  15
  • android developer  · 技术社区  · 6 年前

    背景

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

    问题

    出于某种原因 Interpolator 我尝试的课程(有插图) here

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

    我试过的

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

    主要活动.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)
            }
        }
    }
    

    活动_main.xml文件

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView" xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent"
        android:layout_height="@dimen/list_item_size" android:orientation="horizontal"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
    

    渐变文件

    apply plugin: 'com.android.application'
    apply plugin: 'kotlin-android'
    apply plugin: 'kotlin-android-extensions'
    
    android {
        compileSdkVersion 28
        defaultConfig {
            applicationId "com.example.myapplication"
            minSdkVersion 15
            targetSdkVersion 28
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
    }
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
        implementation 'androidx.appcompat:appcompat:1.0.0-rc02'
        implementation 'androidx.core:core-ktx:1.0.0-rc02'
        implementation 'androidx.constraintlayout:constraintlayout:1.1.2'
        implementation 'androidx.recyclerview:recyclerview:1.0.0-rc02'
    }
    

    这里有一个关于BounceInterpolator的动画,你可以看到它根本不反弹:

    enter image description here

    提供POC项目示例 here

    为什么它不能像预期的那样工作,我怎样才能修复它?


    编辑:似乎这是一个错误,因为我不能使用任何“有趣的”插值循环视图滚动,所以我已经报告了它 here .

    3 回复  |  直到 6 年前
        1
  •  8
  •   TheHebrewHammer    6 年前

    我想看看谷歌的支持动画包。明确地 https://developer.android.com/reference/android/support/animation/DynamicAnimation#SCROLL_X

    它看起来像:

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

    更新:

    看来这也不行。我查看了一些RecyclerView的源代码,bounce interpolator不起作用的原因是RecyclerView没有正确使用interpolator。有人打电话给我 computeScrollDuration 然后,对插值器的调用将获得原始动画时间(以秒为单位),而不是总动画时间的百分比。这个值也不是完全可以预测的,我测试了一些值,从100ms到250ms的任何地方都可以看到

    1. https://github.com/EverythingMe/overscroll-decor

    2. 实现自己的属性并使用弹簧动画:

    
    class ScrollXProperty : FloatPropertyCompat("scrollX") {
    
        override fun setValue(obj: RecyclerView, value: Float) {
            obj.scrollBy(value.roundToInt() - getValue(obj).roundToInt(), 0)
        }
    
        override fun getValue(obj: RecyclerView): Float =
                obj.computeHorizontalScrollOffset().toFloat()
    }
    

    然后在你的弹跳中,将呼叫替换为 smoothScrollBy 变化如下:

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

    更新:

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

    平滑滚动

    解决方案#2所做的是使用spring animator,但需要更新scroll。SCROLL\ux不工作的原因是RecyclerView实际上没有滚动(这是我的错误)。它根据不同的计算来计算视图应该在哪里,这就是为什么需要调用 computeHorizontalScrollOffset ScrollXProperty 允许您更改RecyclerView的水平滚动,就像指定 scrollX 属性,它基本上是一个适配器。RecyclerViews不支持滚动到特定的像素偏移,只支持平滑滚动,但是SpringAnimation已经为您平滑地完成了,所以我们不需要这样做。相反,我们想要滚动到一个离散的位置。看到了吗 https://android.googlesource.com/platform/frameworks/support/+/247185b98675b09c5e98c87448dd24aef4dffc9d/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java#387

    这是我用来测试的代码 https://github.com/yperess/StackOverflow/tree/52148251

    使用插值器时有相同的概念:

    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()
    

    GitHub上的演示项目已更新

    我更新了 包括一个优化,它似乎在我的像素上工作得很好,但我还没有在旧设备上测试。

    class ScrollXProperty(
            private val enableOptimizations: Boolean
    ) : Property<RecyclerView, Int>(Int::class.java, "horizontalOffset") {
    
        private var lastKnownValue: Int? = null
    
        override fun get(`object`: RecyclerView): Int =
                `object`.computeHorizontalScrollOffset().also {
                    if (enableOptimizations) {
                        lastKnownValue = it
                    }
                }
    
        override fun set(`object`: RecyclerView, value: Int) {
            val currentValue = lastKnownValue?.takeIf { enableOptimizations } ?: get(`object`)
            if (enableOptimizations) {
                lastKnownValue = value
            }
            `object`.scrollBy(value - currentValue, 0)
        }
    }
    

    GitHub项目现在包括带有以下插值器的演示:

    <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>
    
        2
  •  0
  •   ʍѳђઽ૯ท    6 年前

    就像我说的,我真的不希望 发布候选 ( recyclerview:1.0.0-rc02

    • 使用第三方库可能有用,但我并不这么认为,因为它们刚刚引入了 androidx 依赖关系,它们已经在开发和 不够稳定 供其他开发者使用。

    更新

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

    所以,最好等待新的更新。

        3
  •  0
  •   dilix    6 年前

    可能的解决办法之一是 https://github.com/chthai64/overscroll-bouncy-android

    把它包括在你的项目中

    implementation 'com.chauthai.overscroll:overscroll-bouncy:0.1.1'
    

    然后在你的视野中改变 RecyclerView 在里面 activity_main.xml com.chauthai.overscroll.RecyclerViewBouncy

    <?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"/>
    

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

    推荐文章