RecyclerView ItemTouchHelper 滑动移除动画
RecyclerView ItemTouchHelper swipe remove animation
我有一个滑动删除,它绘制背景(很像收件箱应用程序),由 ItemTouchHelper 实现 - 通过覆盖 onChilDraw 方法并在提供的 canvas 上绘制一个矩形:
ItemTouchHelper mIth = new ItemTouchHelper(
new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) {
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
remove(viewHolder.getAdapterPosition());
}
public boolean onMove(RecyclerView recyclerview, RecyclerView.ViewHolder v, RecyclerView.ViewHolder target) {
return false;
}
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
View itemView = viewHolder.itemView;
Drawable d = ContextCompat.getDrawable(context, R.drawable.bg_swipe_item_right);
d.setBounds(itemView.getLeft(), itemView.getTop(), (int) dX, itemView.getBottom());
d.draw(c);
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
});
上面调用的remove方法在Adapter中:
public void remove(int position) {
items.remove(position);
notifyItemRemoved(position);
}
背景绘制得很好,但是当调用 notifyItemRemoved 时(根据 Debugger 先生的说法),RecyclerView 首先删除了我漂亮的绿色背景,然后将两个相邻的项目推到一起。
我希望它在执行此操作时将背景保留在那里(就像收件箱应用程序一样)。有什么办法吗?
我设法通过使用 Wasabeefs's recyclerview-animators 库让它工作。
我的 ViewHolder 现在扩展了库提供的 AnimateViewHolder:
class MyViewHolder extends AnimateViewHolder {
TextView textView;
public MyViewHolder(View itemView) {
super(itemView);
this.textView = (TextView) itemView.findViewById(R.id.item_name);
}
@Override
public void animateAddImpl(ViewPropertyAnimatorListener listener) {
ViewCompat.animate(itemView)
.translationY(0)
.alpha(1)
.setDuration(300)
.setListener(listener)
.start();
}
@Override
public void preAnimateAddImpl() {
ViewCompat.setTranslationY(itemView, -itemView.getHeight() * 0.3f);
ViewCompat.setAlpha(itemView, 0);
}
@Override
public void animateRemoveImpl(ViewPropertyAnimatorListener listener) {
ViewCompat.animate(itemView)
.translationY(0)
.alpha(1)
.setDuration(300)
.setListener(listener)
.start();
}
}
覆盖的函数实现与 github 上 recyclerview-animators 的自述文件中的相同。
似乎也有必要将 ItemAnimator 更改为自定义的并将 removeDuration 设置为 0(或另一个较低的值 - 这是为了防止闪烁):
recyclerView.setItemAnimator(new SlideInLeftAnimator());
recyclerView.getItemAnimator().setRemoveDuration(0);
这不会造成任何问题,因为使用的正常(非滑动)移除动画是 AnimateViewHolder 中的动画。
所有其他代码都与问题中的相同。我还没有时间弄清楚它的内部工作原理,但如果有人想这样做,请随时更新这个答案。
更新:设置recyclerView.getItemAnimator().setRemoveDuration(0);
实际上破坏了滑动的"rebind"动画。幸运的是,删除该行并在 animateRemoveImpl 中设置更长的持续时间(500 适合我)也解决了闪烁问题。
更新 2:原来 ItemTouchHelper.SimpleCallback 使用 ItemAnimator 的动画持续时间,这就是为什么上面的 setRemoveDuration(0) 会破坏滑动动画。只需将其方法 getAnimationDuration 重写为:
@Override
public long getAnimationDuration(RecyclerView recyclerView, int animationType, float animateDx, float animateDy) {
return animationType == ItemTouchHelper.ANIMATION_TYPE_DRAG ? DEFAULT_DRAG_ANIMATION_DURATION
: DEFAULT_SWIPE_ANIMATION_DURATION;
}
解决了这个问题。
我遇到了同样的问题,我不想引入一个新的库来修复它。 RecyclerView
并没有删除你漂亮的绿色背景,它只是重新绘制自己,而你的 ItemTouchHelper
不再绘制了。实际上它正在绘制,但 dX
为 0,并且从 itemView.getLeft()
(即 0)绘制到 dX
(即 0),因此您什么也看不到。而且画的太多了,待会儿再补。
无论如何在行动画时回到背景:我无法在 ItemTouchHelper
和 onChildDraw
内完成。最后我不得不添加另一个项目装饰器来完成它。它遵循这些原则:
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (parent.getItemAnimator().isRunning()) {
// find first child with translationY > 0
// draw from it's top to translationY whatever you want
int top = 0;
int bottom = 0;
int childCount = parent.getLayoutManager().getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getLayoutManager().getChildAt(i);
if (child.getTranslationY() != 0) {
top = child.getTop();
bottom = top + (int) child.getTranslationY();
break;
}
}
// draw whatever you want
super.onDraw(c, parent, state);
}
}
此代码仅考虑向上移动的行,但您还应考虑向下移动的行。如果您滑动删除最后一行,就会发生这种情况,上面的行将动画化到 space.
当我说您的 ItemTouchHelper 绘制过多时,我的意思是:看起来 ItemTouchHelper
保留 ViewHolder
删除的行以备需要恢复时使用。除了被刷过的 VH 之外,它还会为那些 VH 调用 onChildDraw
。不确定此行为对内存管理的影响,但我需要在 onChildDraw
开始时进行额外检查,以避免绘制 "fantom" 行。
if (viewHolder.getAdapterPosition() == -1) {
return;
}
在你的情况下,它是从左=0 到右=0 绘制的,所以你什么都看不到,但开销在那里。如果您开始看到之前划掉的行绘制了背景,这就是原因。
编辑:我试了一下,看这个blog post and this github repo。
只需更新适配器位置,然后删除动画
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
if (direction == ItemTouchHelper.LEFT) {
remove(position);
} else {
mAdapter.notifyItemChanged(position);
}
}
我有一个滑动删除,它绘制背景(很像收件箱应用程序),由 ItemTouchHelper 实现 - 通过覆盖 onChilDraw 方法并在提供的 canvas 上绘制一个矩形:
ItemTouchHelper mIth = new ItemTouchHelper(
new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) {
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
remove(viewHolder.getAdapterPosition());
}
public boolean onMove(RecyclerView recyclerview, RecyclerView.ViewHolder v, RecyclerView.ViewHolder target) {
return false;
}
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
View itemView = viewHolder.itemView;
Drawable d = ContextCompat.getDrawable(context, R.drawable.bg_swipe_item_right);
d.setBounds(itemView.getLeft(), itemView.getTop(), (int) dX, itemView.getBottom());
d.draw(c);
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
});
上面调用的remove方法在Adapter中:
public void remove(int position) {
items.remove(position);
notifyItemRemoved(position);
}
背景绘制得很好,但是当调用 notifyItemRemoved 时(根据 Debugger 先生的说法),RecyclerView 首先删除了我漂亮的绿色背景,然后将两个相邻的项目推到一起。
我希望它在执行此操作时将背景保留在那里(就像收件箱应用程序一样)。有什么办法吗?
我设法通过使用 Wasabeefs's recyclerview-animators 库让它工作。
我的 ViewHolder 现在扩展了库提供的 AnimateViewHolder:
class MyViewHolder extends AnimateViewHolder {
TextView textView;
public MyViewHolder(View itemView) {
super(itemView);
this.textView = (TextView) itemView.findViewById(R.id.item_name);
}
@Override
public void animateAddImpl(ViewPropertyAnimatorListener listener) {
ViewCompat.animate(itemView)
.translationY(0)
.alpha(1)
.setDuration(300)
.setListener(listener)
.start();
}
@Override
public void preAnimateAddImpl() {
ViewCompat.setTranslationY(itemView, -itemView.getHeight() * 0.3f);
ViewCompat.setAlpha(itemView, 0);
}
@Override
public void animateRemoveImpl(ViewPropertyAnimatorListener listener) {
ViewCompat.animate(itemView)
.translationY(0)
.alpha(1)
.setDuration(300)
.setListener(listener)
.start();
}
}
覆盖的函数实现与 github 上 recyclerview-animators 的自述文件中的相同。
似乎也有必要将 ItemAnimator 更改为自定义的并将 removeDuration 设置为 0(或另一个较低的值 - 这是为了防止闪烁):
recyclerView.setItemAnimator(new SlideInLeftAnimator());
recyclerView.getItemAnimator().setRemoveDuration(0);
这不会造成任何问题,因为使用的正常(非滑动)移除动画是 AnimateViewHolder 中的动画。
所有其他代码都与问题中的相同。我还没有时间弄清楚它的内部工作原理,但如果有人想这样做,请随时更新这个答案。
更新:设置recyclerView.getItemAnimator().setRemoveDuration(0);
实际上破坏了滑动的"rebind"动画。幸运的是,删除该行并在 animateRemoveImpl 中设置更长的持续时间(500 适合我)也解决了闪烁问题。
更新 2:原来 ItemTouchHelper.SimpleCallback 使用 ItemAnimator 的动画持续时间,这就是为什么上面的 setRemoveDuration(0) 会破坏滑动动画。只需将其方法 getAnimationDuration 重写为:
@Override
public long getAnimationDuration(RecyclerView recyclerView, int animationType, float animateDx, float animateDy) {
return animationType == ItemTouchHelper.ANIMATION_TYPE_DRAG ? DEFAULT_DRAG_ANIMATION_DURATION
: DEFAULT_SWIPE_ANIMATION_DURATION;
}
解决了这个问题。
我遇到了同样的问题,我不想引入一个新的库来修复它。 RecyclerView
并没有删除你漂亮的绿色背景,它只是重新绘制自己,而你的 ItemTouchHelper
不再绘制了。实际上它正在绘制,但 dX
为 0,并且从 itemView.getLeft()
(即 0)绘制到 dX
(即 0),因此您什么也看不到。而且画的太多了,待会儿再补。
无论如何在行动画时回到背景:我无法在 ItemTouchHelper
和 onChildDraw
内完成。最后我不得不添加另一个项目装饰器来完成它。它遵循这些原则:
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (parent.getItemAnimator().isRunning()) {
// find first child with translationY > 0
// draw from it's top to translationY whatever you want
int top = 0;
int bottom = 0;
int childCount = parent.getLayoutManager().getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getLayoutManager().getChildAt(i);
if (child.getTranslationY() != 0) {
top = child.getTop();
bottom = top + (int) child.getTranslationY();
break;
}
}
// draw whatever you want
super.onDraw(c, parent, state);
}
}
此代码仅考虑向上移动的行,但您还应考虑向下移动的行。如果您滑动删除最后一行,就会发生这种情况,上面的行将动画化到 space.
当我说您的 ItemTouchHelper 绘制过多时,我的意思是:看起来 ItemTouchHelper
保留 ViewHolder
删除的行以备需要恢复时使用。除了被刷过的 VH 之外,它还会为那些 VH 调用 onChildDraw
。不确定此行为对内存管理的影响,但我需要在 onChildDraw
开始时进行额外检查,以避免绘制 "fantom" 行。
if (viewHolder.getAdapterPosition() == -1) {
return;
}
在你的情况下,它是从左=0 到右=0 绘制的,所以你什么都看不到,但开销在那里。如果您开始看到之前划掉的行绘制了背景,这就是原因。
编辑:我试了一下,看这个blog post and this github repo。
只需更新适配器位置,然后删除动画
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
if (direction == ItemTouchHelper.LEFT) {
remove(position);
} else {
mAdapter.notifyItemChanged(position);
}
}