Search code examples
androidandroid-recyclerviewandroid-viewholder

Why is DefaultItemAnimator forcing changed ViewHolders to recreate?


Hey so basically I run into a issue with RecyclerView lately (The issue I had is described here: RecyclerView recreating ViewHolder instead of rebinding). What I discovered was that the ItemAnimator inside RecyclerView decides if a changed ViewHolder just has to be rebound or a new ViewHolder should be created and crossfaded.

This article explains some of the things: https://medium.com/android-news/anatomy-of-recyclerview-part-1-a-search-for-a-viewholder-continued-d81c631a2b91

I assume this is the part of the DefaultItemAnimator that decides what should happen:

     /**
     * {@inheritDoc}
     * <p>
     * If the payload list is not empty, DefaultItemAnimator returns <code>true</code>.
     * When this is the case:
     * <ul>
     * <li>If you override {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)}, both
     * ViewHolder arguments will be the same instance.
     * </li>
     * <li>
     * If you are not overriding {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)},
     * then DefaultItemAnimator will call {@link #animateMove(RecyclerView.ViewHolder, int, int, int, int)} and
     * run a move animation instead.
     * </li>
     * </ul>
     */
    @Override
    public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder,
            @NonNull List<Object> payloads) {
        return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads);
    }

Now the solution to my problem was to just call onItemRangeChanged() with some arbitrary payload and it would always reuse the ViewHolder.

My question: Now I was wondering why and when it would be better to recreate the whole ViewHolder and crossfade it than just updating a specific part of it? In the crossreferenced problem mentioned above I was just changing the visibility of a small part of the ViewHolder but the whole things was recreated, which - I assume - is way worse performance wise?


Solution

  • Remember RecyclerView can have multiple item view types. When notifyItemChanged causes view type to change then viewholders are ineligible for reuse regardless of payload.

    By replacing viewholders animator maintains unified behavior in this case.

    Also by updating the ViewHolder they will not undergo adapters onViewAttachedToWindow(holder) which people might expect while new items are being laid out and onViewRecycled(holder) & onViewDetachedFromWindow(holder) which are called for items that are being removed.

    As a side note if you're experiencing onCreateViewHolder calls during your subsequent range updates (without payload) you should probably increase size of your RecyclerView.RecycledViewPool.