web-dev-qa-db-fra.com

RecyclerView ItemTouchHelper swipe supprimer l'animation

J'ai une suppression sur le balayage, qui dessine un arrière-plan (un peu comme l'application Inbox), implémentée par un ItemTouchHelper - en redéfinissant la méthode onChilDraw et en dessinant un rectangle sur le canevas fourni:

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

La méthode de suppression appelée ci-dessus se trouve dans l'adaptateur:

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

L’arrière-plan s’en sort bien, mais lors de l’appel de notifyItemRemoved (selon M. Debugger), le RecyclerView supprime d’abord mon joli fond vert, puis rassemble les deux éléments adjacents. 

enter image description hereenter image description here

Je voudrais qu'il garde le fond là-bas pendant qu'il le fait (tout comme l'application de la boîte de réception). Y'a-t'il un quelconque moyen d'y arriver?

16
RokG

J'ai eu le même problème et je ne voulais pas introduire une nouvelle bibliothèque juste pour le réparer. RecyclerView ne supprime pas votre joli fond vert, il se redessine tout seul, et votre ItemTouchHelper ne dessine plus. En réalité, il s’agit de dessiner, mais dX est 0 et tire de itemView.getLeft() (qui est 0) à dX (qui est 0) et vous ne voyez donc rien. Et ça dessine trop, mais j'y reviendrai plus tard.

Quoi qu'il en soit, revenons à l'arrière-plan pendant que les lignes s'animent: je ne pouvais pas le faire dans ItemTouchHelper et onChildDraw. Finalement, j'ai dû ajouter un autre décorateur d'articles pour le faire. Cela va dans ce sens:

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

Ce code prend en compte uniquement les lignes animées, mais vous devez également prendre en compte les lignes à venir. Cela se produit si vous effacez la dernière ligne, les lignes ci-dessus vont s'animer dans cet espace.

Quand j'ai dit que votre ItemTouchHelper dessinait trop, ce que je voulais dire était: On dirait que ItemTouchHelper conserve les rangées ViewHolders au cas où elles auraient besoin d'être restaurées. Il appelle également onChildDraw pour ces VH en plus du VH balayé. Pas sûr des implications de ce comportement sur la gestion de la mémoire, mais il me fallait une vérification supplémentaire au début de onChildDraw pour éviter de dessiner pour des lignes "fantom".

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

Dans votre cas, il s’agit de dessiner de gauche = 0 à droite = 0, de sorte que vous ne voyez rien mais les frais généraux sont là. Si vous commencez à voir des lignes précédemment balayées dessinant leurs arrière-plans, c'est la raison.

EDIT: Je me suis essayé à cela, voir cette blog post et cette github repo .

3
Nemanja Kovacevic

J'ai réussi à le faire fonctionner en utilisant Wasabeefs's recyclerview-animators library. 

Mon ViewHolder étend désormais la bibliothèque AnimateViewHolder fournie par la bibliothèque:

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

}

Les implémentations de fonctions remplacées sont identiques à celles du fichier readme de recyclerview-animators sur github. 

Il semble également nécessaire de remplacer ItemAnimator par un personnalisé et de définir removeDuration sur 0 (ou une autre valeur basse, ceci afin d’empêcher le scintillement):

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

Cela ne pose aucun problème car l'animation de suppression normale (sans balayage) utilisée est celle de AnimateViewHolder.

Tous les autres codes ont été conservés comme dans la question. Je n'ai pas encore eu le temps de comprendre les rouages ​​de cette affaire, mais si quelqu'un a envie de le faire, n'hésitez pas à mettre à jour cette réponse.

Mise à jour: Le réglage de recyclerView.getItemAnimator().setRemoveDuration(0); interrompt en réalité l'animation "de réassociation" du balayage. Heureusement, le fait de supprimer cette ligne et de définir une durée plus longue dans animateRemoveImpl (500 travaux pour moi) résout également le problème de scintillement.

Mise à jour 2: il s'avère que ItemTouchHelper.SimpleCallback utilise les durées d'animation de ItemAnimator, c'est pourquoi la méthode ci-dessus setRemoveDuration (0) interrompt l'animation de balayage. En remplaçant simplement sa méthode getAnimationDuration par:

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

résout ce problème.

1
RokG

Il suffit de mettre à jour la position de l'adaptateur, puis de supprimer l'animation.

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

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

        }
0
Rafael M. G.