How to Implement Multi-Select in Recyclerview

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

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 :

  1. 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());
    }
    }
    }
  2. 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();
    }
    }
  3. Now the click listener save any state and save into boolean value in my JSON file like below :

Multple Selecion

The click state saved into boolean value in JSON

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

  1. Getting data from firebase and setting that list to Recycler view

    I guess you have done this step successfully

  2. Making recycler view to select multiple tiles

    Follow this link for achieving this

  3. 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



Leave a reply



Submit