73

I'm trying to implement a RecyclerView with multiple selectable items.

Here is what I've tried but I couldn't make it work.

This is what I'm trying to do:

This is implemented using ListView

Brugui
  • 574
  • 6
  • 20
Hegdekar
  • 1,147
  • 1
  • 13
  • 16
  • 8
    Find your answer here. http://enoent.fr/blog/2015/01/18/recyclerview-basics/ – D Agrawal Apr 02 '16 at 06:27
  • Im trying it for fragments. And my Activity extends `AppCompatActivity`. I'm stuck with `actionMode = getActivity().startActionMode(actionModeCallback);` mentioned in the above given link. – Hegdekar Apr 02 '16 at 10:35
  • Replace it with getActivity().startActionMode(yourFragmentName.this); – D Agrawal Apr 02 '16 at 10:52
  • I got it. Searched a bit and found it http://stackoverflow.com/questions/18204386/contextual-action-mode-in-fragment-close-if-not-focused – Hegdekar Apr 02 '16 at 11:20
  • great, upvote if u think comments were helpful. Happy coding. – D Agrawal Apr 02 '16 at 11:23
  • even after initialising the `actionMode = getActivity().startActionMode(actionModeCallback);` still stuck with NullPointerException – Hegdekar Apr 02 '16 at 11:29
  • Did you or not find a solution? did you use this getActivity().startActionMode(yourFragmentName.this); , if u did , still getting nullPointerException ? – D Agrawal Apr 02 '16 at 11:31
  • Also did u get answer from the link you posted stackoverflow.com/questions/18204386/… – D Agrawal Apr 02 '16 at 11:32
  • if you have seen the code in the link you mentioned earlier there is a method `toggleSelection(position)` which uses this `actionMode`. That method has `actionMethod.finish()` thats throwing a NPE. – Hegdekar Apr 02 '16 at 11:35
  • 2
    For Kotlin users, the following article might be useful [How to delete multiple records from Firestore using RecyclerView multi-selection?](https://medium.com/firebase-tips-tricks/how-to-delete-multiple-records-from-firestore-using-recyclerview-multi-selection-96108e4c6166). – Alex Mamo Jan 04 '21 at 17:10

5 Answers5

144

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.

matdev
  • 4,115
  • 6
  • 35
  • 56
Shashanth
  • 4,995
  • 7
  • 41
  • 51
  • Can this be somehow achieved without modifying the model? I would like for the adapter to handle the selection because changing the model is not an option for me... – Chapz Dec 22 '16 at 12:40
  • Sorry, for the late reply. I didn't check that feature. You can maintain another `List` inside Adapter but I don't know whether it's feasible or not?. And what you're facing difficulty in changing structure of current model class? – Shashanth Dec 23 '16 at 05:59
  • Im working on a business app with predefined models. I am not allowed to change model just to make it easier for me to implement something. The model must stay separated from the logic – Chapz Dec 23 '16 at 12:21
  • @Shashanth how can i pass the selected value to other activity using this example – M.Yogeshwaran Mar 28 '17 at 06:45
  • Use Intent to pass the value from one Activity to another. – Shashanth Mar 28 '17 at 08:30
  • how shall i implement the same code for single select recyclerview ? i mean how set all other models to false (isSelected) except the one which is clicked ?? can you share that onBind code ?? @Shashanth – Santanu Sur Feb 11 '18 at 05:21
  • @SantanuSur check my edit. I didn't check whether this code work or not. Let me know if you have any problem. – Shashanth Feb 11 '18 at 16:13
  • Thanks for the code but how can i do this for recycler adapter that is created for firestore – CodeRED Innovations Apr 30 '20 at 19:57
  • How can we use the above code to select <=5 items? – Abm Sep 12 '22 at 12:02
10

Creation of those mentioned custom multi select implementation works correct but might have performance issues when the dataset is huge. I would highly recommend reading the Advanced RecyclerView customization documentation by google.

Jeffrey
  • 1,998
  • 1
  • 25
  • 22
Farruh Habibullaev
  • 2,342
  • 1
  • 26
  • 33
  • 4
    This question probably predates the recyclerview-selection package, but for modern day application, using this package is how it should be done, and this answer should be the accepted answer as far as I'm concerned. – Breeno Sep 20 '18 at 18:03
  • 4
    There's unfortunately very little good documentation about how to use it. Though this is a rather good example http://androidkt.com/recyclerview-selection-28-0-0/ – Maverick Meerkat Nov 26 '18 at 12:37
  • 1
    David, those requirements are quite franky overcomplicated, google really needs to do something about that. Surely its far easier to roll your own observer. No wonder Google did not document its use properly, they must be ashamed or working on something else. – Andrew S Mar 03 '20 at 21:54
6
public class RecyclerColorAdapter extends RecyclerView.Adapter<RecyclerColorAdapter.ViewHolder> {


        private final Activity activity;
        private final ArrayList<ColorItem> itemArrayList;


        public RecyclerColorAdapter(Activity activity, ArrayList<ColorItem> itemArrayList) {
            super();
            this.activity = activity;
            this.itemArrayList = itemArrayList;
        }


        @Override
        public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
            View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_color_recycleview, viewGroup, false);
            return new ViewHolder(v);
        }

        @Override
        public void onBindViewHolder(final ViewHolder holder, final int i) {
            holder.setIsRecyclable(true);

            final ColorItem colorItem = itemArrayList.get(i);


            holder.button_color.setText(colorItem.getColorName());

            holder.button_color.setBackgroundColor(colorItem.isSelected() ? Color.CYAN : Color.WHITE);

            holder.button_color.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    colorItem.setSelected(!colorItem.isSelected());
                    holder.button_color.setBackgroundColor(colorItem.isSelected() ? Color.CYAN : Color.WHITE);
                    if (colorItem.isSelected()){
                        arrayListColor.add("diamond_color[]="+colorItem.getValue()+"&");
                        Log.e("arrayListColor","---------"+arrayListColor);
                    }
                    else {
                        arrayListColor.remove("diamond_color[]="+colorItem.getValue()+"&");
                        Log.e("arrayListColor","---------"+arrayListColor);
                    }


                }
            });

        }

        @Override
        public int getItemCount() {

            return itemArrayList.size();
        }

        public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

            private Button button_color;

            public ViewHolder(View itemView) {
                super(itemView);
                itemView.setOnClickListener(this);
                button_color = (Button) itemView.findViewById(R.id.button_color);

            }

            @Override
            public void onClick(View v) {

            }

        }

    }
