RecyclerView StaggeredGridLayoutManager problema de reordenación

Estoy intentando exhibir tres (por lo menos ése es el caso que tengo un problema con) artículos en un RecyclerView con un StaggeredGridLayoutManager con dos columnas. El primer elemento se distribuye entre las dos filas. Así es como se ve:

Comportamiento inicial correcto

Ahora, voy a mover el elemento "Artículo 2" a la parte superior. Aquí está el código que llamo, en el adaptador (es una muestra que escribí para demostrar el problema que tengo en un proyecto más complejo):

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

Después de eso, la matriz está bien: [Item 2, Item 1, Item 3] . Sin embargo, la vista está lejos de estar bien:

Problema de diseño

Si toco el RecyclerView (lo suficiente como para activar el efecto de overscroll si no hay suficientes elementos para desplazarse), el elemento 2 se mueve a la izquierda, donde esperaba verlo en primer lugar (con una animación agradable):

Resultado Esperado

Como usted vio quizá en el código, intenté substituir notifyItemMoved(idx, position) por una llamada a notifyDataSetChanged() . Funciona, pero el cambio no está animado.

Escribí una muestra completa para demostrar esto y ponerlo en GitHub . Es casi mínimo (hay opciones para mover el elemento y cambiar su alcance).

No veo lo que puedo estar haciendo mal. ¿Se trata de un error con StaggeredGridLayoutManager ? Me gustaría evitar notifyDataSetChanged() como me gustaría mantener la coherencia con respecto a las animaciones.


Editar: después de un poco de excavación, no hay necesidad de un elemento completo para mostrar el problema. Quité el span completo. Cuando trato de mover el elemento 2 a la posición 0, no se mueve : el elemento 1 va tras él y el elemento 3 se mueve a la derecha, así que tengo: celda vacía, elemento 2, nueva línea, elemento 1, elemento 3 Todavía tengo el diseño correcto después de un rollo.

Lo que es más interesante es que no tengo el problema con un GridLayoutManager . Necesito un artículo de pleno-span por lo que no es una solución, pero supongo que es de hecho un error en el StaggeredGridLayoutManager

No tengo una respuesta completa, pero puedo señalarles tanto una solución como el informe de errores (que creo que está relacionado).

El truco para actualizar el diseño para que se vea como su segunda captura de pantalla, es llamar invalidateSpanAssignments() en el StaggeredGridLayoutManger (sglm) después de haber llamado notifyItemMoved() . El "desafío" es que si lo llama inmediatamente después de nIM() , no se ejecutará. Si retrasa la llamada por unos pocos ms, lo hará. Por lo tanto, en su código referenciado para MainActivity, he hecho su sglm un campo privado:

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

Y abajo en el bloque de interruptores, haga referencia a él en un controlador:

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

El resultado es que su animación sigue funcionando, el diseño termina de la manera que lo desea. Esto es un verdadero problema, pero funciona. Creo que este es el mismo error que encontré e informé en el siguiente enlace:

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

Aunque mi "síntoma" y la llamada requerida eran diferentes, la cuestión subyacente parece ser idéntica.

¡Buena suerte!

EDIT: No hay necesidad de postDelayed , simplemente publicar hará el truco:

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

Mi teoría original era que la llamada fue bloqueada hasta que el pase de la disposición fuera encima, pero creo que no es el caso. En su lugar, ahora creo que si llama invalidateSpanAssignments() inmediatamente, en realidad se ejecuta demasiado pronto (antes de que los cambios de diseño hayan terminado). Por lo tanto, el mensaje anterior (sin demora) simplemente agrega la llamada al final de la cola de renderizado donde sucede después del diseño.

Bueno, yo he hecho de esta manera.

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

Cree una clase para el RecyclerView Scroll Listener personalizado .

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

Espero que esto te ayudará.

  • Android animando recyclerview
  • RecyclerView obtiene aplastado cuando se utiliza notifyItemChanged de Handler utilizando Runnable
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.