安卓RecyclerView长按进入多选模式删除子条目(DataBinding+ActionMode实现)

安卓RecyclerView长按进入多选模式删除子条目(DataBinding+ActionMode实现)

先看看我们要实现的效果图:

实现思路:

  1. 在RecyclerView的子条目布局前面添加一个Checkbox并且默认不隐藏(gone)
  2. 当RecyclerView的子条目长按时触发Activity的startActionMode事件,并显示隐藏的CheckBox
  3. 用一个List来存储每个条目的是否被选中的状态
  4. 当点击”全选”或者”删除”选项时触发Adapter的相应的方法完成对子条目的选取和删除

代码实现:

Activity布局代码:




    
        
    

    

        
    


整个项目采用DataBinding实现,所以布局最外层是layout标签。整个Activity的布局非常简单,就只有一个RecyclerView。

Activity代码:

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding mBinding;
    private ActionModeAdapter mAdapter;
    private Presenter mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        mPresenter = new Presenter();

        mBinding.recyclerView.setLayoutManager(new LinearLayoutManager(this));
        mBinding.recyclerView.setItemAnimator(new FadeInLeftAnimator());
        mAdapter = new ActionModeAdapter(getList(50));
        mAdapter.setListener(mPresenter);
        mBinding.recyclerView.setAdapter(new ScaleInAnimationAdapter(mAdapter));

    }

    private List getList(int size) {
        List list = new ArrayList<>();
        for (int i = 0; i < size; i++) {
            list.add("Item" + i);
        }
        return list;
    }

    public class Presenter implements ActionMode.Callback, ActionModeAdapter.ItemListener {

        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            getMenuInflater().inflate(R.menu.menu_action_mode, menu);
            mAdapter.startActionMode();
            return true;
        }

        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            return false;
        }

        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
            switch (item.getItemId()) {
                case R.id.delete:
                    mAdapter.deleteItems();
                    break;
                case R.id.select_all:
                    mAdapter.selectAll();
                    break;
                default:
                    break;
            }
            return false;
        }

        @Override
        public void onDestroyActionMode(ActionMode mode) {
            mAdapter.stopActionMode();
        }

        @Override
        public boolean onLongClick(View v) {
           startSupportActionMode(mPresenter);
            return true;
        }
    }

}

Activity的代码也不复杂,主要完成的是对RecyclerView的一些配置工作。这里为了让我们的ReclerView的条目删除有动画效果,用了Github上的一个开源库:https://github.com/wasabeef/recyclerview-animators使用这个库可以方便地为我们的RecyclerView添加各种动画。Activity中有一个内部类Presenter这个类主要是做事件传递的,他实现了两个接口:ActionMode.Callback是用来监听ActionMode中的各种回调事件的,ActionModeAdapter.ItemListener是用来传递RecyclerView子条目长按事件的。可以看到在ActionMode的各种事件中我们做的工作很简单,都是调用Adapter对应的方法即可。

子条目布局:



    

        

        

        

        

        

    

    

        

            

            
        

        

    

子条目同样使用了DataBinding来实现,其中data标签中的几个变量:visible用来指定CheckBox的可见性,checked用来存储CheckBox的选择与否(双向绑定),listener是对子条目长按事件做监听,text是子条目的文本内容。这里因为系统的CheckBox太难看所以找了两张图片来代替默认的CheckBox背景。

Adapter代码(重点):

public class ActionModeAdapter extends RecyclerView.Adapter {

    private List mList;
    private List mBooleanList;
    private ObservableBoolean mSwitch;
    private ItemListener mListener;

    public void setListener(ItemListener listener) {
        mListener = listener;
    }

    public void startActionMode() {
        mSwitch.set(true);
    }

    public void stopActionMode() {
        for (ObservableBoolean checked : mBooleanList) {
            checked.set(false);
        }
        mSwitch.set(false);
    }

    public void deleteItems() {
        for (int i = 0; i < mList.size(); i++) {
            if (mBooleanList.get(i).get()) {
                mList.remove(i);
                mBooleanList.remove(i);
                notifyItemRemoved(i);
                i--;
            }
        }
    }

    public void selectAll() {
        for (ObservableBoolean checked : mBooleanList) {
            checked.set(true);
        }
    }

    public ActionModeAdapter(List list) {
        mList = list;
        mBooleanList = new ArrayList<>();
        for (int i = 0; i < mList.size(); i++) {
            mBooleanList.add(new ObservableBoolean(false));
        }
        mSwitch = new ObservableBoolean(false);
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item, parent, false);
        return new ViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        holder.mBinding.setText(mList.get(position));
        holder.mBinding.setVisible(mSwitch);
        holder.mBinding.setChecked(mBooleanList.get(position));
        if (mListener != null) {
            holder.mBinding.setListener(mListener);
        }
    }

    @Override
    public int getItemCount() {
        return mList == null ? 0 : mList.size();
    }

    class ViewHolder extends RecyclerView.ViewHolder {
        ItemBinding mBinding;

        public ViewHolder(View itemView) {
            super(itemView);
            mBinding = DataBindingUtil.bind(itemView);

        }
    }

    public interface ItemListener {
        boolean onLongClick(View v);
    }

}

Adapter的代码是关键。

变量:

  1. mList是存储子条目文本的
  2. mBooleanList是存储子条目选中状态的(如果你的子条目中有CheckBox那么这个List是必须有的,因为如果没有的话RecyclerView的复用机制会让你的视图产生错乱,所以当你的RecyclerView子条目中含有CheckBox时一定要用一个List存储状态)
  3. mSwitch用来指定CheckBox的可见性(这里是ObservableBoolean类型因为要同时控制多个视图的属性,常规的方式很难做到,但是基于观察者模式的响应式视图却很容易实现)
  4. mListener用来传递子条目长按事件



    方法:

  5. startActionMode():他做的事情很简单就是开始ActionMode时让所有子条目的CheckBox显示出来

  6. stopActionMode():在ActionMode结束时首先把所有的选中状态置为false,然后隐藏所有子条目的CheckBox
  7. selectAll():将所有的选中状态置为true(因为是双向绑定,所以视图上的CheckBox也就会显示对应的状态)
  8. deleteItems():遍历存储子条目选中状态的列表,如果选中的话首先将条目数据从List中删除,接着将存储状态项从List中删除,然后通知RecyclerView条目删除,接着把i–使下次循环依旧指向当前位置。

总结:

实现思路很清晰,不过仍有一些实现上的难点:

  1. 统一控制所有子条目的CheckBox的显示/隐藏
  2. RecyclerView的复用机制带来的子条目CheckBox试图错乱现象

解决:

  1. 常规方法是用一个List将所有子条目的CheckBox都存起来,然后更改状态时遍历List去通知(简单的观察者模式),但是这样做一是太麻烦,二是当子条目数量改变时还要进行解绑,否者可能会内存泄露。但是通过基于观察者模式的响应式视图编程就很容易实现,利用DataBinding将一个ObservableBoolean变量与所有子条目CheckBox的显示状态绑定,这样只需要更改这个变量它就会通知所有子条目的视图更改了。
  2. 用一个List存储所有子条目的选中状态,同样使用DataBinding。不过使用的双向绑定,这样就解决了RecyclerView的复用机制带来的视图错乱的问题

你可能感兴趣的:(安卓RecyclerView长按进入多选模式删除子条目(DataBinding+ActionMode实现))