如何在 RecyclerView 排序时提供自定义动画(notifyDataSetChanged)
How to provide custom animation during sorting (notifyDataSetChanged) on RecyclerView
目前,通过使用默认动画制作器 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/9aQTyM7K4B0
我如何在没有 RecylerView 的情况下实现这样的动画
几年前,我用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中实现同样的效果?
首先:
- 此解决方案假定在数据集更改后仍然可见的项目也会向右滑出,然后再次从底部滑入(至少我理解您的要求)
- 由于这个要求,我无法为这个问题找到一个简单而好的解决方案(至少在第一次迭代期间)。我发现的唯一方法是欺骗适配器 - 并与框架作斗争,让它做一些它不打算做的事情。这就是为什么第一部分(它通常是如何工作的)描述了如何使用
RecyclerView
和 默认方式 来实现漂亮的动画。第二部分描述了如何在数据集更改后为所有项目强制动画中的幻灯片out/slide。
- 后来我找到了一个更好的解决方案,不需要用随机 ID 欺骗适配器(更新版本跳到底部)。
它通常是如何工作的
要启用动画,您需要告诉 RecyclerView
数据集如何更改(以便它知道应该使用哪种动画 运行)。这可以通过两种方式完成:
1)简单版:
我们需要设置 adapter.setHasStableIds(true);
并通过 public long getItemId(int position)
在您的 Adapter
中向 RecyclerView
提供您的项目 ID。 RecyclerView
利用这些 ID 来确定在调用 adapter.notifyDataSetChanged();
期间哪些项目是 removed/added/moved
2) 高级版本: 除了调用 adapter.notifyDataSetChanged();
你还可以明确说明数据集是如何变化的。 Adapter
提供了几种方法,如 adapter.notifyItemChanged(int position)
、adapter.notifyItemInserted(int position)
、... 来描述数据集的变化
为反映数据集中的变化而触发的动画由 ItemAnimator
管理。 RecyclerView
已经配备了一个不错的默认值 DefaultItemAnimator
。此外,可以使用自定义 ItemAnimator
.
定义自定义动画行为
实现滑出(右)、滑入(下)的策略
右边的幻灯片是从数据集中删除项目时应播放的动画。应该为添加到数据集中的项目播放底部动画的幻灯片。如开头所述,我假设希望所有元素都向右滑出并从底部滑入。即使它们在数据集更改前后可见。通常 RecyclerView
会为这些保持可见的项目播放 change/move 动画。然而,因为我们想对所有项目使用 remove/add 动画,我们需要欺骗适配器认为在更改后只有新元素并且所有以前可用的项目都被删除。这可以通过为适配器中的每个项目提供一个随机 ID 来实现:
@Override
public long getItemId(int position) {
return Math.round(Math.random() * Long.MAX_VALUE);
}
现在我们需要提供自定义 ItemAnimator
来管理 added/removed 项的动画。 SlidingAnimator
的结构与 RecyclerView
提供的 android.support.v7.widget.DefaultItemAnimator
非常相似。另请注意,这是概念证明,在任何应用程序中使用之前应进行调整:
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; }
}
这是最终结果:
更新:在再次阅读 post 时,我想出了一个更好的解决方案
这个更新的解决方案不需要用随机 ID 来欺骗适配器,让其认为所有项目都已删除并且只添加了新项目。如果我们应用 2) 高级版本 - 如何通知适配器有关数据集的更改,我们可以告诉 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
仍然需要动画更改。
如果您不想在每次排序时都重置卷轴,您可以查看另一个方向 (GITHUB demo project):
使用某种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
,但是这个class有私有animateChangeImpl()
所以你必须复制粘贴代码并改变这个方法或使用反射。或者您可以创建自己的 ItemAnimator
class,就像 @Andreas Wenger
在他的 SlidingAnimator
示例中所做的那样。这里的重点是实现 animateChangeImpl
与您的代码类似,有 2 个动画:
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.gif
https://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);
目前,通过使用默认动画制作器 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/9aQTyM7K4B0
我如何在没有 RecylerView 的情况下实现这样的动画
几年前,我用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中实现同样的效果?
首先:
- 此解决方案假定在数据集更改后仍然可见的项目也会向右滑出,然后再次从底部滑入(至少我理解您的要求)
- 由于这个要求,我无法为这个问题找到一个简单而好的解决方案(至少在第一次迭代期间)。我发现的唯一方法是欺骗适配器 - 并与框架作斗争,让它做一些它不打算做的事情。这就是为什么第一部分(它通常是如何工作的)描述了如何使用
RecyclerView
和 默认方式 来实现漂亮的动画。第二部分描述了如何在数据集更改后为所有项目强制动画中的幻灯片out/slide。 - 后来我找到了一个更好的解决方案,不需要用随机 ID 欺骗适配器(更新版本跳到底部)。
它通常是如何工作的
要启用动画,您需要告诉 RecyclerView
数据集如何更改(以便它知道应该使用哪种动画 运行)。这可以通过两种方式完成:
1)简单版:
我们需要设置 adapter.setHasStableIds(true);
并通过 public long getItemId(int position)
在您的 Adapter
中向 RecyclerView
提供您的项目 ID。 RecyclerView
利用这些 ID 来确定在调用 adapter.notifyDataSetChanged();
2) 高级版本: 除了调用 adapter.notifyDataSetChanged();
你还可以明确说明数据集是如何变化的。 Adapter
提供了几种方法,如 adapter.notifyItemChanged(int position)
、adapter.notifyItemInserted(int position)
、... 来描述数据集的变化
为反映数据集中的变化而触发的动画由 ItemAnimator
管理。 RecyclerView
已经配备了一个不错的默认值 DefaultItemAnimator
。此外,可以使用自定义 ItemAnimator
.
实现滑出(右)、滑入(下)的策略
右边的幻灯片是从数据集中删除项目时应播放的动画。应该为添加到数据集中的项目播放底部动画的幻灯片。如开头所述,我假设希望所有元素都向右滑出并从底部滑入。即使它们在数据集更改前后可见。通常 RecyclerView
会为这些保持可见的项目播放 change/move 动画。然而,因为我们想对所有项目使用 remove/add 动画,我们需要欺骗适配器认为在更改后只有新元素并且所有以前可用的项目都被删除。这可以通过为适配器中的每个项目提供一个随机 ID 来实现:
@Override
public long getItemId(int position) {
return Math.round(Math.random() * Long.MAX_VALUE);
}
现在我们需要提供自定义 ItemAnimator
来管理 added/removed 项的动画。 SlidingAnimator
的结构与 RecyclerView
提供的 android.support.v7.widget.DefaultItemAnimator
非常相似。另请注意,这是概念证明,在任何应用程序中使用之前应进行调整:
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; }
}
这是最终结果:
更新:在再次阅读 post 时,我想出了一个更好的解决方案
这个更新的解决方案不需要用随机 ID 来欺骗适配器,让其认为所有项目都已删除并且只添加了新项目。如果我们应用 2) 高级版本 - 如何通知适配器有关数据集的更改,我们可以告诉 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
仍然需要动画更改。
如果您不想在每次排序时都重置卷轴,您可以查看另一个方向 (GITHUB demo project):
使用某种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
,但是这个class有私有animateChangeImpl()
所以你必须复制粘贴代码并改变这个方法或使用反射。或者您可以创建自己的 ItemAnimator
class,就像 @Andreas Wenger
在他的 SlidingAnimator
示例中所做的那样。这里的重点是实现 animateChangeImpl
与您的代码类似,有 2 个动画:
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.gif https://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);