Kohei TAMURA
  • 4,970
  • 7
  • 25
  • 49
Pradip
  • 57
  • 1
  • 3
  • From my understaning, `onBindViewHolder` is called multiple times, so, even though you don't use your `i` (the position) in the `OnClickListener` (which would be a big "no-no" afaik), it would be best to move it into `onCreateViewHolder` (get the position with `holder.getAdapterPosition()` then). This way the listener would only be added once (when it's created), not every time it's re-used. ;) I'm also not sure why there's an extra `onClick` in the nested `ViewHolder` class. – Neph Mar 03 '20 at 13:27
5

Without creating model class we can multi select item in recyclerview. use this code in recyclerview adapter

holder.favplayIcon.setOnClickListener(View.OnClickListener {
                    if (!row_index.contains(position)) {
                        row_index.add(position)
                        holder.favplayIcon.setImageDrawable(
                            ResourcesCompat.getDrawable(resources, R.drawable.ic_starfilled, null ))

                    } else {
                        row_index.removeAt(row_index.indexOf(position))
                        holder.favplayIcon.setImageDrawable(
                   ResourcesCompat.getDrawable(resources,R.drawable.ic_starborder, null)
                        )  }
                })

///// put below code out of onclicklistener method of item
                if (!row_index.contains(position)) {

                    holder.favplayIcon.setImageDrawable(
                        ResourcesCompat.getDrawable(
                            resources,
                            R.drawable.ic_starborder,
                            null
                        )
                    )
} else {
                    holder.favplayIcon.setImageDrawable(
                        ResourcesCompat.getDrawable(
                            resources,
                            R.drawable.ic_starfilled,
                            null
                        )
                    )
civani mahida
  • 314
  • 3
  • 8
1

Simple and short way to do it:

  holder.parentLayout.setOnClickListener {

        if (holder.categoryIcon.isSelected) {
            selectedPos = position
        }

        if (selectedPos == position) {
            notifyItemChanged(selectedPos)
            selectedPos = RecyclerView.NO_POSITION
        } else {
            selectedPos = position
            notifyItemChanged(selectedPos)
        }
    }


    if (selectedPos == position) {
        holder.categoryIcon.setBackgroundColor(ContextCompat.getColor(context, R.color.orange))
        holder.categoryIcon.isSelected = true
    } else {
        holder.categoryIcon.setBackgroundColor(ContextCompat.getColor(context, R.color.white))
        holder.categoryIcon.isSelected = false
    }
Aleesha Kanwal
  • 187
  • 2
  • 9