【问题标题】:PagedListAdapter jumps to beginning of the list on receiving new PagedListPagedListAdapter 在接收到新的 PagedList 时跳转到列表的开头
【发布时间】:2018-06-30 07:54:51
【问题描述】:

我正在使用 分页库 使用 ItemKeyedDataSource 从网络加载数据。获取项目后用户可以对其进行编辑,此更新在 Memory 缓存中完成(不使用像 Room 这样的数据库)。

现在由于PagedList 本身无法更新(讨论here),我必须重新创建PagedList 并将其传递给PagedListAdapter

更新本身没问题,但是在用新的PagedList 更新recyclerView 后,列表会跳转到列表的开头,破坏上一个 滚动位置。是否有在保持滚动位置的同时更新 PagedList(比如它如何与 Room 一起使用)?

DataSource 是这样实现的:

public class MentionKeyedDataSource extends ItemKeyedDataSource<Long, Mention> {

    private Repository repository;
    ...
    private List<Mention> cachedItems;

    public MentionKeyedDataSource(Repository repository, ..., List<Mention> cachedItems){
        super();

        this.repository = repository;
        this.teamId = teamId;
        this.inboxId = inboxId;
        this.filter = filter;
        this.cachedItems = new ArrayList<>(cachedItems);
    }

    @Override
    public void loadInitial(@NonNull LoadInitialParams<Long> params, final @NonNull ItemKeyedDataSource.LoadInitialCallback<Mention> callback) {
        Observable.just(cachedItems)
                .filter(() -> return cachedItems != null && !cachedItems.isEmpty())
                .switchIfEmpty(repository.getItems(..., params.requestedLoadSize).map(...))
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(response -> callback.onResult(response.data.list));
    }

    @Override
    public void loadAfter(@NonNull LoadParams<Long> params, final @NonNull ItemKeyedDataSource.LoadCallback<Mention> callback) {
        repository.getOlderItems(..., params.key, params.requestedLoadSize)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(response -> callback.onResult(response.data.list));
    }

    @Override
    public void loadBefore(@NonNull LoadParams<Long> params, final @NonNull ItemKeyedDataSource.LoadCallback<Mention> callback) {
        repository.getNewerItems(..., params.key, params.requestedLoadSize)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(response -> callback.onResult(response.data.list));
    }

    @NonNull
    @Override
    public Long getKey(@NonNull Mention item) {
        return item.id;
    }
}

PagedList 是这样创建的:

PagedList.Config config = new PagedList.Config.Builder()
        .setPageSize(PAGE_SIZE)
        .setInitialLoadSizeHint(preFetchedItems != null && !preFetchedItems.isEmpty()
                ? preFetchedItems.size()
                : PAGE_SIZE * 2
        ).build();

pagedMentionsList = new PagedList.Builder<>(new MentionKeyedDataSource(mRepository, team.id, inbox.id, mCurrentFilter, preFetchedItems)
        , config)
        .setFetchExecutor(ApplicationThreadPool.getBackgroundThreadExecutor())
        .setNotifyExecutor(ApplicationThreadPool.getUIThreadExecutor())
        .build();

PagedListAdapter 是这样创建的:

public class ItemAdapter extends PagedListAdapter<Item, ItemAdapter.ItemHolder> { //Adapter from google guide, Nothing special here.. }

mAdapter = new ItemAdapter(new DiffUtil.ItemCallback<Mention>() {
            @Override
            public boolean areItemsTheSame(Item oldItem, Item newItem) {
                return oldItem.id == newItem.id;
            }

            @Override
            public boolean areContentsTheSame(Item oldItem, Item newItem) {
                return oldItem.equals(newItem);
            }
        });

,并更新如下:

mAdapter.submitList(pagedList);

【问题讨论】:

  • 您找到解决方法了吗?我确实注意到使用.blockingFirst() 而不是订阅,可以防止跳转
  • 很遗憾,没有,从那以后我们使用自己的分页切换到 ListAdapter。
  • 我遇到了类似的问题,回收站跳到了列表的中间。
  • @Blackbelt 谢谢!非常感谢,我整天都在寻找问题!文档中没有任何地方写过它!如果不是你的回答,我早就死了……谢谢!!!
  • 我也面临同样的问题。有人找到解决方案了吗?

标签: android-recyclerview android-adapter android-architecture-components android-jetpack android-paging


【解决方案1】:

你应该在你的 observable 上使用阻塞调用。如果您没有在与loadInitialloadAfterloadBefore 相同的线程中提交结果,那么适配器将首先针对空列表计算现有列表项的diff,然后针对新加载的项目。如此有效,就好像所有项目都被删除然后再次插入,这就是列表似乎跳到开头的原因。

【讨论】:

