RecyclerView.ItemDecoration doesn't update after item is removed from RecyclerView.Adapter

后端 未结 3 532
小鲜肉
小鲜肉 2020-12-10 03:28

My question

How can I get my ItemDecoration to \"update\" the item offsets for other views when I remove a given view from my adapter and the

相关标签:
3条回答
  • 2020-12-10 03:42

    I solved this by changing my removeItemAt() method as follows:

    private void removeItemAt(int position) {
        if (list.size() > 0) {
            list.remove(position);
            notifyItemRemoved(position);
    
            if (position != 0) {
                notifyItemChanged(position - 1, Boolean.FALSE);
            }
        }
    }
    

    The second argument to notifyItemChanged() is the key. It can be literally any object, but I chose Boolean.FALSE because it's a "well-known" object and because it conveys a little bit of intent: I don't want animations to run on the item I'm changing.

    Why it works

    It turns out that there's a second onBindViewHolder() method defined in RecyclerView.Adapter that I'd never seen before. From the source code:

    public void onBindViewHolder(VH holder, int position, List<Object> payloads) {
        onBindViewHolder(holder, position);
    }
    

    Excerpted from that method's documentation:

    The payloads parameter is a merge list from notifyItemChanged(int, Object) or notifyItemRangeChanged(int, int, Object). If the payloads list is not empty, the ViewHolder is currently bound to old data and Adapter may run an efficient partial update using the payload info. If the payload is empty, Adapter must run a full bind.

    In other words, by passing something (anything) as a payload in notifyItemChanged(), we're telling the system that we want to perform only a "partial update" (in situations where that's possible).

    So, sure, we've instructed the system to perform a partial update... but how does that stop the flickering caused by simply calling notifyItemChanged(position - 1) ? It has to do with the RecyclerView.ItemAnimator that's attached to all RecyclerViews by default: DefaultItemAnimator.

    DefaultItemAnimator's source code includes this method:

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

    You'll notice that this method also takes a List<Object> payloads parameter. This is the same list of payloads as used in the other onBindViewHolder() call mentioned above.

    Because we passed a payload argument, this method will return true. And since the animator is now told that it can reuse the already-existing ViewHolder for our "changed" item, it doesn't tear it down and create a new one (or reuse a recycled one)... which stops the default fade animation on the changed item from running!

    0 讨论(0)
  • 2020-12-10 04:00

    This is a too late answer, but I will answer because I have the same problem. Try call RecyclerView.invalidateItemDecorations() before insert/update/remove data.

    0 讨论(0)
  • 2020-12-10 04:06

    ListAdapter solution:

    listAdapter.submitList(newList) {
        handler.post { // or just "post" if you're inside View
            recyclerView.invalidateItemDecorations()
        }
    }
    

    The key is new submitList(list, commitCallback) method from latest RecyclerView release. It allows you to call invalidateItemDecorations() after ListAdapter will apply all changes to RecyclerView.

    I also had to add post {} call to make it work - without it decorations invalidated too early, and nothing happens.

    0 讨论(0)
提交回复
热议问题