webentwicklung-frage-antwort-db.com.de

Warum funktioniert das reibungslose Scrollen von RecyclerView mit einigen Interpolator-Klassen nicht?

Hintergrund

Um einen schönen Überblick über die Elemente in einer horizontalen RecyclerView zu erhalten, möchten wir eine bounce-ähnliche Animation haben, die von einer Position aus beginnt und an den Anfang der RecyclerView geht (z. B. von Element 3 bis Element 0). .

Das Problem

Aus irgendeinem Grund scheinen alle Interpolator -Klassen, die ich ausprobiere (Abbildung verfügbar hier ), Elemente nicht aus der RecyclerView zu lassen oder zu springen es.

Genauer gesagt habe ich OvershootInterpolator, BounceInterpolator und einige andere ähnliche versucht. Ich habe sogar AnticipateOvershootInterpolator ausprobiert. In den meisten Fällen wird ein Bildlauf ohne Spezialeffekt durchgeführt. bei AnticipateOvershootInterpolator wird nicht einmal gescrollt ...

Was ich versucht habe

Hier ist der Code des POC, den ich erstellt habe, um das Problem zu zeigen:

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([email protected])
                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)
        }
    }
}

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

gradle-Datei

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'
}

Und hier ist eine Animation, wie es für BounceInterpolator aussieht, die, wie Sie sehen, überhaupt nicht abspringt:

enter image description here

Muster-POC-Projekt verfügbar hier

Die Frage

Warum funktioniert es nicht wie erwartet und wie kann ich es beheben?

Könnte RecyclerView zum Scrollen mit Interpolator gut funktionieren?


EDIT: Es scheint ein Fehler zu sein, da ich keinen "interessanten" Interpolator für das Scrollen durch RecyclerView verwenden kann, also habe ich darüber berichtet hier .

15

Ich würde mir das Support-Animationspaket von Google ansehen. Insbesondere https://developer.Android.com/reference/Android/support/animation/DynamicAnimation#SCROLL_X

Es würde ungefähr so ​​aussehen:

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

AKTUALISIEREN:

Sieht so aus, als würde das auch nicht funktionieren. Ich habe mir einige Quellen für RecyclerView angesehen und der Grund dafür, dass der Bounce-Interpolator nicht funktioniert, ist, dass RecyclerView den Interpolator nicht richtig verwendet. Es wird aufgerufen, computeScrollDuration den Interpolator aufzurufen. Anschließend wird die unformatierte Animationszeit in Sekunden anstelle des Werts in% der gesamten Animationszeit abgerufen. Dieser Wert ist auch nicht ganz vorhersehbar. Ich habe einige Werte getestet und irgendwo zwischen 100 ms und 250 ms gesehen. Sowieso von was ich sehe, haben Sie zwei Wahlen (ich habe beide geprüft)

  1. Benutze eine andere Bibliothek wie https://github.com/EverythingMe/overscroll-decor

  2. Implementieren Sie Ihre eigene Immobilie und nutzen Sie die Frühlingsanimation:


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

Ersetzen Sie dann in Ihrem Sprung den Aufruf von smoothScrollBy durch eine Variation von:

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

AKTUALISIEREN:

Die zweite Lösung funktioniert sofort ohne Änderungen an Ihrem RecyclerView und ist die, die ich geschrieben und vollständig getestet habe.

Mehr über Interpolatoren, smoothScrollBy funktioniert nicht gut mit Interpolatoren (wahrscheinlich ein Fehler). Wenn Sie einen Interpolator verwenden, ordnen Sie im Grunde genommen einen 0-1-Wert einem anderen zu, der ein Multiplikator für die Animation ist. Beispiel: t = 0, interp (0) = 0 bedeutet, dass der Wert zu Beginn der Animation der gleiche sein sollte wie zu Beginn, t = .5, interp (.5) = .25 bedeutet, dass das Element 1 animieren würde/4 des Weges usw. Sprunginterpolatoren geben grundsätzlich irgendwann Werte> 1 zurück und oszillieren um 1, bis sie sich schließlich bei 1 einpendeln, wenn t = 1.

Die zweite Lösung besteht darin, den Frühlingsanimator zu verwenden, aber Scroll zu aktualisieren. Der Grund, warum SCROLL_X nicht funktioniert, ist, dass RecyclerView nicht wirklich scrollt (das war mein Fehler). Es berechnet, wo die Ansichten auf einer anderen Berechnung basieren sollen, weshalb Sie den Aufruf von computeHorizontalScrollOffset benötigen. Mit ScrollXProperty können Sie den horizontalen Bildlauf einer RecyclerView so ändern, als würden Sie die Eigenschaft scrollX in einer ScrollView angeben. Es handelt sich im Grunde um einen Adapter. RecyclerViews unterstützen das Scrollen zu einem bestimmten Pixel-Offset nicht, nur beim Smooth-Scrolling. Die SpringAnimation erledigt dies jedoch bereits reibungslos, sodass wir das nicht benötigen. Stattdessen möchten wir zu einer diskreten Position scrollen. Siehe https://Android.googlesource.com/platform/frameworks/support/+/247185b98675b09c5e98c87448dd24aef4dffc9d/v7/recyclerview/src/Android/support/v7/widget/RecyclerView.J

AKTUALISIEREN:

Hier ist der Code, den ich zum Testen verwendet habe https://github.com/yperess/StackOverflow/tree/52148251

AKTUALISIEREN:

Das gleiche Konzept für Interpolatoren:

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

Demo-Projekt auf GitHub wurde aktualisiert

Ich habe ScrollXProperty aktualisiert, um eine Optimierung einzuschließen. Es scheint auf meinem Pixel gut zu funktionieren, aber ich habe es nicht auf älteren Geräten getestet.

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

Das GitHub-Projekt enthält jetzt eine Demo mit den folgenden Interpolatoren:

<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>
7
TheHebrewHammer

Sie müssen den Overscroll-Sprung irgendwie aktivieren.

Eine der möglichen Lösungen ist zu verwenden https://github.com/chthai64/overscroll-bouncy-Android

Binden Sie es in Ihr Projekt ein

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

Ändern Sie dann in Ihrem POV RecyclerView in activity_main.xml in 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"/>

Nach dieser Änderung wird in Ihrer App Bounce angezeigt.

0
dilix

Wie ich bereits sagte, würde ich nicht erwarten, dass ein Release Candidate (recyclerview:1.0.0-rc02 in Ihrem Fall) einwandfrei funktionieren würde, da er bereits in der Entwicklung ist.

  • Die Verwendung von Bibliotheken von Drittanbietern könnte funktionieren, aber ich glaube nicht, dass sie androidx Abhängigkeiten eingeführt haben und bereits in der Entwicklung sind undnicht stabil genugsind, um von anderen Entwicklern verwendet zu werden.

Update Die Antwort von Google Developers:

Wir haben dies an das Entwicklungsteam weitergegeben und werden dieses Problem aktualisieren mit mehr Informationen, sobald sie verfügbar sind.

Warten Sie also besser auf die neuen Updates. 

0
ʍѳђઽ૯ท