Confirmation and undo removing in RecyclerView

后端 未结 4 2003
粉色の甜心
粉色の甜心 2020-12-24 12:22

I have got a list of simple items in RecyclerView. Using ItemTouchHelper it was very easy to implement \"swipe-to-delete\" behavior.

public class TripsAdapte         


        
相关标签:
4条回答
  • 2020-12-24 13:02

    I agree with @Gabor that it is better to soft delete the items and show the undo button.

    However I'm using Snackbar for showing the UNDO. It was easier to implement for me.

    I'm passing the Adapter and the RecyclerView instance to my ItemTouchHelper callback. My onSwiped is simple and most of the work is done by adapter.

    Here is my code (edited 2016/01/10):

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

    The onItemRemove methos of the adapter is:

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

    The photosToDelete is an ArrayList field of myAdapter. I'm doing the real delete of those items in onPause() method of the recyclerView host fragment.

    Note edit 2016/01/10:

    • changed hard-coded position as @Sourabh suggested in comments
    • for the complete example of adapter and fragment with RV see this gist
    0 讨论(0)
  • 2020-12-24 13:03

    I've figured out much simpler way to do a deletion confirmation dialog working:

    @Override
            public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
                int itemPosition = viewHolder.getAdapterPosition();
    
                new AlertDialog.Builder(YourActivity.this)
                        .setMessage("Do you want to delete: \"" + mRecyclerViewAdapter.getItemAtPosition(itemPosition).getName() + "\"?")
                        .setPositiveButton("Delete", (dialog, which) -> mYourActivityViewModel.removeItem(itemPosition))
                        .setNegativeButton("Cancel", (dialog, which) -> mRecyclerViewAdapter.notifyItemChanged(itemPosition))
                        .setOnCancelListener(dialogInterface -> mRecyclerViewAdapter.notifyItemChanged(itemPosition))
                        .create().show();
            }
    

    Note that:

    • deletion is delegated to ViewModel which updates the mRecyclerViewAdapter upon success.
    • for the item to "return" you just have to call mRecyclerViewAdapter.notifyItemChanged
    • cancelListener and negativeButtonListener perform the same thing. You can opt to use .setCanclable(false) if you don't want the user to tap outside the dialog
    0 讨论(0)
  • 2020-12-24 13:18

    I tried JirkaV's solution, but it was throwing an IndexOutOfBoundsException. I was able to modify his solution to work for me. Please try it and let me know if you run into problems.

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

    How it works

    When the user swipes, a snackbar is shown and the item is removed from the dataset, hence this:

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

    Since the data used in populating the recyclerView is from an SQL database, the swiped item is not removed from the database at this point.

    When the user clicks on the "UNDO" button, the swiped item is simply brought back and the recyclerView scrolls to the position of the just re-added item. Hence this:

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

    Then when the snackbar dismisses, I checked if the snackbar was dismissed by the user clicking on the "UNDO" button. If no, I delete the item from the database at this point.

    Probably there are performance issues with this solution, I haven''t found any. Please if you notice any, drop your comment.

    0 讨论(0)
  • 2020-12-24 13:24

    The usual approach is not to delete the item immediately upon swipe. Put up a message (it could be a snackbar or, as in Gmail, a message overlaying the item just swiped) and provide both a timeout and an undo button for the message.

    If the user presses the undo button while the message is visible, you simply dismiss the message and return to normal processing. Delete the actual item only if the timeout elapses without the user pressing the undo button.

    Basically, something along these lines:

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

    This supposes the existence of an undo layout in the item viewholder, normally invisible, with two items, a text (saying Deleted or similar) and an Undo button.

    <?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>
    

    Tapping the button simply removes the message. Optionally, tapping the text confirms the deletion and deletes the item immediately by calling the appropriate callback in your code. Don't forget to call back to your adapter's notifyItemRemoved():

    public void onDismiss(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, int position) {
      //TODO delete the actual item in your data source
      adapter.notifyItemRemoved(position);
    }
    
    0 讨论(0)
提交回复
热议问题