2

I want to select multiple items in recycler view and when it is selected I want to set visibility as visible of a checkbox of that item. So, long I am able to set onlongClickListner using interface and handling onLongClick event in a fragment.

Whenever user performes long click on any item, app's onCLick logic is changed. Before long click app is opening the item in another activity but, after long click onClick's logic is changed and can be set whatever I want. I want to check the checkbox corresponding to the item after long click. And want to add that from an arrayList which is loaded in recycler view.

Fragment

...
@Override
    public void onclick(int position) {
        if (!isSelectionMode) {
            Intent intent = new Intent(getActivity(), FullPhoto.class);
            intent.putExtra("uri", arrayList.get(position).getUri());
            startActivity(intent);
        }
    }

            //Support fun to turn selectionMode on, onLongClick event.

    @Override
    public void onLongClick() {
        isSelectionMode = true;
    }
...

Adapter

...

public static class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener,View.OnLongClickListener {


        private final ImageView img;
        public CheckBox selection;
        OnImageClickListner listner;
        OnImageLongClickListener longClickListener;
        public MyViewHolder(@NonNull View itemView, OnImageClickListner listner,OnImageLongClickListener longClickListener) {
            super(itemView);
            this.listner = listner;
            this.longClickListener = longClickListener;
            itemView.setOnLongClickListener(this);          //onLongClickListener is set to all of the RecyclerView Items for once rather than setting on each item in BindViewHolder for repeated times
            itemView.setOnClickListener(this);          //onClickListener is set to all of the RecyclerView Items for once rather than setting on each item in BindViewHolder for repeated times
            img = itemView.findViewById(R.id.img);
            selection = itemView.findViewById(R.id.checkbox);

        }

        @Override
        public void onClick(View v) {
            listner.onclick(getAdapterPosition());            //Returning the current clicked position
        }

        @Override
        public boolean onLongClick(View v) {
            longClickListener.onLongClick();          
            return  true;
        }
    }
// inner class ends


    public  interface  OnImageClickListner{         //Interface to generate call back when user clicked an image.
         void onclick(int position);
    }

    public interface OnImageLongClickListener{          //Interface to generate call back when user long clicked an image.
        void onLongClick();
    }

...

I this scenario, i am unable to understand how to implement a selection tracker. I can get the adapter position with getAdapterPosition() and then I can remove the element from arrayList on that index. But, I want to highlight the checkbox at that position. In this case i am unable to implement the code.

Things I tried

I did try to pass the View v from onLongClick(View v) and then passing the selection checkbox to onCLick() event. But, It didn't work.

I want to select the items from recycler view and set visibility as VISIBLE for the selected Items.

------ Update ------

I am now able to set visibility of the checkbox as visible with the help of few edits in event methods.

Fragment

@Override
    public void onclick(int position, CheckBox selection) {
        if (!isSelectionMode) {
            Intent intent = new Intent(getActivity(), FullPhoto.class);
            intent.putExtra("uri", arrayList.get(position).getUri());
            startActivity(intent);
        }
        else
        {
            selection.setVisibility(View.VISIBLE);
            selection.setChecked(true);
        }
    }

Where selection is a checkbox passed from MyViewHolder class from adapter. But, because nature of the recycler view i am getting double selections. and one weird issues that after selecting items if I scroll down, selections will be changed randomly.

Issue

In this image as you can see, I only selected 4 images but, when I scrolled down, other images were selected as well and when I scrolled up again it messed my selected items.

PhotosAdapter

public class PhotosAdapter extends RecyclerView.Adapter<PhotosAdapter.MyViewHolder> {


    public Context context;
    ArrayList<ImageModel> arrayList;
    Activity activity;
    OnImageClickListner listener;
    OnImageLongClickListener longClickListener;

    /*===============================================================   CONSTRUCTOR   ===============================================================*/

    public PhotosAdapter(Context context, ArrayList<ImageModel> arrayList, Activity activity, OnImageClickListner listner, OnImageLongClickListener longClickListener) {
        this.context = context;
        this.arrayList = arrayList;
        this.activity = activity;
        this.listener = listner;
        this.longClickListener = longClickListener;

    }

    /*===============================================================   OVERRIDDEN METHODS   ===============================================================*/

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {           //This methods returns single_view.xml as a view for RecyclerView.
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.single_view, parent, false);
        return new MyViewHolder(view, listener, longClickListener);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {          //Binding the uris with each view depending upon the position of current view.


        activity.runOnUiThread(() -> GlideApp.with(context)
                .load(Uri.parse(arrayList.get(position).getUri()))
                .apply(RequestOptions.overrideOf(150, 150))          //It overrides the value of original image and reduces it to the visible thumbnail size.
                .diskCacheStrategy(DiskCacheStrategy.RESOURCE)          //Then it caches the reduced size thumbnail for faster loading speed.
                .into(holder.img));
    }

