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

问题描述 投票:5回答:2

我正在使用Paging Library使用ItemKeyedDataSource从网络加载数据。在获取项目后,用户可以编辑它们,此更新在内存缓存中完成(不使用像Room这样的数据库)。

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

更新本身没有问题,但在使用新的recyclerView更新PagedList后,列表会跳转到列表的开头,从而破坏前一个滚动位置。无论如何更新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);
android-recyclerview android-adapter android-architecture-components android-jetpack
2个回答
8
投票

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


3
投票

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

我看了一下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有关。

So what's going wrong?

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

我认为使用Room with PagedLists时这种方式的工作方式是:

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

我不确定这与你想要做的事情有什么关系,但也许这有帮助吗?每当你提供一个新的PagedList时,它将与前一个相差一点,你想确保没有虚假的插入或删除,或者它可能会变得非常困惑。

Other ideas

我也看到了PAGE_SIZE不够大的问题。文档建议多次使用一次可见的最大元素数。

© www.soinside.com 2019 - 2024. All rights reserved.