2

I've a strange problem with my recyclerView adapter, I just want to show/hide ImageView depending of the selection but when I call the notifyItemChanged(selection) in my click listener, it call onCreateViewHolder and take a delay to refresh de view, I don't know why and I didn't find another solution to perform what I need.

This is my adapter:

public class ChannelAdapter extends RecyclerView.Adapter<ChannelAdapter.ChannelHolder> {
    private ArrayList<Integer> channelList;
    private Integer selection = 0;

    public ChannelAdapter(ArrayList<Integer> channelList) {
        this.channelList = channelList;
    }

    @NotNull
    @Override
    public ChannelHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Log.d(TAG, "onCreateViewHolder: ");
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_channel_item_selected, parent, false);
        ChannelHolder holder = new ChannelHolder(view);
        return holder;
    }

    @SuppressLint("SetTextI18n")
    @Override
    public void onBindViewHolder(ChannelHolder holder, int position) {
        Log.d(TAG, "onBindViewHolder: "+position);
        holder.textViewChannelId.setText("#"+channelList.get(position));
        holder.textViewChannel.setText(channelList.get(position).toString());

        if(position==selection){
            holder.imageViewSelectorLeft.setVisibility(View.VISIBLE);
            holder.imageViewSelectorRight.setVisibility(View.VISIBLE);
        }
        else{
            holder.imageViewSelectorLeft.setVisibility(View.INVISIBLE);
            holder.imageViewSelectorRight.setVisibility(View.INVISIBLE);
        }
    }

    @Override
    public int getItemCount() {
        return channelList.size();
    }

    public class ChannelHolder extends RecyclerView.ViewHolder {
        TextView textViewChannel, textViewChannelId;
        ImageView imageViewSelectorLeft, imageViewSelectorRight;

        public ChannelHolder(View itemView) {
            super(itemView);
            textViewChannelId = itemView.findViewById(R.id.textViewChannelId);
            textViewChannel = itemView.findViewById(R.id.textViewChannel);
            imageViewSelectorLeft = itemView.findViewById(R.id.imageViewSelectorLeft);
            imageViewSelectorRight = itemView.findViewById(R.id.imageViewSelectorRight);

            itemView.setOnClickListener(v -> {
                notifyItemChanged(selection);
                selection=getAdapterPosition();
                notifyItemChanged(selection);
            });
        }
    }
}

Do I miss something or am I doing it by the wrong way? Thanks in advance for any help

Edit : I tried to use notifyItemChanged with a payload set to 1 and override onBindViewHolder to get the payload but it still call onCreateViewHolder, even when mSupportsChangeAnimations is set to false

mimi012
  • 36
  • 6
  • 1
    Use `notifyItemChanged(position, payload)` and override `onBindViewHolder(holder, position, payloads)`. – Pawel Oct 28 '20 at 12:12
  • Already try by setting payload to 1 and override method but it still call `onCreateViewHolder` – mimi012 Oct 29 '20 at 10:41

2 Answers2

2

By default, your RecyclerView will have a DefaultItemAnimator attached to it. When you call notifyItemChanged() on your adapter, the system will eventually call through to the DefaultItemAnimator to find out whether it needs to create a new ViewHolder or if it can "re-use" the existing one.

@Override
public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder,
        @NonNull List<Object> payloads) {
    return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads);
}

The superclass implementation:

@Override
public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) {
    return !mSupportsChangeAnimations || viewHolder.isInvalid();
}

These suggest that there are two easy ways to make sure that the ViewHolder is reused instead of recreated:

  • Make sure that the payloads list is not empty. This is done by calling adapter.notifyItemChanged(position, payload). It doesn't matter what the payload is, as long as it is non-null.
  • Set mSupportsChangeAnimations to false for your DefaultItemAnimator.
DefaultItemAnimator animator = (DefaultItemAnimator) recyclerView.getItemAnimator();
animator.setSupportsChangeAnimations(false);
Ben P.
  • 52,661
  • 6
  • 95
  • 123
  • Thanks you for this explanation, I `setSupportsChangeAnimation` to false in my recycler, and set payload to 1 when calling `notifyItemChanged` but it still call `onCreateViewHolder` when I select an item... I've try to Override onBindViewHolder method with the payloads args but it doesn't change anything... – mimi012 Oct 29 '20 at 08:54
  • Thanks a ton!!! This works!!! The nuances of recyclerview are really hard to understand,. Was struggling a lot to understand why the onCreateViewHolder was being called on notifyItemChanged(). Excellent explanation!!! – codingmonk21 Oct 11 '21 at 07:29
  • @Ben P. What If I'm using `RecyclerView.Adapter` with `ViewPager2` ? I can pass `payload` when `notifyItemChanged` , but I believe there's no 'DefaultItemAnimator '. Any idea how to stop the adapter from calling `onCreateViewHolder` ? Thanks – Sam Mar 30 '22 at 14:10
  • Also, BTW `onCreateViewHolder` is called only first time `notifyItemChanged` is called. If I call `notifyItemChanged` again for the same "position" or different one, only `onBindViewHolder` is called as desired. This is when using `ViewPager2` – Sam Mar 30 '22 at 14:13
-1

You can call your adapter like this

YourAdapter adapter = new YourAdapter(yourList, yourActivity.this);
recyclerView.setAdapter(adapter);
adapter.notifyDataSetChanged();

and then you can put this code on itemClick in adapter

if(position==getAdapterPosition()){
        holder.imageViewSelectorLeft.setVisibility(View.VISIBLE);
        holder.imageViewSelectorRight.setVisibility(View.VISIBLE);
    }
    else{
        holder.imageViewSelectorLeft.setVisibility(View.INVISIBLE);
        holder.imageViewSelectorRight.setVisibility(View.INVISIBLE);
    }
Syed Rafaqat Hussain
  • 1,009
  • 1
  • 9
  • 33
  • I don't see how what you say is suppose to help me, what I've to do with `yourActivity.this` in my adapter constructor? And what about the `onItemClick`? It's not clear... – mimi012 Oct 29 '20 at 10:34
  • yourActivity.this is the context passing to the adapter if you are passing. If you are not passing the context to the Adapter then skip this. yourActivity.this is the context passing to the adapter and on item click paste the above code hope that working – Syed Rafaqat Hussain Oct 29 '20 at 12:37
  • I know that but I don't see how it supposed to help me to avoid the call of `onCreateViewHolder` when I use `onItemChange` – mimi012 Oct 29 '20 at 17:52