RecyclerView ItemTouchHelper swipe remove animation

╄→гoц情女王★ 提交于 2019-12-29 04:16:12

问题


I've got a remove on swipe, that draws a background (much like the Inbox app), implemented by an ItemTouchHelper - by overriding the onChilDraw method and drawing a rectangle on the provided 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);
            }
        });

The remove method called above is in the Adapter:

    public void remove(int position) {
       items.remove(position);
       notifyItemRemoved(position);
    }

The background draws out nicely, but when notifyItemRemoved is called (according to Mr. Debugger), the RecyclerView first deletes my pretty green background, and then pushes the two adjacent items together.

I would like it to keep the background there while it does that (just like the Inbox app). Is there any way to do that?


回答1:


I had the same issue and I didn't wanna introduce a new lib just to fix it. The RecyclerView is not deleting your pretty green background, it's just redrawing itself, and your ItemTouchHelper is not drawing anymore. Actually it's drawing but the dX is 0 and is drawing from the itemView.getLeft() (which is 0) to dX (which is 0) so you see nothing. And it's drawing too much, but I'll come back to it later.

Anyway back to the background while rows animate: I couldn't do it within ItemTouchHelper and onChildDraw. In the end I had to add another item decorator to do it. It goes along these lines:

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

This code takes into account only rows animating up, but you should also consider rows coming down. That happens if you swipe delete the last row, rows above are gonna animate down to that space.

When I said your ItemTouchHelper is drawing too much what I meant was: Looks like ItemTouchHelper keeps ViewHolders of removed rows in case they need to be restored. It's also calling onChildDraw for those VHs in addition to the VH being swiped. Not sure about memory management implications of this behavior but I needed an additional check in the start of onChildDraw to avoid drawing for "fantom" rows.

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

In your case it's drawing from left=0 to right=0 so you don't see anything but the overhead is there. If you start seeing previously swiped away rows drawing their backgrounds that is the reason.

EDIT: I had a go at this, see this blog post and this github repo.




回答2:


I managed to get it to work by using Wasabeefs's recyclerview-animators library.

My ViewHolder now extends the library's provided 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();
    }

}

The overrided function implementations are identical to what is in recyclerview-animators' readme on github.

It also seems necessary to change the ItemAnimator to a custom one and set the removeDuration to 0 (or another low value - this is to prevent some flickering):

    recyclerView.setItemAnimator(new SlideInLeftAnimator());
    recyclerView.getItemAnimator().setRemoveDuration(0);

This doesn't cause any problems as the normal (non-swiping) remove animation used is the one in the AnimateViewHolder.

All other code was kept the same as in the question. I haven't had the time to figure out the inner workings of this yet, but if anyone feels like doing it feel free to update this answer.

Update: Setting recyclerView.getItemAnimator().setRemoveDuration(0); actually breaks the "rebind" animation of the swipe. Fortunately, removing that line and setting a longer duration in animateRemoveImpl (500 works for me) also solves the flickering problem.

Update 2: Turns out that ItemTouchHelper.SimpleCallback uses ItemAnimator's animation durations, which is why the above setRemoveDuration(0) breaks the swipe animation. Simply overriding it's method getAnimationDuration to:

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

solves that problem.




回答3:


Just update the adapter position and then remove the animation

 @Override
        public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
            int position = viewHolder.getAdapterPosition();

            if (direction == ItemTouchHelper.LEFT) {
                remove(position);
            } else {
                mAdapter.notifyItemChanged(position);
            }

        }


来源:https://stackoverflow.com/questions/31418905/recyclerview-itemtouchhelper-swipe-remove-animation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!