Search code examples
androidandroid-viewandroid-recyclerviewphoto-gallery

Control RecyclerView inflated views from outside the adapter: keep selection state


Generally

I want to control the ViewHolder inflated Views of my RecyclerView from outside of the ViewHolder and the RecyclerView classes. In other words, I want to have control of these views from other methods/classes.

My case (en example)

In my specific case, I made a photo gallery activity which allows the user to perform selection and deselection of each inflated view, notifying which items are selected by highlighting them.

selection

For now, the user is able to do that by clicking each generated object / View; then, actions on specific child of RecyclerView / adapter are possible thanks to "setOnClickListener" and "setOnLongClickListener" methods, which perform the corresponding actions in methods inside the ViewHolder class.

But when activity is restarted (i.e. for device rotation) the selection goes lost and the user should perform the selection again (i.e. for deleting photos).

selection lost

Assuming that positions of the selected photos are kept (for example via bundle, or via an array) is possible to restore selection (i.e. highlighting the corresponding item / views) on the adapter views after that the activity is re-started? If yes, how?

Some code

The code below contains the Recyclerview class and the AdapterView class, which both are child of an activity Class.

private class ImageGalleryAdapter extends RecyclerView.Adapter<ImageGalleryAdapter.MyViewHolder>  {

    private ArrayList<PhotoObject.PhotoElement> photoAL;
    private Context mContext;
    public ImageGalleryAdapter(Context context, ArrayList<PhotoObject.PhotoElement> photosToPreviewInGallery) {
        mContext = context;
        photoAL = photosToPreviewInGallery;
    }

    @Override
    public ImageGalleryAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        Context context = parent.getContext();
        LayoutInflater inflater = LayoutInflater.from(context);

        // Inflate the layout
        View itemView = inflater.inflate(R.layout.item_photo, parent, false);

        ImageGalleryAdapter.MyViewHolder viewHolder = new ImageGalleryAdapter.MyViewHolder(itemView);
        // Retrieving the itemView
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(ImageGalleryAdapter.MyViewHolder holder, int position) {

        PhotoObject.PhotoElement previewPhotoInGallery = photoAL.get(position);
        ImageView imageView = holder.mPhotoImageView;

        GlideApp.with(mContext)
                .load(previewPhotoInGallery.getUrl())
                .placeholder(R.drawable.ic_cloud_off_red)
                .into(imageView);
    }

    //The method which gives back the number of items to load as photo.
    @Override
    public int getItemCount() {
        return (photoAL.size());
    }

    // The class that assigns a view holder for each Image and checkbox in the RecyclerView.
    public class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {

        public ImageView mPhotoImageView;
        public CheckBox mPhotoCheckBox;
        public MyViewHolder(View item_view) {

            super(item_view);
            mPhotoImageView = (ImageView) item_view.findViewById(R.id.item_photo_iv);
            mPhotoCheckBox = (CheckBox) item_view.findViewById(R.id.item_photo_checkbox);

            item_view.setOnClickListener(this);
            item_view.setOnLongClickListener(this);

            // Retrieving the item_view
        }

        // The method for managing the click on an image.
        @Override
        public void onClick(View view) {
            itemSelection(view);
        }

        // Manages the selection of the items.
        private void itemSelection(View item) {
            // Retrieving the item

            int position = getAdapterPosition();

            if (position != RecyclerView.NO_POSITION) {
                if (!item.isSelected()) {
                    // Add clicked item to the selected ones
                    MultiPhotoShootingActivity.manageSelection(true, position);

                    // Visually highlighting the ImageView
                    item.setSelected(true);
                    mPhotoCheckBox.setChecked(true);
                    mPhotoCheckBox.setVisibility(View.VISIBLE);
                } else {
                    // Remove clicked item from the selected ones
                    MultiPhotoShootingActivity.manageSelection(false, position);

                    // Removing the visual highlights on the ImageView
                    item.setSelected(false);
                    mPhotoCheckBox.setChecked(false);
                    mPhotoCheckBox.setVisibility(View.INVISIBLE);
                }
            }
        }

        // The method for managing the long click on an image.
        @Override
        public boolean onLongClick(View view) {

            int position = getAdapterPosition();
            if(position != RecyclerView.NO_POSITION) {
                Intent intent = new Intent(mContext, PhotoDetail.class);
                intent.putExtra("KEY4URL", activityPhotoObject.getPath(position));
                startActivity(intent);
            }

            // return true to indicate that the click was handled (if you return false onClick will be triggered too)
            return true;
        }
    }

}

Thank you for your time.


Solution

  • SOLVED

    Find out that for solving the problem I had to accomplish two little tasks:

    • saving and restoring the selected item selection state (for example via an array, as helpfully suggested by @Inkognito);
    • retrieving the views for applying the selection, based on the position inside the RecyclerView.

    So, I had to modify some code.
    Before proceeding, I would like to point out that the Activity class has a sub-class, which is the Adapter class (named ImageGalleryAdapter); the Adapter subclass, in turn, has its own subclass, which is the ViewHolder class (named MyViewHolder).
    So: Activity class -> Adapter class -> ViewHolder class

    Code modified in the parent class (the activity class, in which the RecyclerView is)

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        adapter.onSaveInstanceState(outState);
    }
    
    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        adapter.onRestoreInstanceState(savedInstanceState);
    }
    

    In the onSaveInstanceState and onRestoreInstanceState methods, I added the references for saving and restoring instance states of the "adapter" sub-class.

    Code added in the adapter class (which is inside the RecyclerView class)

        private boolean [] selectedItemsArray;
        private void onSaveInstanceState(Bundle outState) {
            outState.putBooleanArray("my_array_key" , selectedItemsArray = mpsaPO.getItemsSelected());
        }
    
        private void onRestoreInstanceState(Bundle state) {
            if (state != null) {
                selectedItemsArray = state.getBooleanArray("my_array_key");
            }
        }
    

    The selectedItemsArray is a boolean array in which the information of which elements of the RecyclerView are selected (true = selected; false = not selected) is contained.
    Then, adding this element in the saved instance and retrieved via the activity class, makes the app able to know which are the views selected after that the activity is re-created.

    Code added inside the onBindViewHolder method, which is inside the adapter class

            if (selectedItemsArray != null) {
                if (selectedItemsArray[position]) {
                    holder.itemView.setSelected(true);
                    holder.mPhotoCheckBox.setChecked(true);
                    holder.mPhotoCheckBox.setVisibility(View.VISIBLE);
                }
            }
    

    With this last part of code, we are applying the selection to the corresponding views based on which items/views were selected before that the activity was saved. The holer object contains the itemView and mPhotoCheckBox objectsm on which we can perform the selection.