首页 文章

在RecyclerView中确认和撤消删除

提问于
浏览
25

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

它运作良好 . 但是我还需要实现一些撤消操作或确认 . 做这个的最好方式是什么?

第一个问题是如何使用确认对话框插入另一个视图代替删除?如果用户选择撤消删除,如何恢复刷卡项目?

4 回答

  • 2

    我同意@Gabor最好软删除项目并显示撤消按钮 .

    但是我正在使用Snackbar来展示UNDO . 这对我来说更容易实现 .

    我正在将Adapter和RecyclerView实例传递给我的ItemTouchHelper回调 . 我的onSwiped很简单,大部分工作都是通过适配器完成的 .

    这是我的代码( edited 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字段 . 我在recyclelerView主机片段的onPause()方法中真正删除了这些项目 .

    edit 2016/01/10

    • 改变了硬编码的位置,正如@Sourabh在评论中所建议的那样

    • 有关RV的适配器和片段的完整示例,请参阅this gist

  • 12

    通常的方法是不要在刷卡时立即删除该项目 . 提出一条消息(它可能是一个小吃吧,或者像在Gmail中一样,覆盖刚刚刷过的项目的消息),并为消息提供超时和撤消按钮 .

    如果用户在消息可见时按下撤消按钮,您只需关闭该消息并返回正常处理 . 仅在超时超过而没有用户按下撤销按钮的情况下删除实际项目 .

    基本上,这些方面的东西:

    @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 布局,通常是不可见的,包含两个项目,一个文本(说明已删除或类似)和一个撤消按钮 .

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

    点击按钮只会删除消息 . (可选)点击文本确认删除并通过调用代码中的相应回调立即删除项目 . 唐'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);
    }
    
  • 1

    我试过JirkaV's solution,但它正在抛出 IndexOutOfBoundsException . 我能够修改他的解决方案,为我工作 . 如果您遇到问题,请尝试并告诉我 .

    @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”按钮时,只需将刷过的物品带回,并将recyclerView滚动到刚刚重新添加的项目的位置 . 因此:

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

    然后当小吃店解散时,我检查用户点击“UNDO”按钮是否解除了小吃吧 . 如果不是,我此时从数据库中删除该项 .

    可能这个解决方案存在性能问题,我还没有发现任何问题 . 如果您发现任何问题,请删除您的评论 .

  • 20

    我已经找到了更简单的方法来执行删除确认对话框:

    @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 .

    • 为项目"return"你只需要调用mRecyclerViewAdapter.notifyItemChanged

    • cancelListener和negativeButtonListener执行相同的操作 . 如果您不希望用户在对话框外点击,则可以选择使用.setCanclable(false)

相关问题