recyclerview 中的 Diffutil,如果添加了新项目,则使其自动滚动

Posted

技术标签:

【中文标题】recyclerview 中的 Diffutil,如果添加了新项目,则使其自动滚动【英文标题】:Diffutil in recycleview, making it autoscroll if a new item is added 【发布时间】:2017-09-13 11:09:12 【问题描述】:

如果我们使用DiffUtil.Callback,然后做

adapter.setItems(itemList);
diff.dispatchUpdatesTo(adapter);

我们如何确保添加的新元素会滚动到新位置。

我有一个案例,我看到 item 消失了,并且在顶部创建了一个新元素作为第一个元素,但不可见。它隐藏在顶部,直到您向下滚动以使其可见。 在使用DiffUtil 之前,我是手动实现的,在我知道我在某个位置(顶部)插入之后,我可以滚动到。

【问题讨论】:

只需添加项目并使用RecyclerView.scrollToPosition(int position)。在我不得不这样做的时候工作,即使使用 DiffUtil。 【参考方案1】:

您也可以使用dispatchUpdatesTo(ListUpdateCallback) 方法。

所以你可以只实现一个ListUpdateCallback,它会为你提供插入的第一个元素

class MyCallback implements ListUpdateCallback 
    int firstInsert = -1;
    Adapter adapter = null;
    void bind(Adapter adapter) 
        this.adapter = adapter;
    
    public void onChanged(int position, int count, Object payload) 
        adapter.notifyItemRangeChanged(position, count, payload);
    
    public void onInserted(int position, int count) 
        if (firstInsert == -1 || firstInsert > position) 
            firstInsert = position;
        
        adapter.notifyItemRangeInserted(position, count);
    
    public void onMoved(int fromPosition, int toPosition) 
        adapter.notifyItemMoved(fromPosition, toPosition);
    
    public void onRemoved(int position, int count) 
        adapter.notifyItemRangeRemoved(position, count);
    

然后手动滚动RecyclerView

myCallback.bind(adapter)
adapter.setItems(itemList);
diff.dispatchUpdatesTo(myCallback);
recycler.smoothScrollToPosition(myCallback.firstInsert);

【讨论】:

注意:即使开始之前不可见,这也会滚动到第一个位置。为了获得更好的用户体验,您应该尝试保留用户当前的滚动位置,并且只有在更新之前开始可见时才滚动到开始。 感谢分享! @LorneLaliberte 您能否提供代码示例以供您提出建议?我认为这将是最好的解决方案 @okset 我已经做到了。我对这个问题的回答包含将完成我的评论建议的代码。【参考方案2】:

有一种简单的方法可以做到这一点,如果项目被插入到可视区域之外,它还可以保留用户的滚动位置:

import android.os.Parcelable;

Parcelable recyclerViewState = recyclerView.getLayoutManager().onSaveInstanceState();
// apply diff result here (dispatch updates to the adapter)
recyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState);              

使用这种方法,如果将新项目插入到用户可以看到的位置,它们就会变得可见 - 但如果将项目插入到视图之外,则用户的观点会被保留。

【讨论】:

谢谢!它可以工作,只需在更新项目之前保存实例状态并在更新后立即恢复 谢谢。我将其用作某些边缘情况的解决方法。一个关于不需要的自动滚动的跟踪器被打开 - issuetracker.google.com/issues/70149059 这应该是公认的答案。效果很好!非常感谢! 看来这是唯一可行的解​​决方案。但为什么?我喜欢新的 ListAdapter,但有时它会很痛苦……是因为 DiffUtil 内容比较是错误的吗?【参考方案3】:

我使用RecyclerView.AdapterDataObserver 检测何时添加了新项目,然后滚动到我的列表顶部;

adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() 
    @Override
    public void onItemRangeInserted(int positionStart, int itemCount) 
        super.onItemRangeInserted(positionStart, itemCount);
        recycleView.smoothScrollToPosition(0);
    
);

【讨论】:

如果您希望在添加新项目时始终滚动到顶部,这很有效。谢谢@gregdev 我最终使用了这个和onItemRangeRemoved,因为我可以在更改范围时始终滚动到顶部。 onItemRangeChanged 对我来说工作不正常。谢谢!

以上是关于recyclerview 中的 Diffutil,如果添加了新项目,则使其自动滚动的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 DiffUtil 更新 RecyclerView 适配器

Epoxy库中的自动比较是基于DiffUtil吗?

RecyclerView — DiffUtil

Android 高性能列表:RecyclerView + DiffUtil

Android 高性能列表:RecyclerView + DiffUtil

如何在 Recyclerview 中正确使用 DiffUtil 更新数组列表?