Confirmación y eliminación de deshacer en RecyclerView

Tengo una lista de elementos simples en RecyclerView. Utilizando ItemTouchHelper fue muy fácil implementar el comportamiento de "borrar para eliminar".

public class TripsAdapter extends RecyclerView.Adapter<TripsAdapter.VerticalItemHolder> { private List<Trip> mTrips; private Context mContext; private RecyclerView mRecyclerView; [...] //Let adapter know his RecyclerView. Attaching ItemTouchHelper @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new TripItemTouchHelperCallback()); itemTouchHelper.attachToRecyclerView(recyclerView); mRecyclerView = recyclerView; } [...] public class TripItemTouchHelperCallback extends ItemTouchHelper.SimpleCallback { public TripItemTouchHelperCallback (){ super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.RIGHT); } @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { //some "move" implementation } @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) { //AND WHAT HERE? } } } 

Funciona bien. Sin embargo, también necesito implementar alguna acción de deshacer o confirmación. ¿Cuál es la mejor manera de hacer esto?

La primera pregunta es cómo insertar otra vista en lugar de eliminado con diálogo de confirmación? ¿Y cómo restaurar el artículo swiped, si el usuario elige deshacer la eliminación?

Estoy de acuerdo con @Gabor que es mejor borrar los elementos y mostrar el botón de deshacer.

Sin embargo, estoy usando Snackbar para mostrar el UNDO. Era más fácil de implementar para mí.

Estoy pasando el adaptador y la instancia RecyclerView a mi devolución de llamada ItemTouchHelper. Mi onSwiped es simple y la mayor parte del trabajo es hecho por el adaptador.

Aquí está mi código ( editado 2016/01/10 ):

 @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { mAdapter.onItemRemove(viewHolder, mRecyclerView); } 

El onItemRemove methos del adaptador es:

  public void onItemRemove(final RecyclerView.ViewHolder viewHolder, final RecyclerView recyclerView) { final int adapterPosition = viewHolder.getAdapterPosition(); final Photo mPhoto = photos.get(adapterPosition); Snackbar snackbar = Snackbar .make(recyclerView, "PHOTO REMOVED", Snackbar.LENGTH_LONG) .setAction("UNDO", new View.OnClickListener() { @Override public void onClick(View view) { int mAdapterPosition = viewHolder.getAdapterPosition(); photos.add(mAdapterPosition, mPhoto); notifyItemInserted(mAdapterPosition); recyclerView.scrollToPosition(mAdapterPosition); photosToDelete.remove(mPhoto); } }); snackbar.show(); photos.remove(adapterPosition); notifyItemRemoved(adapterPosition); photosToDelete.add(mPhoto); } 

El archivo photosToDelete es un campo ArrayList de myAdapter. Estoy haciendo la eliminación real de esos elementos en el método onPause () del fragmento de host recyclerView.

Nota editar 2016/01/10 :

  • Cambió la posición codificada como @Sourabh sugirió en comentarios
  • Para el ejemplo completo de adaptador y fragmento con RV ver esta esencia

El acercamiento generalmente no es suprimir el artículo inmediatamente sobre golpe. Ponga un mensaje (puede ser una barra de menús o, como en Gmail, un mensaje sobre el que acaba de pasar el artículo) y proporcionar un tiempo de espera y un botón de deshacer para el mensaje.

Si el usuario presiona el botón de deshacer mientras el mensaje es visible, simplemente descarta el mensaje y vuelve al proceso normal. Elimina el elemento real sólo si transcurren los tiempos de espera sin que el usuario presione el botón de deshacer.

Básicamente, algo en este sentido:

