在RecyclerView中确认和取消删除

我有一个在RecyclerView简单的项目列表。 使用ItemTouchHelper实现“轻扫即删除”行为是非常容易的。

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

它运作良好。 不过,我也需要执行一些撤销操作或确认。 做这个的最好方式是什么?

第一个问题是如何插入另一个视图,而不是通过确认对话框删除? 如果用户select撤消删除,如何恢复滑动的项目?

Solutions Collecting From Web of "在RecyclerView中确认和取消删除"

我同意@Gabor,软删除项目并显示撤消button更好。

不过,我正在使用Snackbar来显示UNDO。 这对我来说更容易实现。

我将适配器和RecyclerView实例传递给我的ItemTouchHelpercallback。 我的onSwiped很简单,大部分工作都是由适配器完成的。

这是我的代码( 编辑2016/01/10 ):

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

适配器的onItemRemove方法是:

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

photosToDelete是myAdapter的ArrayList字段。 我正在删除recyclerView主机片段的onPause()方法中的这些项目。

注意编辑2016/01/10

  • @Sourabh在评论中提出了改变硬编码的立场
  • 有关RV的适配器和片段的完整示例,请参阅此要点

通常的做法是不要在刷卡后立即删除项目。 build立一条消息(可能是一个小吃店,或者像Gmail一样,只是一个消息叠加在刚刚刷过的项目上),同时提供消息的超时和撤消button。

如果用户在消息可见的情况下按下撤消button,则只需closures消息并返回到正常处理。 只有在用户没有按下撤销button的情况下超时,才能删除实际项目。

基本上是这样的:

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

这假定在项目视图持有者中存在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> 

点击button只需删除该消息。 可选地,点击文本确认删除,并通过在您的代码中调用适当的callback来立即删除该项目。 不要忘记callback你的适配器的notifyItemRemoved()

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

我尝试了JirkaV的解决scheme ,但它抛出了IndexOutOfBoundsException 。 我能够修改他的解决scheme为我工作。 请尝试一下,让我知道如果你遇到问题。

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

怎么运行的

当用户滑动时,会显示小吃棒,并将该项目从数据集中移除,因此:

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

由于填充recyclerView所使用的数据来自SQL数据库,因此此时不会从数据库中移除滑动的项目。

当用户点击“UNDO”button时,被轻扫的项目被简单地带回,并且recyclerView滚动到刚重新添加的项目的位置。 因此:

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

然后,当小吃店解雇时,我检查用户是否点击了“UNDO”button,小吃店被解雇了。 如果不是,我从这个时候删除数据库中的项目。

可能有这个解决scheme的性能问题,我还没有find任何。 请注意,如果您发表评论,请发表您的评论。

我已经想出了更简单的方法来执行删除确认对话框工作:

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

注意:

  • 删除被委托给ViewModel ,成功后更新mRecyclerViewAdapter
  • 为“返回”的项目,你只需要调用mRecyclerViewAdapter.notifyItemChanged
  • cancelListenernegativeButtonListener执行相同的操作。 如果您不希望用户在对话框外轻敲,则可以select使用.setCanclable(false)