PagedListAdapter在接收到新的PagedList时跳到列表的开头

Kei*_*ati 8 android-adapter android-recyclerview android-architecture-components android-jetpack

我正在使用Paging Library使用来从网络加载数据ItemKeyedDataSource。提取项目后,用户可以对其进行编辑,此更新在内存缓存内部完成(不使用任何数据库,例如Room)。

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

更新本身没有问题,但是在recyclerView使用new 更新之后PagedList,列表会跳至列表的开头,从而破坏先前的滚动位置。无论如何,要在保持滚动位置的同时更新PagedList(例如它如何与Room一起工作)?

数据源是通过以下方式实现的:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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();
Run Code Online (Sandbox Code Playgroud)

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);
            }
        });
Run Code Online (Sandbox Code Playgroud)

,并像这样更新:

mAdapter.submitList(pagedList);
Run Code Online (Sandbox Code Playgroud)

小智 8

您应该对可观察对象使用阻塞调用。如果你没有在同一个线程提交的结果loadInitialloadAfter或者loadBefore,什么情况是,该适配器将计算diff对空列表现有的列表项,然后再对新加载项。如此有效,就好像所有项目都被删除然后再次插入一样,这就是列表似乎跳到开头的原因。

  • 即使我使用阻塞调用,我仍然面临这个问题。我正在使用“PageKeyedDataSource”。你们对可能发生的事情有见解吗? (5认同)
  • 我一生都无法弄清楚为什么 diff util 无法正常工作。谢谢@Marc El Naddaf (2认同)

Chr*_*her 4

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

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

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

...这些函数用params.requestedStartPosition,params.requestedLoadSize和做一些事情params.pageSize

那么出了什么问题呢?

每当您传递 new 时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 时,它将与前一个 PagedList 有所不同,并且您需要确保没有虚假的插入或删除,否则可能会变得非常混乱。

其他想法

我还看到过 PAGE_SIZE 不够大的问题。文档建议一次可见的最大元素数的几倍。