recyclerview-selection简化RecyclerView复选及状态持久化

6,251 阅读4分钟

题记

在查看RecyclerView的官方文档的时候发现了这个recyclerview-selection库,经过测试感觉功能挺好的,省去了自己需要编写大量多选功能的代码,官方文档的guide又不是太清晰,这篇文章仅仅作为简单的记录。

参考:

  1. 如何将多个选择添加到Android RecyclerView(Kotlin)
  2. RecyclerView-Selection(Kotlin)
  3. github示例代码(Java)
  4. Create a List with RecyclerView——Google
  5. androidx.recyclerview.selection——Google

说明

根据官方文档的描述,这个库就是用来处理RecyclerView的Item的选择问题,并且可以在设备配置改变的时候保存已选择的数据,重新创建页面的时候再次加载,省去了我们自己对这部分的操作;同时通过观察者模式提供了item点击监听、长按订阅功能。

使用

  1. 选择一个key的类型。用来构建ItemKeyProvider;可选择的类型目前只有三种: String:基于字符串的稳定标识符可以使用String; Long:当RecyclerView的long stable Id已经在使用时,使用long,但是会有一些限制,在运行时访问一个稳定的id会被限定(不过目前没有发现有什么限定,测试中直接使用了list的索引); Parcelable:任何Parcelable都可以用作selection的key,如果view中的内容与稳定的content:// uri相关联,就是用uri作为key的类型;(这个还没有试验)
public class StringItemKeyProvider extends ItemKeyProvider<String> {

    private List<String> items;

    public StringItemKeyProvider(int scope, List<String> items) {
        super(scope);
        this.items = items;
    }

    @Nullable
    @Override
    public String getKey(int position) {
        return items.get(position);
    }

    @Override
    public int getPosition(@NonNull String key) {
        return items.indexOf(key);
    }
}

  1. 实现ItemDetailsLookup接口,该接口可以接受RecyclerView的Item上发生的MotionEvent事件,我们需要实现其ItemDetails getItemDetails(@NonNull MotionEvent e)方法,通过ReyclerView的findChildView(int,int)方法来判断具体touch的是哪一个Item,强转成我们的ViewHolder类型,调用我们RecyclerView.ViewHolder中的方法来返回一个ItemDetails实例,返回实例的方法是我们自己添加的,ViewHolder中并没有该抽象方法
public class StringItemDetailsLookup extends ItemDetailsLookup {

    private final RecyclerView mRecyclerView;

    StringItemDetailsLookup(RecyclerView recyclerView) {
        mRecyclerView = recyclerView;
    }

    @Nullable
    @Override
    public ItemDetails getItemDetails(@NonNull MotionEvent e) {
        View view = mRecyclerView.findChildViewUnder(e.getX(), e.getY());
        if (view != null) {
            RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(view);
            if (holder instanceof StringItemRecyclerViewAdapter.ItemViewHolder) {
                return ((StringItemRecyclerViewAdapter.ItemViewHolder) holder).getItemDetails();
            }
        }
        return null;
    }
}

以下是返回ItemDetails实例的方法,其中注释的语句是通过继承ItemDetails的子类来创建的。

             ItemDetailsLookup.ItemDetails getItemDetails() {
//                return new StringItemDetail(getAdapterPosition(),datas.get(getAdapterPosition()));
                return new ItemDetailsLookup.ItemDetails() {
                    @Override
                    public int getPosition() {
                        return getAdapterPosition();
                    }

                    @Nullable
                    @Override
                    public Object getSelectionKey() {
                        return datas.get(getAdapterPosition());
                    }
                };
            }
public class StringItemDetails extends ItemDetailsLookup.ItemDetails {

    private int position;
    private String item;

    public StringItemDetails(int position, String item) {
        this.position = position;
        this.item = item;
    }

    @Override
    public int getPosition() {
        return position;
    }

    @Nullable
    @Override
    public Object getSelectionKey() {
        return item;
    }
}

现在我们准备好了StringItemDetailsLookupStringItemKeyProvider两个类,但是准备好了又怎么用呢?

  1. 创建SelectionTracker实例,在Activity的OnCreate中加入如下代码(注意是在一个参数的OnCreate中,Android5.0以上两个参数的方法只有在Manifast文件为Activity设置了android:persistableMode属性才会调用,同样onSaveInstanceStateonRestoreInstanceState也是如此,没注意参数个数导致我浪费 了大半个小时,哭),创建基本的实例,其他的查看源码即可:
        mAdapter = new StringItemRecyclerViewAdapter(ITEMS);
        mRecyclerView.setAdapter(mAdapter);
        mSelectionTracker = new SelectionTracker.Builder<>(
                "string-items-selection",
                mRecyclerView,
                new StringItemKeyProvider(1, ITEMS),
                new StringItemDetailsLookup(mRecyclerView),
                StorageStrategy.createStringStorage())
                //设置可选择的item,这里设置为都可选
                .withSelectionPredicate(SelectionPredicates.<String>createSelectAnything())
                .build();
        mAdapter.setSelectionTracker(mSelectionTracker);

注意上边setSelectionTracker方法,这个方法是我们在自定义的RecyclerView.Adapter中添加的,目的是将我们创建的SelectionTracker注入到我们的Adpter。

 public void setSelectionTracker(SelectionTracker mSelectionTracker) {
    this.mSelectionTracker = mSelectionTracker;
 }

前边说了,我们在Adapter中添加了getItemDetails方法,现在我们的基本工作已经完成,程序能正常运行了,但是我们还看不到多选的效果,因为tracker并不能为我们提供选中高亮功能,高亮功能按我们喜欢的方式实现即可,判断是否选中的代码如下(在Adapter的onBindViewHolder方法中):

  if (mTracker.isSelected(datas.get(i))) {
                viewHolder.tvInfo.setBackgroundColor(Color.parseColor("#80deea"));
            } else {
                viewHolder.tvInfo.setBackgroundColor(Color.WHITE);
            }
  1. 前边的代码已经足以我们完成RecyclerView多选功能,但是当屏幕配置改变,比如旋转屏幕时,我们已选中的item的选中状态就会消失,所以我们需要将我们的选中状态进行持久化操作,tracker为提供了相应的API方便我们持久化,将数据持久化操作与Activity的生命周期事件进行绑定。在Activity的onSaveInstanceStateonRestoreInstanceState中分别调用tracker的onSaveInstanceStateonRestoreInstanceState方法即可,如下:
 @Override
 protected void onRestoreInstanceState(Bundle savedInstanceState) {

    super.onRestoreInstanceState(savedInstanceState);
    if (savedInstanceState != null) {
        mSelectionTracker.onRestoreInstanceState(savedInstanceState);
    }
 }
 @Override
 protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    mSelectionTracker.onSaveInstanceState(outState);
 }

后续

我们可以为tracker添加订阅事件或者item单击事件,这样选择状态改变或者item被点击是我们可以添加自己的逻辑,后续多选操作及多选信息我们都可以通过tracker的事件或者方法获得,具体请看参考3的代码或者查看官网SelectionTracker的说明。

最后,必须第一步的操作,Android studio 3.4竟然无法直接搜索recyclerview添加其依赖,搜索结果是androidx版本的,会有冲突,recyclerview-selection倒是可以直接搜索依赖到项目中。

implementation 'com.android.support:recyclerview-v7:28.0.0'
implementation 'com.android.support:recyclerview-selection:28.0.0'