web-dev-qa-db-ja.com

RecyclerViewのスムーズスクロールが一部のInterpolatorクラスでうまく機能しないのはなぜですか?

バックグラウンド

水平方向のRecyclerViewのアイテムの概要を簡潔に説明するために、ある位置から始まり、RecyclerViewの先頭に移動する(たとえば、アイテム3からアイテム0まで)バウンスのようなアニメーションを作成します。 。

問題

何らかの理由で、すべての Interpolator 私が試すクラス(イラストが利用可能 here )アイテムがRecyclerViewの外に出たりバウンスしたりすることを許可していないようです。

より具体的には、私はOvershootInterpolator、BounceInterpolatorおよび他のいくつかの同様のものを試しました。私はAnticipateOvershootInterpolatorも試しました。ほとんどの場合、特別な効果なしに単純なスクロールを行います。 AnticipateOvershootInterpolatorでは、スクロールさえしません...

私が試したこと

問題を示すために作成した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)
        }
    }
}

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 file

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プロジェクト ここ

質問

なぜ期待どおりに動作しないのですか、どうすれば修正できますか?

RecyclerViewはスクロールのためにInterpolatorとうまく動作しますか?


編集:RecyclerViewのスクロールに「興味深い」補間機能を使用できないため、それはバグのようですので、それについて報告しました here

16

Googleのサポートアニメーションパッケージを見てみましょう。具体的には https://developer.Android.com/reference/Android/support/animation/DynamicAnimation#SCROLL_X

それは次のようになります:

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

更新:

これも機能しないようです。 RecyclerViewのソースをいくつか見ましたが、バウンス補間が機能しない理由は、RecyclerViewが補間を正しく使用していないためです。 computeScrollDurationへの呼び出しがあり、インターポレーターへの呼び出しは、アニメーション時間全体の%としての値ではなく、生のアニメーション時間を秒単位で取得します。この値も完全には予測可能ではありません。いくつかの値をテストしたところ、100ミリ秒から250ミリ秒の範囲で見られました。とにかく、あなたが見ているものから2つのオプションがある(私は両方をテストした)

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

更新:

2番目のソリューションは、RecyclerViewに変更を加えることなくそのまま使用でき、私が作成して完全にテストしたものです。

補間器の詳細については、smoothScrollByは補間器ではうまく機能しません(バグの可能性があります)。補間を使用するときは、基本的に0-1の値をアニメーションの乗数である別の値にマッピングします。例:t = 0、interp(0)= 0は、アニメーションの開始時に、値が開始時と同じであることを意味します。t= .5、interp(.5)=。25は、要素が1をアニメーション化することを意味します/ 4等。バウンス補間は基本的にある時点で> 1の値を返し、t = 1のときに最終的に1に落ち着くまで約1振動します。

#2が行っている解決策は、スプリングアニメーターを使用することですが、スクロールを更新する必要があります。 SCROLL_Xが機能しない理由は、RecyclerViewが実際にスクロールしないためです(それは私の間違いでした)。それはビューが異なる計算に基づいてどこにあるべきかを計算するので、computeHorizontalScrollOffsetへの呼び出しが必要です。 ScrollXPropertyを使用すると、まるでScrollViewでscrollXプロパティを指定しているかのように、RecyclerViewの水平スクロールを変更できます。これは基本的にアダプターです。 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のデモプロジェクトが更新されました

最適化を含めるようにScrollXPropertyを更新しました。Pixelではうまく機能するようですが、古いデバイスではテストしていません。

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

どういうわけか、オーバースクロールバウンスを有効にする必要があります。

可能な解決策の1つは、使用することです https://github.com/chthai64/overscroll-bouncy-Android

プロジェクトに含める

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

次に、POVでactivity_main.xmlRecyclerViewcom.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"/>

この変更後、アプリにバウンスが表示されます。

0
dilix

私が言ったように、私は正直にRelease Candidaterecyclerview:1.0.0-rc02の場合)はすでに開発中であるため、問題を引き起こすことなく適切に機能します。

  • サードパーティのライブラリを使用しても機能する可能性がありますが、androidx依存関係を導入したばかりで、すでに開発中であり、十分に安定していないため、私は実際にはそうは思いません他の開発者が使用します。

更新 Google Developersからの回答:

これを開発チームに渡しました。利用可能になり次第、この問題を更新して詳細をお知らせします。

したがって、新しい更新を待つことをお勧めします。

0
ʍѳђઽ૯ท