0

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.

Phantômaxx
  • 37,901
  • 21
  • 84
  • 115

3 Answers3

1

You shouldn't "control" views from outside the adapter. Instead, Override onSaveState and onRestoreState in your activity. Make same methods in your adapter with passing the bundle to the adapter in order to save state. save an integer array of positions that were selected into the bundle(that you passed into an adapter). In corresponding way, you can get the array of selected positions from the bundle of On restore state.

activity:

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

in your adapter:

public void onRestoreInstanceState(Bundle state){
    selectedItemsArray = state.getIntArray("my_array_key")
}
Inkognito
  • 224
  • 1
  • 2
  • 10
  • I didn't think that with Java/Android the developer is able to perform methods for Activity lifecycle managing also from sub-classes. It seems logical, and also the fact that I shouldn't control the views from outside seems logic too. I will try this approach and let you know. Thank you. – Alessandro Iudicone Oct 19 '17 at 14:29
  • I'm still not able to restore the selection visually: I've implemented the code for saving and restoring the array of items (which contains booleans value: true if the item, false if it is not) in the Adapter class, but from the array adapter, I'm not able to highlight the corresponding views because I don't know which are. The parameter "position" (position of the item in the RecyclerView) is contained only in the "onBindViewHolder" method, which does not specify the view; instead, the view (itemView) is contained in the "onCreateViewHolder", but which doesn't contain the position parameter. – Alessandro Iudicone Oct 20 '17 at 14:01
  • I accomplished the task I needed to. The problem was twice: saving and restoring the array of items selected and retrieving the views for applying the selection, based on the position inside the RecyclerView. I'll put the complete solution in an answer. Anyway, your answer was really helpful. Thank you. – Alessandro Iudicone Oct 20 '17 at 15:51
  • Glad I could help – Inkognito Oct 22 '17 at 20:01
0

@Alessandro

You can handle the Runtime changes by yourself.

In your manifest, you can define the changes that your activity will handle by itself and it will not be restarted.

android:configChanges="orientation|keyboardHidden"

After that, you'll have to handle the Configuration changes that you declared in your manifest using this method in your activity:

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    // Checks the orientation of the screen
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        // Do your thing
    } 
}
Thiago Souto
  • 761
  • 5
  • 13
  • I know how to save and restore state via the corresponding methods, and I don't want to prevent the system to kill and restore activities. What I don't know is how to retrieve the views on which I need to perform the action of saving and restoring. Please read more carefully my question. – Alessandro Iudicone Oct 19 '17 at 14:13
  • I see, sry about that. I think that your problem is related to runtime changes (https://developer.android.com/guide/topics/resources/runtime-changes.html) . – Thiago Souto Oct 19 '17 at 14:16
  • I read both your comment and modifications to the answer but they were not completely replying my question...maybe I didn't explain my problem enough well. Anyway, I thank you for both your time and effort and put the solution in an answer. – Alessandro Iudicone Oct 20 '17 at 15:48
0

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.