Update #1
Added hasStableIds(true) and updated Picasso to version 2.5.2. It does not solve the issue.
Reproduction:
RecyclerView with GridLayoutManager (spanCount = 3). List items are CardViews with ImageView inside.
When all the items does not fit the screen calling notifyItemChanged on one item causes more than one calls to onBindViewHolder(). One call is for position from notifyItemChanged others for items not visible on the screen.
Issue:
Sometimes the item at position passed to the notifyItemChanged is loaded with an image belonging to an item that is not on the screen (most likely due to recycling of the view holder - although I would assume that if the item remains in place then the passed viewholder would be the same).
I have found Jake's comment on other issue here about calling load() even if the file/uri is null. Image is loaded on every onBindViewHolder here.
Simple sample app:
git clone https://github.com/gswierczynski/recycler-view-grid-layout-with-picasso.git
Tap on an item calls notifyItemChanged with parameter equal to the position of that item.
Code:
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new PlaceholderFragment())
.commit();
}
}
public static class PlaceholderFragment extends Fragment {
public PlaceholderFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
RecyclerView rv = (RecyclerView) rootView.findViewById(R.id.rv);
rv.setLayoutManager(new GridLayoutManager(getActivity(), 3));
rv.setItemAnimator(new DefaultItemAnimator());
rv.setAdapter(new ImageAdapter());
return rootView;
}
}
private static class ImageAdapter extends RecyclerView.Adapter<ImageViewHolder> implements ClickableViewHolder.OnClickListener {
public static final String TAG = "ImageAdapter";
List<Integer> resourceIds = Arrays.asList(
R.drawable.a0,
R.drawable.a1,
R.drawable.a2,
R.drawable.a3,
R.drawable.a4,
R.drawable.a5,
R.drawable.a6,
R.drawable.a7,
R.drawable.a8,
R.drawable.a9,
R.drawable.a10,
R.drawable.a11,
R.drawable.a12,
R.drawable.a13,
R.drawable.a14,
R.drawable.a15,
R.drawable.a16,
R.drawable.a17,
R.drawable.a18,
R.drawable.a19,
R.drawable.a20);
@Override
public ImageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item, parent, false);
return new ImageViewHolder(v, this);
}
@Override
public void onBindViewHolder(ImageViewHolder holder, int position) {
Log.d(TAG, "onBindViewHolder position: " + position + " | holder obj:" + holder.toString());
Picasso.with(holder.iv.getContext())
.load(resourceIds.get(position))
.fit()
.centerInside()
.into(holder.iv);
}
@Override
public int getItemCount() {
return resourceIds.size();
}
@Override
public void onClick(View view, int position) {
Log.d(TAG, "onClick position: " + position);
notifyItemChanged(position);
}
@Override
public boolean onLongClick(View view, int position) {
return false;
}
}
private static class ImageViewHolder extends ClickableViewHolder {
public ImageView iv;
public ImageViewHolder(View itemView, OnClickListener onClickListener) {
super(itemView, onClickListener);
iv = (ImageView) itemView.findViewById(R.id.iv);
}
}
}
public class ClickableViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
OnClickListener onClickListener;
public ClickableViewHolder(View itemView, OnClickListener onClickListener) {
super(itemView);
this.onClickListener = onClickListener;
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
}
@Override
public void onClick(View view) {
onClickListener.onClick(view, getPosition());
}
@Override
public boolean onLongClick(View view) {
return onClickListener.onLongClick(view, getPosition());
}
public static interface OnClickListener {
void onClick(View view, int position);
boolean onLongClick(View view, int position);
}
}
I spent more time than I'd like to admit to work around oddities with RecyclerView
and the new adapter that comes with it. The only thing that finally worked for me in terms of correct updates and making sure notifyDataSetChanges
and all of its other siblings didn't cause odd behavior was this:
On my adapter, I set
setHasStableIds(true);
In the constructor. I then overrode this method:
@Override
public long getItemId(int position) {
// return a unique id here
}
And made sure that all my items returned a unique id.
How you achieve this is up to you. For me, the data was supplied from my web service in the form of a UUID and I cheated by converting parts of the UUID to long using this:
SomeContent content = _data.get(position);
Long code = Math.abs(content.getContentId().getLeastSignificantBits());
Obviously this is not a very safe approach but chances are it works for my lists which will contain < 1000 items. So far I haven't run into any trouble with it.
What I recommend is to try this approach and see if it works for you. Since you have an array, getting a unique number for you should be simple. Maybe try returning the position of the actual item (and not the position that is passed in the getItemId()
) or create a unique long for each of your records and pass that in.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With