List UI is a very common thing in Android Applications. Pretty much every single app has a list of something they want to display.RecyclerView Selection is a library that will allow you to handle item selection a lot easier. It will help you handle motion events and touch events, and convert them into selection in the RecyclerView.This is a fairly flexible library where it allows creating, modifying, inspecting, and monitoring changes to items in a RecyclerView list. It enables users to select items in RecyclerView list using touch or mouse input.
To add selection support to a RecyclerView instance, follow these steps:
1.Add Dependency
As you can imagine, you add the new dependency to your build.gradle file. An important thing to note here is that we are using the new AndroidX artifact.
Add following dependency to your app’s build.gradle
file.28.0.0-alpha is a pre-release version to support the Android P developer preview.
dependencies { implementation 'androidx.recyclerview:recyclerview:1.0.0-beta01' implementation 'androidx.appcompat:appcompat:1.0.0-beta01' implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha1' implementation 'androidx.recyclerview:recyclerview-selection:1.0.0-beta01' .... }
For the setup what you need to do is you want to create a new adapter and important thing is that it uses stable IDs.There is no selection code just yet. You need just basic RecyclerView setup.
2.Implement ItemKeyProvider
Developers must decide on the key type used to identify selected items. There are three key types: Parcelable, String, and Long.ItemKey provider conjunction of stable IDs. It will allow for a quick mapping between the IDs and the items that will handle the selection by the selection library.
public class MyItemKeyProvider extends ItemKeyProvider { private final List<Item> itemList; public MyItemKeyProvider(int scope, List<Item> itemList) { super(scope); this.itemList = itemList; } @Nullable @Override public Object getKey(int position) { return itemList.get(position); } @Override public int getPosition(@NonNull Object key) { return itemList.indexOf(key); } }
3.Implement ItemDetails
ItemDetails implementation provides the selection library with access to information about a specific RecyclerView item. This class is a key component in controling the behaviors of the selection library in the context of a specific activity.
public class MyItemDetail extends ItemDetailsLookup.ItemDetails { private final int adapterPosition; private final Item selectionKey; public MyItemDetail(int adapterPosition, Item selectionKey) { this.adapterPosition = adapterPosition; this.selectionKey = selectionKey; } @Override public int getPosition() { return adapterPosition; } @Nullable @Override public Object getSelectionKey() { return selectionKey; } }
4.Implement ItemDetailsLookup
ItemDetailsLookup
enables the selection library to access information about RecyclerView items given a MotionEvent. It is effectively a factory for ItemDetails
instances that are backed up by a RecyclerView.ViewHolder instance.
It is actually a fairly simple class. You need to overwrite only one method. That returns the position and the selection key for the item that is for the given motion event.
public class MyItemLookup extends ItemDetailsLookup { private final RecyclerView recyclerView; public MyItemLookup(RecyclerView recyclerView) { this.recyclerView = recyclerView; } @Nullable @Override public ItemDetails getItemDetails(@NonNull MotionEvent e) { View view = recyclerView.findChildViewUnder(e.getX(), e.getY()); if (view != null) { RecyclerView.ViewHolder viewHolder = recyclerView.getChildViewHolder(view); if (viewHolder instanceof ItemListAdapter.ItemListViewHolder) { return ((ItemListAdapter.ItemListViewHolder) viewHolder).getItemDetails(); } } return null; } }
The Selection library calls getItemDetails() when it needs access to information about the area. Your implementation must negotiate ViewHolder lookup with the corresponding RecyclerView instance and the subsequent conversion of the ViewHolder instance to an ItemDetailsLookup.ItemDetails instance.
public class ItemListAdapter extends RecyclerView.Adapter<ItemListAdapter.ItemListViewHolder> { .... public class ItemListViewHolder extends RecyclerView.ViewHolder implements ViewHolderWithDetails { ... @Override public ItemDetailsLookup.ItemDetails getItemDetails() { return new MyItemDetail(getAdapterPosition(), itemList.get(getAdapterPosition())); } } }
The adapter, as I said again, nothing super exciting. The only important thing here is that we’re doing stable IDS. This allows for a consistent mapping from ID to the item.
5.Reflect Selected State
When the user selects an item the library will record that in SelectionTracker then notify RecyclerView that the state of the item has changed.The item must then be updated to reflect the new selection status. Without this, the user will not see that the item has been selected.
Update the styling of the view to represent the activated status with a color state list.
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@color/selected_background" android:state_activated="true" /> <item android:drawable="@android:color/white" /> </selector>
item_list.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" ... android:background="@drawable/item_background"> .... </android.support.constraint.ConstraintLayout>
RecyclerView has no default selection mechanism. You have to handle it in onBind.There you might want to change the background of a view or you can use it by setting activated state to get activates state working. You use background for the view and that background is a selectable drawable that has an activated state which will allow to indicate to the user that the item has been selected.
public class ItemListAdapter extends RecyclerView.Adapter<ItemListAdapter.ItemListViewHolder> { ... private SelectionTracker selectionTracker; public void setSelectionTracker(SelectionTracker selectionTracker) { this.selectionTracker = selectionTracker; } .... @Override public void onBindViewHolder(@NonNull ItemListViewHolder holder, int position) { Item item = itemList.get(position); holder.bind(item, selectionTracker.isSelected(item)); } public class ItemListViewHolder extends RecyclerView.ViewHolder implements ViewHolderWithDetails { TextView itemId, itemName, itemPrice; .... public final void bind(Item item, boolean isActive) { itemView.setActivated(isActive); .... } } }
6.Add Contextual Actions
ActionModes used to provide alternative interaction modes and replace parts of the normal UI until finished. Action modes include text selection and contextual actions.
public class ActionModeController implements ActionMode.Callback { private final Context context; private final SelectionTracker selectionTracker; public ActionModeController(Context context, SelectionTracker selectionTracker) { this.context = context; this.selectionTracker = selectionTracker; } @Override public boolean onCreateActionMode(ActionMode actionMode, Menu menu) { return false; } @Override public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) { return false; } @Override public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) { return false; } @Override public void onDestroyActionMode(ActionMode actionMode) { selectionTracker.clearSelection(); } }
7.Build SelectionTracker
Now, what we need to set up is the selection tracker, which actually is the actual machinery behind the scenes, and we pass in RecyclerView, the key provider both of these that we just created.
In order to build a SelectionTracker
instance, your app must supply the same Adapter that you used to initialize RecyclerView to SelectionTracker.Builder
.
itemListView.setAdapter(mAdapter); selectionTracker = new SelectionTracker.Builder<>( "my-selection-id", itemListView, new MyItemKeyProvider(1, itemList), new MyItemLookup(itemListView), StorageStrategy.createLongStorage() ) .withOnDragInitiatedListener(new OnDragInitiatedListener() { @Override public boolean onDragInitiated(@NonNull MotionEvent e) { Log.d(TAG, "onDragInitiated"); return true; } }).build(); mAdapter.setSelectionTracker(selectionTracker);
Register a SelectionTracker.SelectionObserver to be notified when the selection changes. When a selection is first created, start ActionMode to represent this to the user, and provide selection specific actions.
selectionTracker.addObserver(new SelectionTracker.SelectionObserver() { @Override public void onSelectionChanged() { super.onSelectionChanged(); if (selectionTracker.hasSelection() && actionMode == null) { actionMode = startSupportActionMode(new ActionModeController(MainActivity.this, selectionTracker)); setMenuItemTitle(selectionTracker.getSelection().size()); } else if (!selectionTracker.hasSelection() && actionMode != null) { actionMode.finish(); actionMode = null; } else { setMenuItemTitle(selectionTracker.getSelection().size()); } Iterator<Item> itemIterable = selectionTracker.getSelection().iterator(); while (itemIterable.hasNext()) { Log.i(TAG, itemIterable.next().getItemName()); } } });
8.Activity lifecycle events
In order to preserve state you must handling of Activity lifecycle events. your app must call the selection tracker’sonSaveInstanceState()
and onRestoreInstanceState()
methods from the activity’s onSaveInstanceState()and onRestoreInstanceState() methods respectively.
private SelectionTracker mTracker; public void onCreate(Bundle savedInstanceState) { if (savedInstanceState != null) { mTracker.onRestoreInstanceState(savedInstanceState); } } protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); mTracker.onSaveInstanceState(outState); }