    @Override
    public int getItemCount() {
        return arrayList.size();
    }


    /*===============================================================   INNER VIEW HOLDER CLASS   ===============================================================*/

    public static class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {


        private final ImageView img;
        public CheckBox selection;
        OnImageClickListner listener;
        OnImageLongClickListener longClickListener;

        public final SparseBooleanArray selectedItems;              ///////////////////////////////// ADDED LINE /////////////////////////////////

        public MyViewHolder(@NonNull View itemView, OnImageClickListner listener, OnImageLongClickListener longClickListener) {
            super(itemView);
            this.listener = listener;
            this.longClickListener = longClickListener;
            itemView.setOnLongClickListener(this);          //onLongClickListener is set to all of the RecyclerView Items for once rather than setting on each item in BindViewHolder for repeated times
            itemView.setOnClickListener(this);          //onClickListener is set to all of the RecyclerView Items for once rather than setting on each item in BindViewHolder for repeated times
            img = itemView.findViewById(R.id.img);
            selection = itemView.findViewById(R.id.checkbox);

            selectedItems = new SparseBooleanArray();             ///////////////////////////////// ADDED LINE /////////////////////////////////
        }



        @Override
        public void onClick(View v) {

            listener.onclick(getAdapterPosition(), selection);            //Returning the current clicked position and selection checkbox to the implemented method.
        }

        @Override
        public boolean onLongClick(View v) {
            longClickListener.onLongClick(getAdapterPosition(), v);          //Returning the current clicked position and view to the implemented method.
            return true;
        }



        //////////////////////////////////////////////////////////////////////////// ADDED LINES ////////////////////////////////////////////////////////////////////////////



        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;
        }





    }       //INNER CLASS ENDS

    /*===============================================================   INTERFACES   ===============================================================*/

    public interface OnImageClickListner {         //Interface to generate call back when user clicked an image.
        void onclick(int position, CheckBox selection);
    }

    public interface OnImageLongClickListener {          //Interface to generate call back when user long clicked an image.
        void onLongClick(int position, View v);
    }


}
  • 1
    https://code.tutsplus.com/tutorials/how-to-add-selection-support-to-a-recyclerview--cms-32175 give it a shot – QAMAR Jul 15 '21 at 08:37
  • https://stackoverflow.com/questions/36369913/how-to-implement-multi-select-in-recyclerview – QAMAR Jul 15 '21 at 08:43
  • Did you want multiple selection of the items? – Ananta Raha Jul 15 '21 at 12:46
  • @AnantaRaha Yes, but, with the interface i have implemented. I have seen many example in which devlopers are setting `onClickListener` and `onLongClickListner` in `onBindViewHolder` but, I found that this can cause issues if the user has large number of photos. So, I successfully implemented `onClickListener` and `onLongClickListner` but, cannot set visibility of the checkbox corresponding of item which is selected. – Anirudhdhsinh Jadeja Jul 15 '21 at 12:54

1 Answers1

2

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.

Bro Nicholas
  • 224
  • 3
  • 14
  • ok. This makes sense... but, i still don't understand how am i going to control `isItemSelected` from fragment... i can create a method like to set `true` or `false` and then i wil call it in fragment... but, how to check a particular photo at certain index? this part i am unable to understand... if you can then please explain it bit further with some code... – Anirudhdhsinh Jadeja Jul 16 '21 at 05:33
  • Please go through `PhotosAdapter` I updated in my question. As `Adapter` class is already extending inner class i cannot make it extend other class. So, I placed the code you mentioned inside inner class. But, Inner class is static and android studio insists that it should be static if I made it non static, so the methods `getItemCount()` and `notifyDataSetChanged()` methods are showing an error. – Anirudhdhsinh Jadeja Jul 17 '21 at 02:55
  • If you look at my code carefully you will see that you actually can work with it, Maybe I did not mention how you are supposed to extend the selectable adapter on the main adapter: This is how `public class PhotosAdapter extends SelectableAdapter` . Modify it that way, because what you are currently extending is extended in the selectable adapter. – Bro Nicholas Jul 17 '21 at 11:09
  • Your last comment was removed or something. – Bro Nicholas Jul 19 '21 at 15:21
  • I just asked if you use any social media or not.. Then i thought it might not feel good. so i deleted. – Anirudhdhsinh Jadeja Jul 19 '21 at 17:34