androidandroid-recyclerviewstaggeredgridlayoutmanager

RecylerView with StaggeredGridLayoutManager jumping to 2 element after DiffUtil change


I have 2-column RecyclerView based on StaggeredGridLayoutManager.

When changing a content of the 1 item and triggering DiffUtil the RecyclerView is jumping to the 2 item. But when scrolling up it is possible to see 1 element and empty space, during the scroll items are transforming to a natural order. Also, the amount of items in the diff list is the same.

How to avoid this annoying behavior and to keep scrolling position during diffUtil?

val layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
    recyclerView.layoutManager = layoutManager
    recyclerView.itemAnimator = null
    recyclerView.setHasFixedSize(true)
    recyclerView.setItemViewCacheSize(if (App.isTablet()) 3 else 2)

public void diffUpdate(List<T> newList) {
    if (getCollection().size() == 0) {
      addAll(newList);
      notifyDataSetChanged();
    } else {
      DiffCallback diffCallback = new DiffCallback<T>(getCollection(), newList);
      DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback);
      clear();
      addAll(newList);
      diffResult.dispatchUpdatesTo(this);
    }
  }

Solution

  • The problem was with the implementation of DiffCallback. Earlier I used hashes:

        @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
          T oldItem = oldList.get(oldItemPosition);
          T newItem = newList.get(newItemPosition);
          boolean areTheSameInstance = oldItem == newItem;
          boolean hasTheSameType = oldItem.getClass().equals(newItem.getClass());
          boolean hasTheSameHash = oldItem.hashCode() == newItem.hashCode();
          return areTheSameInstance || hasTheSameType && hasTheSameHash;
        }
    

    This was triggering recreation of the entire view in the RecyclerView and probably measuring in the StaggeredGridLayoutManager.

    After I have changed it to id check, diffCalbback has started to triggering only rendering of the view in the RecyclerView.

    return ((Model) oldItem).id() == ((Model) newItem).id();