 @Override public void onSwiped(final RecyclerView.ViewHolder viewHolder, int direction) { final View undo = viewHolder.itemView.findViewById(R.id.undo); if (undo != null) { // optional: tapping the message dismisses immediately TextView text = (TextView) viewHolder.itemView.findViewById(R.id.undo_text); text.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { callbacks.onDismiss(recyclerView, viewHolder, viewHolder.getAdapterPosition()); } }); TextView button = (TextView) viewHolder.itemView.findViewById(R.id.undo_button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { recyclerView.getAdapter().notifyItemChanged(viewHolder.getAdapterPosition()); clearView(recyclerView, viewHolder); undo.setVisibility(View.GONE); } }); undo.setVisibility(View.VISIBLE); undo.postDelayed(new Runnable() { public void run() { if (undo.isShown()) callbacks.onDismiss(recyclerView, viewHolder, viewHolder.getAdapterPosition()); } }, UNDO_DELAY); } } 

Esto supone la existencia de un diseño de undo en el visor de elementos, normalmente invisible, con dos elementos, un texto (que dice Eliminado o similar) y un botón Deshacer .

 <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content"> ... <LinearLayout android:id="@+id/undo" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/darker_gray" android:orientation="horizontal" android:paddingLeft="10dp" android:paddingRight="10dp" android:visibility="gone"> <TextView android:id="@+id/undo_text" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="2" android:gravity="center|start" android:text="Deleted" android:textColor="@android:color/white"/> <TextView android:id="@+id/undo_button" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center|end" android:text="UNDO" android:textColor="?attr/colorAccent" android:textStyle="bold"/> </LinearLayout> </FrameLayout> 

Al pulsar el botón simplemente elimina el mensaje. Opcionalmente, tocando el texto confirma la eliminación y elimina el elemento inmediatamente llamando a la devolución de llamada adecuada en su código. No olvide llamar de nuevo a notifyItemRemoved() su adaptador:

 public void onDismiss(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, int position) { //TODO delete the actual item in your data source adapter.notifyItemRemoved(position); } 

Intenté la solución de JirkaV , pero lanzaba una IndexOutOfBoundsException . Pude modificar su solución para trabajar para mí. Por favor, inténtelo y hágamelo saber si tiene problemas.

  @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { final int adapterPosition = viewHolder.getAdapterPosition(); final BookItem bookItem = mBookItems.get(adapterPosition); //mBookItems is an arraylist of mBookAdpater; snackbar = Snackbar .make(mRecyclerView, R.string.item_removed, Snackbar.LENGTH_LONG) .setAction(R.string.undo, new View.OnClickListener() { @Override public void onClick(View view) { mBookItems.add(adapterPosition, bookItem); mBookAdapter.notifyItemInserted(adapterPosition); //mBookAdapter is my Adapter class mRecyclerView.scrollToPosition(adapterPosition); } }) .setCallback(new Snackbar.Callback() { @Override public void onDismissed(Snackbar snackbar, int event) { super.onDismissed(snackbar, event); Log.d(TAG, "SnackBar dismissed"); if (event != DISMISS_EVENT_ACTION) { Log.d(TAG, "SnackBar not dismissed by click event"); //In my case I doing a database transaction. The items are only deleted from the database if the snackbar is not dismissed by click the UNDO button mDatabase = mBookHelper.getWritableDatabase(); String whereClause = "_id" + "=?"; String[] whereArgs = new String[]{ String.valueOf(bookItem.getDatabaseId()) }; mDatabase.delete(BookDbSchema.BookEntry.NAME, whereClause, whereArgs); mDatabase.close(); } } }); snackbar.show(); mBookItems.remove(adapterPosition); mBookAdapter.notifyItemRemoved(adapterPosition); } 

Cómo funciona

Cuando el usuario pasa, se muestra un snackbar y el elemento se elimina del dataset, por lo tanto:

 snackbar.show(); BookItems.remove(adapterPosition); mBookAdapter.notifyItemRemoved(adapterPosition); 

Dado que los datos utilizados para rellenar el recyclerView son de una base de datos SQL, el elemento swiped no se elimina de la base de datos en este momento.

Cuando el usuario hace clic en el botón "UNDO", el elemento deslizado se vuelve a traer y el recicladorVista se desplaza hasta la posición del elemento recién agregado. Por lo tanto esto:

  mBookItems.add(adapterPosition, bookItem); mBookAdapter.notifyItemInserted(adapterPosition); mRecyclerView.scrollToPosition(adapterPosition); 

Luego, cuando el snackbar despedidas, me registré si el snackbar fue descartado por el usuario haciendo clic en el "UNDO" botón. Si no, elimino el elemento de la base de datos en este momento.

Probablemente hay problemas de rendimiento con esta solución, no he encontrado ninguno. Por favor, si nota alguna, deje su comentario.

  • Error al inflar la clase android.support.v7.widget.RecyclerView
  • Desactivar desplazamiento para posición en RecyclerView utilizando ItemTouchHelper.SimpleCallback
  • Android recyclerView findViewHolderForAdapterPosition devuelve null
  • CheckBox en RecyclerView mantiene la comprobación de diferentes elementos
  • Cómo implementar coverflow con recyclerview
  • RecyclerView - saltar a la posición, luego desplazamiento suave hasta la parte superior
  • RecyclerView desordenado los datos al desplazarse
  • Cómo agregar consistentemente fondos alternativos a las vistas de elementos en RecyclerView
  • Recyclerview no llama aCreateViewHolder
  • Seleccione elementos en RecyclerView
  • Cómo obtener el elemento del adaptador RecyclerView en Android
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.