現在、デフォルトのアニメーターAndroid.support.v7.widget.DefaultItemAnimator
、これが並べ替えの際の結果です
DefaultItemAnimatorアニメーションビデオ:https://youtu.be/EccI7RUcdbg
public void sortAndNotifyDataSetChanged() {
int i0 = 0;
int i1 = models.size() - 1;
while (i0 < i1) {
DemoModel o0 = models.get(i0);
DemoModel o1 = models.get(i1);
models.set(i0, o1);
models.set(i1, o0);
i0++;
i1--;
//break;
}
// adapter is created via adapter = new RecyclerViewDemoAdapter(models, mRecyclerView, this);
adapter.notifyDataSetChanged();
}
ただし、並べ替え時の既定のアニメーション(notifyDataSetChanged)ではなく、次のようにカスタムアニメーションを提供することを好みます。古いアイテムは右側からスライドし、新しいアイテムは上にスライドします。
予想されるアニメーションビデオ:https://youtu.be/9aQTyM7K4B
数年前、私はLinearLayout
+ View
を使用してこの効果を達成しました。現時点では、RecyclerView
はまだありません。
これがアニメーションの設定方法です
PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f, 0f);
PropertyValuesHolder translationX = PropertyValuesHolder.ofFloat("translationX", 0f, (float) width);
ObjectAnimator animOut = ObjectAnimator.ofPropertyValuesHolder(this, alpha, translationX);
animOut.setDuration(duration);
animOut.setInterpolator(accelerateInterpolator);
animOut.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator anim) {
final View view = (View) ((ObjectAnimator) anim).getTarget();
Message message = (Message)view.getTag(R.id.TAG_MESSAGE_ID);
if (message == null) {
return;
}
view.setAlpha(0f);
view.setTranslationX(0);
NewsListFragment.this.refreshUI(view, message);
final Animation animation = AnimationUtils.loadAnimation(NewsListFragment.this.getActivity(),
R.anim.slide_up);
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
view.setVisibility(View.VISIBLE);
view.setTag(R.id.TAG_MESSAGE_ID, null);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
view.startAnimation(animation);
}
});
layoutTransition.setAnimator(LayoutTransition.DISAPPEARING, animOut);
this.nowLinearLayout.setLayoutTransition(layoutTransition);
そして、これがアニメーションがトリガーされる方法です。
// messageView is view being added earlier in nowLinearLayout
for (int i = 0, ei = messageViews.size(); i < ei; i++) {
View messageView = messageViews.get(i);
messageView.setTag(R.id.TAG_MESSAGE_ID, messages.get(i));
messageView.setVisibility(View.INVISIBLE);
}
疑問に思っていましたが、RecylerViewで同じ効果をどのように実現できますか?
スクロールごとに並べ替えをリセットしたくない場合は、次の方向を見てください( GITHUBデモプロジェクト )。
何らかの_RecyclerView.ItemAnimator
_を使用しますが、animateAdd()
およびanimateRemove()
関数を書き換える代わりに、animateChange()
およびanimateChangeImpl()
を実装できます。ソート後、adapter.notifyItemRangeChanged(0, mItems.size());
を呼び出してアニメーションをトリガーできます。したがって、アニメーションをトリガーするコードは非常にシンプルになります。
_for (int i = 0, j = mItems.size() - 1; i < j; i++, j--)
Collections.swap(mItems, i, j);
adapter.notifyItemRangeChanged(0, mItems.size());
_
アニメーションコードの場合は_Android.support.v7.widget.DefaultItemAnimator
_を使用できますが、このクラスにはプライベートanimateChangeImpl()
があるため、コードをコピーして貼り付け、このメソッドを変更するか、リフレクションを使用する必要があります。または、ItemAnimator
の例で_@Andreas Wenger
_が行ったように、独自のSlidingAnimator
クラスを作成できます。ここでのポイントは、2つのアニメーションがあるコードにanimateChangeImpl
Simmilarを実装することです。
1)古いビューを右にスライドします
_private void animateChangeImpl(final ChangeInfo changeInfo) {
final RecyclerView.ViewHolder oldHolder = changeInfo.oldHolder;
final View view = oldHolder == null ? null : oldHolder.itemView;
final RecyclerView.ViewHolder newHolder = changeInfo.newHolder;
final View newView = newHolder != null ? newHolder.itemView : null;
if (view == null) return;
mChangeAnimations.add(oldHolder);
final ViewPropertyAnimatorCompat animOut = ViewCompat.animate(view)
.setDuration(getChangeDuration())
.setInterpolator(interpolator)
.translationX(view.getRootView().getWidth())
.alpha(0);
animOut.setListener(new VpaListenerAdapter() {
@Override
public void onAnimationStart(View view) {
dispatchChangeStarting(oldHolder, true);
}
@Override
public void onAnimationEnd(View view) {
animOut.setListener(null);
ViewCompat.setAlpha(view, 1);
ViewCompat.setTranslationX(view, 0);
dispatchChangeFinished(oldHolder, true);
mChangeAnimations.remove(oldHolder);
dispatchFinishedWhenDone();
// starting 2-nd (Slide Up) animation
if (newView != null)
animateChangeInImpl(newHolder, newView);
}
}).start();
}
_
2)新しいビューを上にスライドします
_private void animateChangeInImpl(final RecyclerView.ViewHolder newHolder,
final View newView) {
// setting starting pre-animation params for view
ViewCompat.setTranslationY(newView, newView.getHeight());
ViewCompat.setAlpha(newView, 0);
mChangeAnimations.add(newHolder);
final ViewPropertyAnimatorCompat animIn = ViewCompat.animate(newView)
.setDuration(getChangeDuration())
.translationY(0)
.alpha(1);
animIn.setListener(new VpaListenerAdapter() {
@Override
public void onAnimationStart(View view) {
dispatchChangeStarting(newHolder, false);
}
@Override
public void onAnimationEnd(View view) {
animIn.setListener(null);
ViewCompat.setAlpha(newView, 1);
ViewCompat.setTranslationY(newView, 0);
dispatchChangeFinished(newHolder, false);
mChangeAnimations.remove(newHolder);
dispatchFinishedWhenDone();
}
}).start();
}
_
これは、作業スクロールと同様のアニメーションを含むデモ画像です https://i.gyazo.com/04f4b767ea61569c00d3b4a4a86795ce.gifhttps://i.gyazo.com/57a52b8477a361c383d44664392db0be.gif =
編集:
RecyclerViewのパフォーマンスを高速化するには、adapter.notifyItemRangeChanged(0, mItems.size());
の代わりに、おそらく次のようなものを使用する必要があります。
_LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
int firstVisible = layoutManager.findFirstVisibleItemPosition();
int lastVisible = layoutManager.findLastVisibleItemPosition();
int itemsChanged = lastVisible - firstVisible + 1;
// + 1 because we start count items from 0
adapter.notifyItemRangeChanged(firstVisible, itemsChanged);
_
RecyclerView
のデフォルトの方法でニースアニメーションを実現する方法を説明する理由です。 2番目の部分では、データセットが変更された後にすべてのアイテムにスライドアウト/スライドインアニメーションを適用する方法について説明します。アニメーションを有効にするには、データセットがどのように変更されたかをRecyclerView
に伝える必要があります(実行するアニメーションの種類を認識させるため)。これは2つの方法で実行できます。
1)シンプルバージョン:adapter.setHasStableIds(true);
を設定し、public long getItemId(int position)
を介してアイテムのIDを提供する必要がありますAdapter
からRecyclerView
へ。 RecyclerView
はこれらのIDを利用して、adapter.notifyDataSetChanged();
の呼び出し中に削除/追加/移動されたアイテムを特定します
2)Advanced Version:adapter.notifyDataSetChanged();
を呼び出す代わりに、データセットがどのように変更されたかを明示的に示すこともできます。 Adapter
は、データセットの変更を説明するadapter.notifyItemChanged(int position)
、adapter.notifyItemInserted(int position)
、...などのいくつかのメソッドを提供します
データセットの変更を反映するためにトリガーされるアニメーションは、ItemAnimator
によって管理されます。 RecyclerView
には、NiceのデフォルトDefaultItemAnimator
がすでに備わっています。さらに、カスタムItemAnimator
を使用してカスタムアニメーションの動作を定義することができます。
右側のスライドは、データセットからアイテムが削除された場合に再生されるアニメーションです。データセットに追加されたアイテムでは、下からのアニメーションのスライドを再生する必要があります。最初に述べたように、すべての要素が右にスライドして、下からスライドすることが望ましいと思います。データセット変更の前後に表示されている場合でも。通常RecyclerView
は、表示され続けるアイテムのアニメーションを変更/移動するために再生されます。ただし、すべてのアイテムに削除/追加アニメーションを使用するため、変更後に新しい要素のみが存在し、以前に使用可能だったすべてのアイテムが削除されたとアダプターに思わせる必要があります。これは、アダプターの各アイテムにランダムなIDを提供することで実現できます。
@Override
public long getItemId(int position) {
return Math.round(Math.random() * Long.MAX_VALUE);
}
次に、追加/削除されたアイテムのアニメーションを管理するカスタムItemAnimator
を提供する必要があります。表示されるSlidingAnimator
の構造はtheAndroid.support.v7.widget.DefaultItemAnimator
はRecyclerView
で提供されます。また、これは概念の証明であり、アプリで使用する前に調整する必要があります。
public class SlidingAnimator extends SimpleItemAnimator {
List<RecyclerView.ViewHolder> pendingAdditions = new ArrayList<>();
List<RecyclerView.ViewHolder> pendingRemovals = new ArrayList<>();
@Override
public void runPendingAnimations() {
final List<RecyclerView.ViewHolder> additionsTmp = pendingAdditions;
List<RecyclerView.ViewHolder> removalsTmp = pendingRemovals;
pendingAdditions = new ArrayList<>();
pendingRemovals = new ArrayList<>();
for (RecyclerView.ViewHolder removal : removalsTmp) {
// run the pending remove animation
animateRemoveImpl(removal);
}
removalsTmp.clear();
if (!additionsTmp.isEmpty()) {
Runnable adder = new Runnable() {
public void run() {
for (RecyclerView.ViewHolder addition : additionsTmp) {
// run the pending add animation
animateAddImpl(addition);
}
additionsTmp.clear();
}
};
// play the add animation after the remove animation finished
ViewCompat.postOnAnimationDelayed(additionsTmp.get(0).itemView, adder, getRemoveDuration());
}
}
@Override
public boolean animateAdd(RecyclerView.ViewHolder holder) {
pendingAdditions.add(holder);
// translate the new items vertically so that they later slide in from the bottom
holder.itemView.setTranslationY(300);
// also make them invisible
holder.itemView.setAlpha(0);
// this requests the execution of runPendingAnimations()
return true;
}
@Override
public boolean animateRemove(final RecyclerView.ViewHolder holder) {
pendingRemovals.add(holder);
// this requests the execution of runPendingAnimations()
return true;
}
private void animateAddImpl(final RecyclerView.ViewHolder holder) {
View view = holder.itemView;
final ViewPropertyAnimatorCompat anim = ViewCompat.animate(view);
anim
// undo the translation we applied in animateAdd
.translationY(0)
// undo the alpha we applied in animateAdd
.alpha(1)
.setDuration(getAddDuration())
.setInterpolator(new DecelerateInterpolator())
.setListener(new ViewPropertyAnimatorListener() {
@Override
public void onAnimationStart(View view) {
dispatchAddStarting(holder);
}
@Override
public void onAnimationEnd(View view) {
anim.setListener(null);
dispatchAddFinished(holder);
// cleanup
view.setTranslationY(0);
view.setAlpha(1);
}
@Override
public void onAnimationCancel(View view) {
}
}).start();
}
private void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
View view = holder.itemView;
final ViewPropertyAnimatorCompat anim = ViewCompat.animate(view);
anim
// translate horizontally to provide slide out to right
.translationX(view.getWidth())
// fade out
.alpha(0)
.setDuration(getRemoveDuration())
.setInterpolator(new AccelerateInterpolator())
.setListener(new ViewPropertyAnimatorListener() {
@Override
public void onAnimationStart(View view) {
dispatchRemoveStarting(holder);
}
@Override
public void onAnimationEnd(View view) {
anim.setListener(null);
dispatchRemoveFinished(holder);
// cleanup
view.setTranslationX(0);
view.setAlpha(1);
}
@Override
public void onAnimationCancel(View view) {
}
}).start();
}
@Override
public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
// don't handle animateMove because there should only be add/remove animations
dispatchMoveFinished(holder);
return false;
}
@Override
public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) {
// don't handle animateChange because there should only be add/remove animations
if (newHolder != null) {
dispatchChangeFinished(newHolder, false);
}
dispatchChangeFinished(oldHolder, true);
return false;
}
@Override
public void endAnimation(RecyclerView.ViewHolder item) { }
@Override
public void endAnimations() { }
@Override
public boolean isRunning() { return false; }
}
これが最終結果です。
この更新されたソリューションでは、ランダムなIDを持つアダプターをだまして、すべてのアイテムが削除され、新しいアイテムのみが追加されたと考える必要はありません。 2)Advanced Versionを適用する場合-データセットの変更についてアダプタに通知する方法は、adapter
に以前のすべてのアイテムが削除され、すべての新しいアイテムが追加されました:
int oldSize = oldItems.size();
oldItems.clear();
// Notify the adapter all previous items were removed
notifyItemRangeRemoved(0, oldSize);
oldItems.addAll(items);
// Notify the adapter all the new items were added
notifyItemRangeInserted(0, items.size());
// don't call notifyDataSetChanged
//notifyDataSetChanged();
以前に提示されたSlidingAnimator
は、変更をアニメーション化するために依然として必要です。