Android recycler view with multiple item selections

Often we need to show a list of options, let user select some of those and then take some action on selected options. Android provides this feature via androidx.recyclerview.selection.SelectionTracker introduced in “com.android.support:recyclerview-selection:28.0.0-alpha1”

  1. Create selection tracker instance.
selectionTracker = new SelectionTracker.Builder<>("my_selection",
binding.recyclerView,
new Adapter.KeyProvider(binding.recyclerView.getAdapter()),
new Adapter.DetailsLookup(binding.recyclerView),
StorageStrategy.createLongStorage())
.withSelectionPredicate(new Adapter.Predicate())
.build();

2. Create KeyProvider by extending ItemKeyProvider. You can have any complex logic of generating key for selection tracker. For simplicity I am using position as a key itself.

static class KeyProvider extends ItemKeyProvider<Long> {

KeyProvider(RecyclerView.Adapter adapter) {
super(ItemKeyProvider.SCOPE_MAPPED);
}

@Nullable
@Override
public Long getKey(int position) {
return (long) position;
}

@Override
public int getPosition(@NonNull Long key) {
long value = key;
return (int) value;
}
}

3. Create DetailsLookup by extending ItemDetailsLookup.

static class DetailsLookup extends ItemDetailsLookup<Long> {

private RecyclerView recyclerView;

DetailsLookup(RecyclerView recyclerView) {
this.recyclerView = recyclerView;
}

@Nullable
@Override
public ItemDetails<Long> getItemDetails(@NonNull MotionEvent e) {
View view = recyclerView.findChildViewUnder(e.getX(), e.getY());
if (view != null) {
RecyclerView.ViewHolder viewHolder = recyclerView.getChildViewHolder(view);
if (viewHolder instanceof MyViewHolder) {
return ((MyViewHolder) viewHolder).getItemDetails();
}
}
return null;
}
}

4. Create Predicate by extending SelectionPredicate.

static class Predicate extends SelectionTracker.SelectionPredicate<Long> {

@Override
public boolean canSetStateForKey(@NonNull Long key, boolean nextState) {
return true;
}

@Override
public boolean canSetStateAtPosition(int position, boolean nextState) {
return true;
}

@Override
public boolean canSelectMultiple() {
return true;
}
}

5. Create a usual recycler view and corresponding adapter.

6. SelectionTracker records or clear selection whenever user taps on an item and invokes view holder’s bind() method.

void bind(ViewModel viewModel, int position) {
details.position = position;
binding.setViewModel(viewModel);
binding.executePendingBindings();
if (selectionTracker != null) {
if (Adapter.this.selectionTracker.isSelected(details.getSelectionKey())) {
binding.getViewModel().textColor.set(
binding.getRoot().getContext().getColor(R.color.colorAccent));
binding.getRoot().setActivated(true);
} else {
binding.getViewModel().textColor.set(
binding.getRoot().getContext().getColor(R.color.colorPrimary));
binding.getRoot().setActivated(false);
}
}
}

7. Inside bind() method one can check if item is selected or not by using SelectionTracker instance created above and can update the view. In this case view’s text color is changing based upon if view is selected or not.

8. One can use selectionTracker.getSelection() to retrieve all the selected items at a particular time.

9. https://github.com/dalvin/RecyclerViewMultiSelect