Arrastra y suelta elementos en RecyclerView con GridLayoutManager

Lo que quiero lograr: tener un RecyclerView con GridLayoutManager que admita arrastrar y soltar y reorganizar los elementos mientras se arrastra.

Nota al pie: primera vez desarrollando cualquier cosa con arrastrar y soltar.

Hay muchos temas sobre cómo lograr esta característica usando un ListView, por ejemplo: https://raw.githubusercontent.com/btownrippleman/FurthestProgress/master/FurthestProgress/src/com/anappforthat/android/languagelineup/DynamicListView.java

Sin embargo, los ejemplos suelen contener una gran cantidad de código, creando mapas de bits de la vista arrastrada y parece que debería ser posible lograr el mismo resultado utilizando View.startDrag(...) y RecyclerView con notifyItemAdded() , notifyItemMoved() y notifyItemRemoved() ya que proporcionan animaciones de reorganización.

Así que jugué un poco y se me ocurrió esto:

 final CardAdapter adapter = new CardAdapter(list); adapter.setHasStableIds(true); adapter.setListener(new CardAdapter.OnLongClickListener() { @Override public void onLongClick(View view) { ClipData data = ClipData.newPlainText("",""); View.DragShadowBuilder builder = new View.DragShadowBuilder(view); final int pos = mRecyclerView.getChildAdapterPosition(view); final Goal item = list.remove(pos); mRecyclerView.setOnDragListener(new View.OnDragListener() { int prevPos = pos; @Override public boolean onDrag(View view, DragEvent dragEvent) { final int action = dragEvent.getAction(); switch(action) { case DragEvent.ACTION_DRAG_LOCATION: View onTopOf = mRecyclerView.findChildViewUnder(dragEvent.getX(), dragEvent.getY()); int i = mRecyclerView.getChildAdapterPosition(onTopOf); list.add(i, list.remove(prevPos)); adapter.notifyItemMoved(prevPos, i); prevPos = i; break; case DragEvent.ACTION_DROP: View underView = mRecyclerView.findChildViewUnder(dragEvent.getX(), dragEvent.getY()); int underPos = mRecyclerView.getChildAdapterPosition(underView); list.add(underPos, item); adapter.notifyItemInserted(underPos); adapter.notifyDataSetChanged(); break; } return true; } }); view.startDrag(data, builder, view, 0); } }); mRecyclerView.setAdapter(adapter); 

Este tipo de trabajo tipo código, obtengo el intercambio, pero muy inestable / tembloroso y, a veces, cuando es refrescante, toda la cuadrícula se reorganiza en el orden original o en algo aleatorio. De todos modos, el código anterior es solo mi primer bash rápido, lo que realmente me interesa saber es si hay alguna forma estándar / de mejores prácticas de arrastrar y soltar con ReyclerView o si la forma correcta de resolverlo sigue siendo la misma. sido utilizado para ListViews durante años?

    En realidad, hay una mejor manera de lograr esto. Puede usar algunas de las clases “complementarias” de RecyclerView :

    ItemTouchHelper , que es

    una clase de utilidad para agregar deslizar para descartar y arrastrar y soltar el soporte a RecyclerView.

    y su ItemTouchHelper.Callback , que es

    el contrato entre ItemTouchHelper y su aplicación

     // Create an `ItemTouchHelper` and attach it to the `RecyclerView` ItemTouchHelper ith = new ItemTouchHelper(_ithCallback); ith.attachToRecyclerView(rv); // Extend the Callback class ItemTouchHelper.Callback _ithCallback = new ItemTouchHelper.Callback() { //and in your imlpementaion of public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { // get the viewHolder's and target's positions in your adapter data, swap them Collections.swap(/*RecyclerView.Adapter's data collection*/, viewHolder.getAdapterPosition(), target.getAdapterPosition()); // and notify the adapter that its dataset has changed _adapter.notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition()); return true; } @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { //TODO } //defines the enabled move directions in each state (idle, swiping, dragging). @Override public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { return makeFlag(ItemTouchHelper.ACTION_STATE_DRAG, ItemTouchHelper.DOWN | ItemTouchHelper.UP | ItemTouchHelper.START | ItemTouchHelper.END); } }; 

    Para más detalles verifique su documentación.

    Esta es mi solución con el reordenamiento de la base de datos:

      ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) { @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { final int fromPosition = viewHolder.getAdapterPosition(); final int toPosition = target.getAdapterPosition(); if (fromPosition < toPosition) { for (int i = fromPosition; i < toPosition; i++) { Collections.swap(mAdapter.getCapitolos(), i, i + 1); } } else { for (int i = fromPosition; i > toPosition; i--) { Collections.swap(mAdapter.getCapitolos(), i, i - 1); } } mAdapter.notifyItemMoved(fromPosition, toPosition); return true; } @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) { MyViewHolder svH = (MyViewHolder ) viewHolder; int index = mAdapter.getCapitolos().indexOf(svH.currentItem); mAdapter.getCapitolos().remove(svH.currentItem); mAdapter.notifyItemRemoved(index); if (emptyView != null) { if (mAdapter.getCapitolos().size() > 0) { emptyView.setVisibility(TextView.GONE); } else { emptyView.setVisibility(TextView.VISIBLE); } } } @Override public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { super.clearView(recyclerView, viewHolder); reorderData(); } }; ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback); itemTouchHelper.attachToRecyclerView(recList); 

    Hay una función de soporte que hace uso de AsyncTask:

     private void reorderData() { AsyncTask task = new AsyncTask() { @Override protected Spanned doInBackground(String... strings) { dbService.deleteAllData(); for (int i = mAdapter.getCapitolos().size() - 1; i >= 0; i--) { Segnalibro s = mAdapter.getCapitolos().get(i); dbService.saveData(s.getIdCapitolo(), s.getVersetto()); } return null; } @Override protected void onPostExecute(Spanned spanned) { } }; task.execute(); } 

    Encontré Advancedrecyclerview bastante útil. ¡¡¡Echar un vistazo!!!