スクロールしたときにRecyclerViewの要素をアニメーション化する方法はありますか?
DefaultItemAnimator
と_RecyclerView.ItemAnimator
_を調べましたが、そのアニメーションはデータセットが変更された場合にのみ呼び出されるようです。間違っている場合は修正してください。
RecyclerView.ItemAnimator.animateMove()
がいつ呼ばれるのか少し混乱していますか?そのクラスにいくつかのブレークポイントを入れましたが、どれもアプリを停止しません。
しかし、私の質問に戻って、RecyclerViewをどのようにアニメーション化できますか?一部のカスタムルールに応じて、一部の要素に別の不透明度が必要です。
私はもう少し努力しましたが、アニメーションの動きはまさに私が探しているものです。そのメソッドはdispatchLayout()
から呼び出されます。そのメソッドのjavadocは次のとおりです。
レイアウトに起因する変更のアニメーション化を処理するlayoutChildren()のラッパー。アニメーションは、5つの異なる種類のアイテムがプレイ中にあるという前提で機能します。
PERSISTENT:アイテムはレイアウトの前後に表示されます
削除:レイアウト前にアイテムが表示され、アプリによって削除されました
ADDED:アイテムはレイアウト前に存在せず、アプリによって追加されました
DISAPPEARING:アイテムはデータセット内に存在しますが、レイアウトのプロセスで表示から非表示に変更されました(他の変更の副作用として画面外に移動しました)
APPEARING:アイテムはデータセット内に存在しますが、レイアウトのプロセスで非表示から表示に変更されます(他の変更の副作用として画面上で移動されました)
全体的なアプローチでは、レイアウトの前後にどのアイテムが存在するかを把握し、各アイテムについて上記の5つの状態のいずれかを推測します。その後、それに応じてアニメーションが設定されます。
PERSISTENTビューは移動されます({@link ItemAnimator#animateMove(ViewHolder、int、int、int、int)})削除されたビューは削除されます({@link ItemAnimator#animateRemove(ViewHolder)})
追加されたビューが追加されます({@link ItemAnimator#animateAdd(ViewHolder)})
DISAPPEARINGビューは画面外に移動します
APPEARINGビューは画面上で移動します
これまでのところ、私はPERSISTENT、DISAPPEARING、およびAPPEARINGを探していますが、この行が次の理由で呼び出されることはありません。
_boolean animateChangesSimple = mItemAnimator != null && mItemsAddedOrRemoved
&& !mItemsChanged;
_
mItemsAddedOrRemoved
は常に常にfalseであるため、そのコールバックに到達することはありません。フラグを正しく設定する方法はありますか?
OnScrollListener
を使用し、カスタムanimate()
メソッドでアニメーション化することになりました。私の場合、そのコードの所要時間はわずか2msなので、60fpsでは問題ありません。
recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(int newState) {
if(newState == RecyclerView.SCROLL_STATE_IDLE) {
// special handler to avoid displaying half elements
scrollToNext();
}
animate();
}
@Override
public void onScrolled(int dx, int dy) {
animate();
}
});
私はこのようにしました。誰かを助けるかもしれない。それが最善の方法であるかどうかはわかりませんが、私にとってはうまくいきます。
PDATE:高速スクロール動作を修正するには、アダプターのonViewDetachedFromWindow
メソッドをオーバーライドし、アニメーションビューでclearAnimation
を呼び出します(この場合、holder.itemView.clearAnimation()
)。
up_from_bottom.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:shareInterpolator="@Android:anim/decelerate_interpolator">
<translate
Android:fromXDelta="0%" Android:toXDelta="0%"
Android:fromYDelta="100%" Android:toYDelta="0%"
Android:duration="400" />
</set>
down_from_top.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:shareInterpolator="@Android:anim/decelerate_interpolator">
<translate
Android:fromXDelta="0%" Android:toXDelta="0%"
Android:fromYDelta="-100%" Android:toYDelta="0%"
Android:duration="400" />
</set>
最後に、このコードをonBindViewHolder
のrecyclerView
に配置します。 lastPositionというフィールドを作成し、-1に初期化します。
Animation animation = AnimationUtils.loadAnimation(context,
(position > lastPosition) ? R.anim.up_from_bottom
: R.anim.down_from_top);
holder.itemView.startAnimation(animation);
lastPosition = position;
良い解決策は、onBindViewHolder
メソッドでアダプターのホルダーをアニメーション化することです。 Material TestプロジェクトSlidenerdから抜粋したスニペット:
@Override
public void onBindViewHolder(ViewHolderBoxOffice holder, int position) {
Movie currentMovie = mListMovies.get(position);
//one or more fields of the Movie object may be null since they are fetched from the web
holder.movieTitle.setText(currentMovie.getTitle());
//retrieved date may be null
Date movieReleaseDate = currentMovie.getReleaseDateTheater();
if (movieReleaseDate != null) {
String formattedDate = mFormatter.format(movieReleaseDate);
holder.movieReleaseDate.setText(formattedDate);
} else {
holder.movieReleaseDate.setText(Constants.NA);
}
int audienceScore = currentMovie.getAudienceScore();
if (audienceScore == -1) {
holder.movieAudienceScore.setRating(0.0F);
holder.movieAudienceScore.setAlpha(0.5F);
} else {
holder.movieAudienceScore.setRating(audienceScore / 20.0F);
holder.movieAudienceScore.setAlpha(1.0F);
}
if (position > mPreviousPosition) {
AnimationUtils.animateSunblind(holder, true);
} else {
AnimationUtils.animateSunblind(holder, false);
}
mPreviousPosition = position;