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?
- Eliminación de divisor después de un pie de página en una Recyclerview
- ¿Cómo centrar los elementos de RecyclerView?
- Encabezado RecyclerView bajo elementos de Android
- ¿Cómo mostrar diferentes diseños en recyclerView?
- RecyclerView ItemDecoration Espaciado y Span
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?
- ¿Necesito dependencia de Gradle para RecyclerView?
- Actualizar la barra de progreso en Recyclerview
- Cómo resaltar correctamente el elemento seleccionado en RecyclerView?
- Horizontal RecyclerView con elementos que tienen altura dinámica
- Cómo mantener RecyclerView siempre desplazarse hacia abajo
- Conjunto de datos de cambio RecyclerView
- RecyclerView cortar el último artículo
- Detectar inicio de desplazamiento y desplazamiento final en recyclerview
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.
- Emulador no se ejecuta
- Android – ¿Cómo obtener una lista de todos los filtros de intención disponibles?