  • 我一辈子都想不通为什么 diff 工具不能正常工作。谢谢@Marc El Naddaf
  • @Marc 我应该阻止哪个 observable?能详细点吗?
  • 即使我正在使用阻塞调用,我仍然面临这个问题。我正在使用PageKeyedDataSource。你们对可能发生的事情有见解吗?
  • @marcnaddaf,非常感谢,你拯救了我的一天!我不明白为什么删除单个项目后跟dataSource.invalidate() 调用会导致整个RecyclerView 状态擦除。当我理解了问题时(感谢您的回答),我找到了这篇文章,对我也有帮助:jacobstinson.com/posts/the-android-paging-library
【解决方案2】:

您在实现loadInitial 时没有使用androidx.paging.ItemKeyedDataSource.LoadInitialParams#requestedInitialKey,我认为您应该这样做。

我查看了 ItemKeyedDataSource 的另一种实现,自动生成的 Room DAO 代码使用的实现:LimitOffsetDataSource。它对loadInitial 的实现包含(Apache 2.0 licensed 代码如下):

// bound the size requested, based on known count final int firstLoadPosition = computeInitialLoadPosition(params, totalCount); final int firstLoadSize = computeInitialLoadSize(params, firstLoadPosition, totalCount);

...这些函数对params.requestedStartPositionparams.requestedLoadSizeparams.pageSize 起作用。

那么出了什么问题?

每当您传递一个新的PagedList 时,您需要确保它包含用户当前滚动到的元素。否则,您的 PagedListAdapter 会将其视为删除这些元素。然后,稍后,当您的 loadAfter 或 loadBefore 项目加载这些元素时,它会将它们视为这些元素的后续插入。您需要避免移除和插入任何可见项目。由于听起来您正在滚动到顶部,因此您可能不小心删除了所有项目并将它们全部插入。

我认为将 Room 与 PagedLists 一起使用时的工作方式是:

  1. 数据库已更新。
  2. Room 观察者使数据源无效。
  3. PagedListAdapter 代码发现失效并使用工厂创建新数据源,并调用 loadInitial 并将 params.requestedStartPosition 设置为可见元素。
  4. 向 PagedListAdapter 提供了一个新的 PagedList,它运行差异检查代码以查看实际更改的内容。通常,可见的内容没有任何变化,但可能已插入、更改或删除了一个元素。初始加载之外的所有内容都被视为已删除 - 这在 UI 中不应该被注意到。
  5. 滚动时,PagedListAdapter 代码可以发现需要加载新项目,并调用loadBeforeloadAfter
  6. 这些完成后,将向 PagedListAdapter 提供一个全新的 PagedList,后者运行差异检查代码以查看实际更改的内容。通常 - 只是插入。

我不确定这与您要做什么相对应,但也许这有帮助?每当您提供一个新的 PagedList 时,它都会与前一个不同,并且您要确保没有虚假的插入或删除,否则它会变得非常混乱。

其他想法

我还看到了 PAGE_SIZE 不够大的问题。文档建议一次可以看到的最大元素数是其数倍。

【讨论】:

  • "将 params.requestedStartPosition 设置为可见元素。"在我的情况下,它在失效后总是 0
【解决方案3】:

DiffUtil.ItemCallback 未正确实现时也会发生这种情况。通过正确的实现,我的意思是,您应该正确检查oldItemnewItem 是否相同,并相应地从areItemsTheSame()areContentsTheSame() 方法返回truefalse

例如,如果我总是从这两种方法中返回 false,例如:

DiffUtil.ItemCallback<Mention>() {
    @Override
    public boolean areItemsTheSame(Item oldItem, Item newItem) {
        return false;
    }

    @Override
    public boolean areContentsTheSame(Item oldItem, Item newItem) {
        return false;
    }
}

图书馆认为所有的项目都是新的,因此它跳到顶部以显示所有新项目。

因此,请务必仔细检查oldItemnewItem,并根据您的比较正确返回truefalse

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2019-10-27
  • 1970-01-01
  • 1970-01-01
  • 2019-02-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多