首页 文章

RecyclerView ItemTouchHelper滑动删除动画

提问于
浏览
16

我有一个刷卡删除,它绘制一个背景(很像收件箱应用程序),由ItemTouchHelper实现 - 通过覆盖onChilDraw方法并在提供的画布上绘制一个矩形:

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首先删除我漂亮的绿色背景,然后将两个相邻的项目推到一起 .

enter image description here

enter image description here

我希望它保留背景,同时它(就像收件箱应用程序一样) . 有没有办法做到这一点?

3 回答

  • 1

    我有同样的问题,我不想介绍一个新的lib只是为了解决它 . RecyclerView 没有删除你漂亮的绿色背景,它只是重绘自己,你的 ItemTouchHelper 不再画画了 . 实际上它是绘图但是 dX 是0并且从 itemView.getLeft() (它是0)绘制到 dX (它是0)所以你什么也看不见 . 它会在稍后再回来 .

    无论如何在行动画时回到背景:我无法在 ItemTouchHelperonChildDraw 中执行此操作 . 最后我不得不添加另一个项目装饰器来做到这一点 . 它遵循这些方针:

    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);
        }
    }
    

    此代码仅考虑动画的行,但您还应考虑下降的行 . 如果您滑动删除最后一行,上面的行将动画到该空间,就会发生这种情况 .

    当我说你的ItemTouchHelper绘制的太多我的意思是:看起来 ItemTouchHelper 保留 ViewHolder 的被删除的行,以防它们需要恢复 . 除了正在刷的VH之外,它还为那些VH调用 onChildDraw . 不确定此行为的内存管理含义,但我需要在 onChildDraw 的开头进行额外检查,以避免绘制"fantom"行 .

    if (viewHolder.getAdapterPosition() == -1) {
        return;
    }
    

    在你的情况下,它是从左= 0到右= 0绘制,所以你没有看到任何东西,但开销就在那里 . 如果你开始看到之前刷过的行描绘他们的背景,这就是原因 .

    编辑:我去了这个,看到这个blog post和这个github repo .

  • 0

    我设法通过使用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;
    }
    

    解决了这个问题 .

  • 3

    只需更新适配器位置,然后删除动画即可

    @Override
            public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
                int position = viewHolder.getAdapterPosition();
    
                if (direction == ItemTouchHelper.LEFT) {
                    remove(position);
                } else {
                    mAdapter.notifyItemChanged(position);
                }
    
            }
    

相关问题