RecyclerView ambiguos setVisibility function, clicking on one view affects multiple views

This happens because the views get recycled and reused.

So when the view gets recycled, it retains properties of the "old" view if you don't change them again. So when you scroll down to number 12, the view that used to hold number 1 gets recycled (as it can't be seen on the screen anymore), and is used to create number 12. This is why the blue color is on number 12.

When the item is, for example, clicked, you'll need to save a "clicked" value into your POJO object. Then when the item is drawn, check that value and set the correct image / background color depending on that value.

I've done this in the below code, so it should give you a rough idea of what to do:

@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
    TextView title = (TextView) holder.view.findViewById(R.id.title);
    final TextView desc = (TextView) holder.view.findViewById(R.id.desc);
    final ImageView imageView = (ImageView) holder.view.findViewById(R.id.imageView);

    final MyPojo pojo = pojos.get(position);

    title.setText(pojo.getTitle());
    if(!pojo.clicked) {
        desc.setText(pojo.getDesc());
        imageView.setImageResource(pojo.getImage());
        desc.setBackgroundColor(Color.argb(0,0,0,0));
    } else {
        desc.setText("clicked");
        desc.setBackgroundColor(Color.BLUE);
        imageView.setImageResource(R.drawable.heart_red);
    }

    imageView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            pojo.clicked = true;
            desc.setText("clicked");
            desc.setBackgroundColor(Color.BLUE);
            imageView.setImageResource(R.drawable.heart_red);
        }
    });
}

And i've added a "clicked" boolean to the MyPojo class.

public class MyPojo {

    String title;
    String desc;
    int image;
    boolean clicked;
 }

Just add a method in your adapter class after the getItemCount method

@Override
    public int getItemViewType(int position) {
        return position;
    }

it will solve the problem


It appears that you have a confusion on using RecyclerView by calling to findViewById in onBindViewHolder. These expensive lookups should be happening in onCreateViewHolder, where you lookup all the views and save their references to your custom view holder. I went ahead at looked at your code in github repo and propose the following changes:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {

private ArrayList<MyPojo> pojos;

// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
public static class ViewHolder extends RecyclerView.ViewHolder {
    // each data item is just a string in this case
    public TextView title;
    public TextView desc;
    public ImageView imageView;

    public ViewHolder(View v) {
        super(v);

        // all expensive findViewById lookups happen in ViewHolder constructor,
        // which is called only when onCreateViewHolder is called
        this.title = (TextView) v.findViewById(R.id.title);
        this.desc = (TextView) v.findViewById(R.id.desc);
        this.imageView = (ImageView) v.findViewById(R.id.imageView);
    }
}

// Provide a suitable constructor (depends on the kind of dataset)
public MyAdapter(ArrayList<MyPojo> pojos) {
    this.pojos = pojos;
}

// Create new views (invoked by the layout manager)
@Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                               int viewType) {
    // create a new view
    View v = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.row, parent, false);
    // set the view's size, margins, paddings and layout parameters
    ViewHolder vh = new ViewHolder(v);
    return vh;
}

// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
    // this callback will be constantly called during scrolling
    // therefore, to make it smooth, we should not make any expensive operations here
    // - get element from your dataset at this position
    // - replace the contents of the view with that element
    holder.title.setText(pojos.get(position).getTitle());
    holder.desc.setText(pojos.get(position).getDesc());
    holder.imageView.setImageResource(pojos.get(position).getImage());

    // you'll need to implement this function based on the way you decide to save clicked state for each clicked view
    if(isClickedState(position)) {
          holder.imageView.setImageResource(R.drawable.heart_red);
    } else {
          // provide some default background
          holder.imageView.setImageResource(R.drawable.default);
    }

    holder.imageView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            // you'll need to implement this function to save clicked position
            saveClickForPosition(position)
            imageView.setImageResource(R.drawable.heart_red);
        }
    });
}

// Return the size of your dataset (invoked by the layout manager)
@Override
public int getItemCount() {
    return pojos.size();
}
}

This should be the starting point for your debugging as following this pattern will guarantee proper recycling.

As it is also mentioned in another answer, you do need to remember the clicked state for each item, and keeping this state in MyPojo object or somewhere else should be relatively easy to accomplish.