web-dev-qa-db-fra.com

RecyclerView StaggeredGridLayoutManager problème de réapprovisionnement

J'essaie d'afficher trois éléments (du moins c'est le cas qui me pose problème) dans un RecyclerView avec un StaggeredGridLayoutManager avec deux colonnes. Le premier élément est réparti sur les deux lignes. Voici à quoi ça ressemble:

Correct initial behaviour

Maintenant, je déplace l'élément "Item 2" en haut. Voici le code que j'ai appelé dans l'adaptateur (c'est un exemple que j'ai écrit pour illustrer le problème que j'ai rencontré dans un projet plus complexe):

private int findById(int id) {
    for (int i = 0; i < items.size(); ++i) {
        if (items.get(i).title.equals("Item " + id)) {
            return i;
        }
    }

    return -1;
}

// Moving the item "Item 2" with id = 2 and position = 0
public void moveItem(int id, int position) {
    final int idx = findById(id);
    final Item item = items.get(idx);

    if (position != idx) {
        items.remove(idx);
        items.add(position, item);
        notifyItemMoved(idx, position);
        //notifyDataSetChanged();
    }
}

Après cela, le tableau va bien: [Item 2, Item 1, Item 3]. Cependant, la vue est loin d'être satisfaisante:

Layout issue

Si je touche la RecyclerView (assez pour déclencher l'effet de survol s'il n'y a pas assez d'éléments pour faire défiler), l'élément 2 se déplace vers la gauche, où je m'attendais à le voir en premier lieu (avec une animation Nice):

Expected result

Comme vous l'avez peut-être vu dans le code, j'ai essayé de remplacer notifyItemMoved(idx, position) par un appel à notifyDataSetChanged(). Cela fonctionne, mais le changement n'est pas animé.

J'ai écrit un échantillon complet pour démontrer cela et le mettre sur GitHub . C'est presque minime (il y a des options pour déplacer l'élément et changer leur étendue).

Je ne vois pas ce que je peux faire de mal. Est-ce un bug avec StaggeredGridLayoutManager? Je voudrais éviter notifyDataSetChanged() car je voudrais garder une cohérence en ce qui concerne les animations.


Edit: après quelques recherches, il n’est plus nécessaire d’obtenir un élément complet pour montrer le problème. J'ai enlevé la pleine portée. Lorsque j'essaie de déplacer le point 2 en position 0, il ne bouge pas: le point 1 le suit et le point 3 est déplacé à droite. J'ai donc: une cellule vide, un point 2, une nouvelle ligne, un point 1, point 3. J'ai toujours la bonne disposition après un parchemin.

Ce qui est plus intéressant, c'est que je n'ai pas le problème avec un GridLayoutManager. J'ai besoin d'un élément complet, donc ce n'est pas une solution, mais je suppose que c'est en fait un bug dans la StaggeredGridLayoutManager

16
Marc Plano-Lesay

Je n'ai pas de réponse complète, mais je peux vous indiquer à la fois une solution de contournement et le rapport de bogue (ce qui, à mon avis, est lié). 

L'astuce pour mettre à jour la mise en page de sorte qu'elle ressemble à votre deuxième capture d'écran consiste à appeler invalidateSpanAssignments() sur la StaggeredGridLayoutManger (sglm) après avoir appelé notifyItemMoved(). Le "défi" est que si vous l'appelez immédiatement après nIM(), il ne fonctionnera pas. Si vous retardez l'appel de quelques ms, ce sera le cas. Donc, dans votre code référencé pour MainActivity, j'ai fait de votre sglm un champ privé:

private StaggeredGridLayoutManager sglm;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    adapter = new Adapter();
    recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
    sglm = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
    recyclerView.setLayoutManager(sglm);
    recyclerView.setItemAnimator(new DefaultItemAnimator());
    recyclerView.setAdapter(adapter);
}

Et dans le bloc de commutation, référencez-le dans un gestionnaire:

        case R.id.move_sec_top:
            adapter.moveItem(2, 0);
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    sglm.invalidateSpanAssignments();
                }
            }, 100);
            return true;

Le résultat est que votre animation fonctionne toujours, la mise en page se termine comme vous le souhaitez. C’est un vrai kludge, mais ça marche. Je crois que c'est le même bug que j'ai trouvé et signalé au lien suivant:

https://code.google.com/p/Android/issues/detail?id=93156

Alors que mon "symptôme" et l'appel requis étaient différents, le problème sous-jacent semble être identique.

Bonne chance!

EDIT: Pas besoin de postDelayed, il suffit de poster pour faire l'affaire:

        case R.id.move_sec_top:
            adapter.moveItem(2, 0);
            new Handler().post(new Runnable() {
                @Override
                public void run() {
                    sglm.invalidateSpanAssignments();
                }
            });
            return true;

Ma théorie initiale était que l'appel était bloqué jusqu'à la fin de la configuration, mais je crois que ce n'est pas le cas. Au lieu de cela, je pense maintenant que si vous appelez invalidateSpanAssignments() immédiatement, il s’exécutera trop tôt (avant que les modifications de présentation ne soient terminées). Ainsi, la publication ci-dessus (sans délai) ajoute simplement l'appel à la fin de la file d'attente de rendu, où il se produit après la mise en page.

12
MojoTosh

Eh bien j'ai fait de cette façon.

StaggeredGridLayoutManager gaggeredGridLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
gaggeredGridLayoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
recyclerView.setLayoutManager(gaggeredGridLayoutManager);

dataList = YourDataList (Your Code for Arraylist);

recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerAdapter = new DataAdapter(dataList, recyclerView);
recyclerView.setAdapter(recyclerAdapter);

// Magic line
recyclerView.addOnScrollListener(new ScrollListener());

Créer une classe pour le programme d'écoute personnalisé RecyclerView Scroll .

private class ScrollListener extends RecyclerView.OnScrollListener {
   @Override
   public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        gaggeredGridLayoutManager.invalidateSpanAssignments();
   }
}

J'espère que ceci vous aidera.

1
Hiren Patel