How to implement multi-select in RecyclerView?
I know it's a little bit late to answer this question. And I don't know whether it meets requirements of OP or not. But this may help someone. I implemented this multi-select RecyclerView with a simple trick. Here is my code.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#EEE">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
item_row.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:layout_marginBottom="1dp"
android:background="#FFF"
android:clickable="true"
android:orientation="vertical">
<TextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
tools:text="TextView" />
</LinearLayout>
In item_row.xml
android:clickable="true"
is important.
MainActivity.java
public class MainActivity extends AppCompatActivity {
private List<Model> mModelList;
private RecyclerView mRecyclerView;
private RecyclerView.Adapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
mAdapter = new RecyclerViewAdapter(getListData());
LinearLayoutManager manager = new LinearLayoutManager(MainActivity.this);
mRecyclerView.setHasFixedSize(true);
mRecyclerView.setLayoutManager(manager);
mRecyclerView.setAdapter(mAdapter);
}
private List<Model> getListData() {
mModelList = new ArrayList<>();
for (int i = 1; i <= 25; i++) {
mModelList.add(new Model("TextView " + i));
}
return mModelList;
}
}
Model.java
public class Model {
private String text;
private boolean isSelected = false;
public Model(String text) {
this.text = text;
}
public String getText() {
return text;
}
public void setSelected(boolean selected) {
isSelected = selected;
}
public boolean isSelected() {
return isSelected;
}
}
RecyclerViewAdapter.java
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.MyViewHolder> {
private List<Model> mModelList;
public RecyclerViewAdapter(List<Model> modelList) {
mModelList = modelList;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_row, parent, false);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(final MyViewHolder holder, int position) {
final Model model = mModelList.get(position);
holder.textView.setText(model.getText());
holder.view.setBackgroundColor(model.isSelected() ? Color.CYAN : Color.WHITE);
holder.textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
model.setSelected(!model.isSelected());
holder.view.setBackgroundColor(model.isSelected() ? Color.CYAN : Color.WHITE);
}
});
}
@Override
public int getItemCount() {
return mModelList == null ? 0 : mModelList.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder {
private View view;
private TextView textView;
private MyViewHolder(View itemView) {
super(itemView);
view = itemView;
textView = (TextView) itemView.findViewById(R.id.text_view);
}
}
}
How does it work?onBindViewHolder()
method binds the data from ArrayList to View objects. So, on time binding the data to the view it gets the single object from ArrayList that is Model model = mModelList.get(position);
with the current position. Now we need to check whether that particular object is selected or not. Like this,
model.isSelected()
which returns either true
or false
. If that object is already selected we need to change the background color of row_item
selected. For this here is the code
holder.view.setBackgroundColor(model.isSelected() ? Color.CYAN : Color.WHITE);
If it's selected change the background color to cyan
else white
.
For selection we need to use setOnClickListener()
method. (here I am using only a TextView
. So I am performing a click event on TextView
). Here holder.view
means the entire single item_row
. Onclick toggle the boolean values to true
or false
.
holder.textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
model.setSelected(!model.isSelected());
holder.view.setBackgroundColor(model.isSelected() ? Color.CYAN : Color.WHITE);
}
});
In your Activity or Fragment which is hosting RecyclerView, you can get the selected objects/items like this
String text = "";
for (Model model : mModelList) {
if (model.isSelected()) {
text += model.getText();
}
}
Log.d("TAG","Output : " + text);
Here is the output
Edit 1: Restricting user to select only one item.
private int lastSelectedPosition = -1; // declare this variable
...
// your code
...
@Override
public void onBindViewHolder(final MyViewHolder holder, int position) {
final Model model = mModelList.get(position);
holder.textView.setText(model.getText());
holder.view.setBackgroundColor(model.isSelected() ? Color.CYAN : Color.WHITE);
holder.textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// check whether you selected an item
if(lastSelectedPosition > 0) {
mModelList.get(lastSelectedPosition).setSelected(false);
}
model.setSelected(!model.isSelected());
holder.view.setBackgroundColor(model.isSelected() ? Color.CYAN : Color.WHITE);
// store last selected item position
lastSelectedPosition = holder.getAdapterPosition();
}
});
}
I hope it will be helpful.
How to select multiple items in recycler view android?
This is what is causing the double or multiple selections.
Recyclerview works this way, that views are recycled.
So you have to hide, check or uncheck each item everytine a view is inflated.
So in the onbindViewholder
you have to setChecked()
to true or false depending on your scenario.
My approach for this problem would be:
Instead of passing views to the parent fragment, keep the check logic in the adapter this way:
if (isItemSelected){
selection.setChecked(true);
}else{
selection.setChecked(false);
}
By so doing, there will be perfect checking and unchecking.
-- Update --
Create a Selectable adapter class to provide the isSelected()
method as follows
SelectableAdapter
public abstract class SelectableAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
private static final String TAG = SelectableAdapter.class.getSimpleName();
private final SparseBooleanArray selectedItems;
SelectableAdapter() {
selectedItems = new SparseBooleanArray();
}
boolean isSelected(int position) {
return getSelectedItems().contains(position);
}
public void toggleSelection(int position) {
if (selectedItems.get(position, false)) {
selectedItems.delete(position);
} else {
selectedItems.put(position, true);
}
notifyItemChanged(position);
}
public void selectAll() {
for (int i = 0; i < getItemCount(); i++) {
if (!(selectedItems.get(i, false))) {
selectedItems.put(i, true);
}
notifyItemChanged(i);
}
notifyDataSetChanged();
}
public void clearSelection() {
List<Integer> selection = getSelectedItems();
selectedItems.clear();
for (Integer i : selection) {
notifyItemChanged(i);
}
}
public int getSelectedItemCount() {
return selectedItems.size();
}
public List<Integer> getSelectedItems() {
List<Integer> items = new ArrayList<>(selectedItems.size());
for (int i = 0; i < selectedItems.size(); ++i) {
items.add(selectedItems.keyAt(i));
}
return items;
}
}
Have your Adapter extend the selectableAdapter
as follows:
Adapter Class
public class RecyclerViewAdapter extends SelectableAdapter<RclAdapter.ViewHolder>
On the fragment use toggleSelection
to set a position as selected or not Selected.
@Override
public void onclick(int position) {
if (!isSelectionMode) {
Intent intent = new Intent(getActivity(), FullPhoto.class);
intent.putExtra("uri", arrayList.get(position).getUri());
startActivity(intent);
}
else
{
// Use the adapter instance here
adapter.toggleSelection(position);
}
}
Remember toggleSelection
notifies the adapter and calls onBindViewHolder
.
In the adapter: onBindViewHolder
implement the selection logic to show and hide the checkbox.
If you only set it to View.VISIBLE
and don't set it to View.GONE
for the ones not selected, you will still have the same problem.
Implementing multi selection in Android RecyclerView
A RecyclerView
as its name suggests, recycles views. That means that once a view scrolls off screen, it can be reused.
Before a view is reused, it still contains all of the settings from the last time it was used. For example, if it contains a TextView
, that TextView
will still have its Text property set to whatever it was the last time it was displayed.
The reason that some items "seem" to be selected is because your selected views that have scrolled off screen are now being reused and you have not unselected them.
In your OnBindViewHolder
method, you need to "reset" all the views back to their defaults. In this case that would be to "turn off" whatever method you use to make a view appear selected.
For example:
@Override
public void onBindViewHolder(MyViewHolder myViewHolder, final int position) {
final PatternImages currentPattern = patternImages.get(position);
myViewHolder.setData(currentPattern, position);
myViewHolder.itemView.setBackgroundColor(currentPattern.isSelected() ?R.color.Red: R.color.WHITE); // choose your colors
myViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
currentPattern.setSelected(!currentPattern.isSelected())
notifyItemChanged(position);
}
});
}
Essentially, every time you bind, you set the background colour to the selected or non selected state based on the relevant property in your model.
RecyclerView selects multiple items on a item click
Add the else
branch in the onBindViewHolder
to avoid the repetition of the visible ticked items while recycling views
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
holder.mItem = mValues.get(position);
holder.mNameTextView.setText(holder.mItem.getName());
holder.mPhoneNumberView.setText(holder.mItem.getPhoneNumber());
if (mSelectedValues.contains(holder.mItem)) {
holder.tickImageView.setVisibility(View.VISIBLE);
} else {
holder.tickImageView.setVisibility(View.GONE);
}
holder.mView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onItemClick(holder, position);
}
});
}
Android Jetpack: How to to multi selection in recycler view with PagingDataAdapter?
I have implemented a custom way of doing it.
I used an observable list in the adapter and expose methods to the viewholders to select themselfs.
You could of course create a base class for this. I should eventually do that too :)
My Code:
Adapter
class PhotoAdapter(
private val context: Context,
private val photoRepository: PhotoRepository,
private val viewPhotoCallback: KFunction1<Int, Unit>,
val lifecycleOwner: LifecycleOwner
) : PagingDataAdapter<Photo, PhotoItemViewHolder>(differCallback) {
/**
* Holds the layout positions of the selected items.
*/
val selectedItems = ObservableArrayList<Int>()
/**
* Holds a Boolean indicating if multi selection is enabled. In a LiveData.
*/
var isMultiSelectMode: MutableLiveData<Boolean> = MutableLiveData(false)
override fun onBindViewHolder(holderItem: PhotoItemViewHolder, position: Int) {
holderItem.bindTo(this, getItem(position))
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoItemViewHolder =
PhotoItemViewHolder(parent, context, photoRepository)
/**
* Called by ui. On Click.
*/
fun viewPhoto(position: Int) {
viewPhotoCallback.invoke(getItem(position)?.id!!)
}
/**
* Disables multi selection.
*/
fun disableSelection() {
selectedItems.clear()
isMultiSelectMode.postValue(false)
}
/**
* Enables multi selection.
*/
fun enableSelection() {
isMultiSelectMode.postValue(true)
}
/**
* Add an item it the selection.
*/
fun addItemToSelection(position: Int): Boolean = selectedItems.add(position)
/**
* Remove an item to the selection.
*/
fun removeItemFromSelection(position: Int) = selectedItems.remove(position)
/**
* Indicate if an item is already selected.
*/
fun isItemSelected(position: Int) = selectedItems.contains(position)
/**
* Indicate if an item is the last selected.
*/
fun isLastSelectedItem(position: Int) = isItemSelected(position) && selectedItems.size == 1
/**
* Select all items.
*/
fun selectAll() {
for (i in 0 until itemCount) {
if (!isItemSelected(i)) {
addItemToSelection(i)
}
}
}
/**
* Get all items that are selected.
*/
fun getAllSelected(): List<Photo> {
val items = mutableListOf<Photo>()
for(position in selectedItems) {
val photo = getItem(position)
if (photo != null) {
items.add(photo)
}
}
return items
}
companion object {
private val differCallback = object : DiffUtil.ItemCallback<Photo>() {
override fun areItemsTheSame(oldItem: Photo, newItem: Photo): Boolean =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Photo, newItem: Photo): Boolean =
oldItem == newItem
}
}
}
ViewHolder
class PhotoItemViewHolder(
parent: ViewGroup,
private val context: Context,
private val photoRepository: PhotoRepository
) : RecyclerView.ViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.photo_item, parent, false)
) {
private val imageView: ImageView = itemView.findViewById(R.id.photoItemImageView)
private val checkBox: CheckBox = itemView.findViewById(R.id.photoItemCheckBox)
var photo: Photo? = null
private lateinit var adapter: PhotoAdapter
/**
* Binds the parent adapter and the photo to the ViewHolder.
*/
fun bindTo(adapter: PhotoAdapter, photo: Photo?) {
this.photo = photo
this.adapter = adapter
imageView.setOnClickListener {
if (adapter.isMultiSelectMode.value!!) {
// If the item clicked is the last selected item
if (adapter.isLastSelectedItem(layoutPosition)) {
adapter.disableSelection()
return@setOnClickListener
}
// Set checked if not already checked
setItemChecked(!adapter.isItemSelected(layoutPosition))
} else {
adapter.viewPhoto(layoutPosition)
}
}
imageView.setOnLongClickListener {
if (!adapter.isMultiSelectMode.value!!) {
adapter.enableSelection()
setItemChecked(true)
}
true
}
adapter.isMultiSelectMode.observe(adapter.lifecycleOwner, {
if (it) { // When selection gets enabled, show the checkbox
checkBox.show()
} else {
checkBox.hide()
}
})
adapter.selectedItems.addOnListChangedCallback(onSelectedItemsChanged)
listChanged()
loadThumbnail()
}
/**
* Listener for changes in selected images.
* Calls [listChanged] whatever happens.
*/
private val onSelectedItemsChanged =
object : ObservableList.OnListChangedCallback<ObservableList<Int>>() {
override fun onChanged(sender: ObservableList<Int>?) {
listChanged()
}
override fun onItemRangeChanged(
sender: ObservableList<Int>?,
positionStart: Int,
itemCount: Int
) {
listChanged()
}
override fun onItemRangeInserted(
sender: ObservableList<Int>?,
positionStart: Int,
itemCount: Int
) {
listChanged()
}
override fun onItemRangeMoved(
sender: ObservableList<Int>?,
fromPosition: Int,
toPosition: Int,
itemCount: Int
) {
listChanged()
}
override fun onItemRangeRemoved(
sender: ObservableList<Int>?,
positionStart: Int,
itemCount: Int
) {
listChanged()
}
}
private fun listChanged() {
val isSelected = adapter.isItemSelected(layoutPosition)
val padding = if (isSelected) 20 else 0
checkBox.isChecked = isSelected
imageView.setPadding(padding)
}
private fun setItemChecked(checked: Boolean) {
layoutPosition.let {
if (checked) {
adapter.addItemToSelection(it)
} else {
adapter.removeItemFromSelection(it)
}
}
}
/**
* Load the thumbnail for the [photo].
*/
private fun loadThumbnail() {
GlobalScope.launch(Dispatchers.IO) {
val thumbnailBytes =
photoRepository.readPhotoThumbnailFromInternal(context, photo?.id!!)
if (thumbnailBytes == null) {
Timber.d("Error loading thumbnail for photo: $photo.id")
return@launch
}
val thumbnailBitmap =
BitmapFactory.decodeByteArray(thumbnailBytes, 0, thumbnailBytes.size)
runOnMain { // Set thumbnail in main thread
imageView.setImageBitmap(thumbnailBitmap)
}
}
}
}
Multiple selected items RecyclerView in Activity.java
I totally change my code but its now work powerfully, here my code looks like :
I change my
Adapter
into generic adapter. Its not process any data, but its throw to my activity.public class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder> {
public int lastCheckedPosition = -1;
private static OnRecyclerViewItemClickedListener recyclerViewItemClickedListener;
public void setOnRecyclerViewClickedListener (OnRecyclerViewItemClickedListener l) {
recyclerViewItemClickedListener = l;
}
public interface OnRecyclerViewItemClickedListener {
void OnRecyclerViewItemClicked(int position);
void OnRecyclerViewItemBind(ViewHolder holder, int position);
int OnRecyclerViewItemCount();
}
public Adapter() {
super();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.table_item_empty, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
recyclerViewItemClickedListener.OnRecyclerViewItemBind(holder,position);
}
@Override
public int getItemCount() {
return recyclerViewItemClickedListener.OnRecyclerViewItemCount();
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public TextView txt_no_table;
public ViewHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(this);
txt_no_table = (TextView) itemView.findViewById(R.id.txt_no_table_empty);
}
@Override
public void onClick(View itemView) {
recyclerViewItemClickedListener.OnRecyclerViewItemClicked(getAdapterPosition());
}
}
}And My
Activity
become like this :/*Add Click Listener*/
adapter.setOnRecyclerViewClickedListener(new Adapter.OnRecyclerViewItemClickedListener() {
@Override
public void OnRecyclerViewItemClicked(int position) {
try {
JSONObject currTable = filteredTableList.getJSONObject(position);
if (currTable.has("selected")) {
currTable.put("selected", !currTable.getBoolean("selected"));
} else {
currTable.put("selected",true);
}
adapter.notifyItemChanged(position);
} catch (JSONException e) {
e.printStackTrace();
}
try
{
Toast.makeText(TableActivity.this, filteredTableList.getJSONObject(position).getString("tischnr"), Toast.LENGTH_SHORT).show();
}
catch (JSONException e) {
e.printStackTrace();
}
}
@Override
public void OnRecyclerViewItemBind(Adapter.ViewHolder holder, int position) {
try {
JSONObject currTable = filteredTableList.getJSONObject(position);
holder.txt_no_table.setText(currTable.getString("tischnr"));
int queasy33Index = ProgramMethod.getJSONArrayIndex(queasy33List,"number2", currTable.getInt("tischnr"));
if (queasy33Index >= 0) {
holder.txt_no_table.setText(holder.txt_no_table.getText() + "-" + queasy33List.getJSONObject(queasy33Index).getString("key"));
}
if (currTable.has("selected") && currTable.getBoolean("selected")) {
holder.itemView.setBackgroundResource(R.color.colorRedTableOcc);
} else {
holder.itemView.setBackgroundResource(R.color.colorTableGreen);
}
} catch (JSONException e) {
e.printStackTrace();
}
}Now the click listener save any state and save into boolean value in my JSON file like below :
Thanks to everyone with suggest and great answer :)
How can I create Multiple selected for my recyclerview code?
This requirement you asked for need to be divided into 3 parts
Getting data from firebase and setting that list to Recycler view
I guess you have done this step successfully
Making recycler view to select multiple tiles
Follow this link for achieving this
Get all selected items in a list
Declare this globally in your adapter class
List<YourDataModel> list = new ArrayList<YourDataModel>();
Implement onClick or OnTap to recycler view tile
recyclerView.addOnItemTouchListener(new RecyclerTouchListener(getApplicationContext(), recyclerView, new RecyclerTouchListener.ClickListener() {
@Override
public void onClick(View view, int position) {
}
@Override
public void onLongClick(View view, int position) {
list.add(yourDataList.get[position]);
}}));
Now list will contain the data of all selected items
Related Topics
How to Change Default Dialog Button Text Color in Android 5
Calling Activity Class Method from Service Class
How to Create a Listview with Rounded Corners in Android
Counting Chars in Edittext Changed Listener
How to Pass Arraylist of Objects from One to Another Activity Using Intent in Android
Getting the Physical Screen Dimensions/Dpi/Pixel Density in Chrome on Android
Parse Dynamic Key JSON String Using Retrofit
How to Maintain Fragment State When Added to the Back Stack
How Set Spannable Object Font with Custom Font
How to Provide Shadow to Button
Simple Parse JSON from Url on Android and Display in Listview
How to Programmatically Open the Permission Screen for a Specific App on Android 6.0 (Marshmallow)
Startforeground Fail After Upgrade to Android 8.1
How to Write Files to Assets Folder or Raw Folder in Android
Meaning of Choreographer Messages in Logcat
How to Find the Data Usage on a Per-Application Basis on Android