水平方向の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を探す方法のアニメーションです。これは、ご覧のとおり、まったく跳ね返りません。
利用可能なサンプルPOCプロジェクト ここ
なぜ期待どおりに動作しないのですか、どうすれば修正できますか?
RecyclerViewはスクロールのためにInterpolatorとうまく動作しますか?
編集:RecyclerViewのスクロールに「興味深い」補間機能を使用できないため、それはバグのようですので、それについて報告しました here 。
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つのオプションがある(私は両方をテストした)
https://github.com/EverythingMe/overscroll-decor などの別のライブラリを使用します
独自のプロパティを実装し、春のアニメーションを使用します。
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>
どういうわけか、オーバースクロールバウンスを有効にする必要があります。
可能な解決策の1つは、使用することです https://github.com/chthai64/overscroll-bouncy-Android
プロジェクトに含める
implementation 'com.chauthai.overscroll:overscroll-bouncy:0.1.1'
次に、POVでactivity_main.xml
のRecyclerView
を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"/>
この変更後、アプリにバウンスが表示されます。
私が言ったように、私は正直にRelease Candidate(recyclerview:1.0.0-rc02
の場合)はすでに開発中であるため、問題を引き起こすことなく適切に機能します。
androidx
依存関係を導入したばかりで、すでに開発中であり、十分に安定していないため、私は実際にはそうは思いません他の開発者が使用します。更新 Google Developersからの回答:
これを開発チームに渡しました。利用可能になり次第、この問題を更新して詳細をお知らせします。
したがって、新しい更新を待つことをお勧めします。