3

I've been trying to use the new RecyclerView widget for a couple of days now and thought I've managed to create a Horizontal and Grid layout and also overcome the fact that it lacks of a straightforward way of implementing a OnClickListener (I used GreenRobot's EventBus for this) , I'm still having a hard time selecting an item and preserving that state (style) throughout the recycling process.

This is the code I'm using for my adapter

public class ArticuloItemAdapter extends RecyclerView.Adapter<ArticuloItemAdapter.ItemHolder> { private ArrayList<ArticuloObject> mItems; private LayoutInflater mLayoutInflater; private int mSize; private int mSelectedPosition; private RecyclerView mRecyclerView; public ArticuloItemAdapter(Context context, RecyclerView rv, ArrayList<ArticuloObject> articulos) { mLayoutInflater = LayoutInflater.from(context); mItems=articulos ; mSize = context.getResources() .getDimensionPixelSize(R.dimen.icon); mSelectedPosition = -1; mRecyclerView = rv; } @Override public ItemHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = mLayoutInflater.inflate(R.layout.gallery_recycler_row, parent, false); return new ItemHolder(itemView, this); } @Override public void onBindViewHolder(ItemHolder holder, int position) { Ion.with(holder.getPicImageView()) .placeholder(R.drawable.owner_placeholder) .resize(mSize, mSize) .centerCrop() .error(R.drawable.owner_error) .load(mItems.get(position).getUrl()); //Update the views as they got recycled if (mSelectedPosition != position) { holder.getPicImageView().setBackgroundColor(Color.TRANSPARENT); } else { holder.getPicImageView().setBackgroundColor(Color.CYAN); } } @Override public int getItemCount() { return mItems.size(); } public int getSelectedPosition() { return mSelectedPosition; } public void setSelectedPosition(int mSelectedPosition) { this.mSelectedPosition = mSelectedPosition; } public RecyclerView getRecyclerView() { return mRecyclerView; } /* Required implementation of ViewHolder to wrap item view */ public class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener { private ArticuloItemAdapter mParent; private ImageView mPicImageView; public ItemHolder(View itemView, ArticuloItemAdapter parent) { super(itemView); itemView.setOnClickListener(this); mParent = parent; mPicImageView = (ImageView) itemView.findViewById(R.id.picture_image_view); } public ImageView getPicImageView() { return mPicImageView; } @Override public void onClick(View v) { EventBus.getDefault().post(new OnArticuloCartaClickEvent(this, getPosition())); setItemActivated(v); } public void setItemActivated(View v) { //Check position of previous selected item. The first time an item is selected, the previous position will be -1 if (mParent.getSelectedPosition() >= 0) { ItemHolder row = (ItemHolder) mParent.getRecyclerView().findViewHolderForPosition(mParent.getSelectedPosition()); if (row != null) { row.getPicImageView().setBackgroundColor(Color.TRANSPARENT); } } mParent.setSelectedPosition(getPosition()); v.findViewById(R.id.picture_image_view).setBackgroundColor(Color.CYAN); } } } 

Now in the code shown above, all I'm doing to keep track of the selected item is the following:

I. In the onClick(View v) event:

  1. Whenever an item is clicked, I call the setItemActivated method
  2. In the setItemActivated method, I check if the selectedPosition is greater than or equal to 0 ( the default is -1 when no item is selected).
  3. If the previous condition is true I try to get the ViewHolder in that position and if that View is not null, I change the background colour of the ImageView back to it's original background colour (transparent)
  4. Set the new value of mSelectedPosition to the current position using getPosition()
  5. Finally, change the background colour of the ImageView inside the current ViewHolder to a different colour (CYAN in this case)

II. In the onBindViewHolder(ItemHolder holder, int position) method:

If position of selected item (mSelectedPosition) is different from the position of the view being displayed, I change the background colour of the ImageView back to its default colour. Otherwise, I change the colour of the ImageView to the colour used to identify a selected item (CYAN).

All this works as long as the RecyclerView has a considerable number of item that it needs to start recycling the views. But if it doesn't, like in my case, I've noticed that findViewHolderForPosition always returns null, and as a result, I cannot change the colour of the previously selected item back to its default colour and so I ended up with more than one item selected.

enter image description here

And to make matters worse, as the RecyclerView doesn't have to recycle anything, the onBindViewHolder method never gets call and therefore I can't count on the code inside to adjust the colour of the items accordingly.

Please, I'd really appreciate if anyone could tell me what I'm doing wrong here or perhaps give some ideas. It's really frustrating not being able to do something as basic as selecting an item.

Thanks in advance.

1 Answer 1

4

This is happening because when selected item changes, you are not telling to RecyclerView that the previous item is invalidated.

Instead of trying to set it manually, do the following:

public void setItemActivated(View v) { final int myPos = getAdapterPosition(); if (myPos == RecyclerView.NO_POSITION) { // very rare case where item is clicked but its adapter position // is unknown. ignore the event. // See docs of getAdapterPosition for details } final int prevSelected = mParent.getSelectedPosition(); mParent.setSelectedPosition(myPos); if (prevSelected >= 0 && prevSelected < mParent.getItemCount()) { mParent.notifyItemChanged(prevSelected); } mParent.notifyItemChanged(myPos); } 

RecyclerView will call onBind for Views that should be updated. You don't need to care about where previous selected item is.

Sign up to request clarification or add additional context in comments.

6 Comments

Thank you @yigit. Could you tell where I can find this getAdapterPosition method? I've tried to use it everywhere in my adapter or within the ViewHolder , but the method is not recognized? From the docs it looks like it is something like getPosition(), but I don't understand why I can't use it.
I guess you don't have the latest version of the support library. developer.android.com/reference/android/support/v7/widget/…
But I have used compile 'com.android.support:appcompat-v7:21.0.3' and compile 'com.android.support:recyclerview-v7:21.0.3' . I even used the "+" wildcard to get the latest version, but I still can't use getAdapterPosition
Sorry about that. Effectively, it turned out to be that there was a revision 22 I was not aware of. After I installed it I was able to use getAdapterPosition. One last question, when you compare this if (myPos == RecyclerView.NO_POSITION), you said that I should ignore the event. If the event is the onClick event, shouldn't that validation be done before I call setItemActivated, passing in the value of getAdapterPosition ?
Sure, I don't have the rest of your code. Again, this is a very edge case. Actually, it will only happen if you call notifyDataSetChanged or setAdapter / swapAdapter and the new layout is not calculated yet.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.