我正在使用分页库通过ItemKeyedDataSource
从网络加载数据。提取项目后,用户可以对其进行编辑,此更新将在内存缓存中完成(不使用像Room这样的数据库)。
现在,由于PagedList
本身无法更新(讨论了here),我必须重新创建PagedList
并将其传递给PagedListAdapter
。
更新本身没有问题,但是用新的recyclerView
更新了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;
}
}
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);
答案 0 :(得分:5)
您应该在可观察对象上使用阻止调用。如果您不在与loadInitial
,loadAfter
或loadBefore
相同的线程中提交结果,则结果是适配器将计算现有列表项的diff
首先针对一个空列表,然后针对新加载的项目。如此有效,就好像所有项目都被删除然后再次插入一样,这就是列表似乎跳到开头的原因。
答案 1 :(得分: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.requestedStartPosition
,params.requestedLoadSize
和params.pageSize
起作用。
每当传递新的PagedList
时,都需要确保它包含用户当前滚动到的元素。否则,您的PagedListAdapter会将其视为这些元素的删除。然后,稍后,当您的loadAfter或loadBefore项目加载这些元素时,它将把它们视为这些元素的后续插入。您需要避免删除和插入任何可见项。既然听起来像是滚动到顶部,也许是您不小心删除了所有项目并将其全部插入。
我认为将Room与PagedLists一起使用时的工作方式是:
loadInitial
设置为可见元素的情况下调用params.requestedStartPosition
。loadBefore
或loadAfter
。 我不确定这与您要尝试执行的操作相对应,但也许有帮助吗?每当您提供新的PagedList时,它都会与前一个页面相区别,并且您要确保没有虚假的插入或删除,否则可能会造成混淆。
我还看到了PAGE_SIZE不够大的问题。文档建议一次可以看到的最大元素数量是原来的几倍。
答案 2 :(得分:2)
如果未正确实现DiffUtil.ItemCallback
,也会发生这种情况。通过正确的实现,我的意思是,您应该正确检查oldItem
和newItem
是否相同,并相应地从true
和{返回false
或areItemsTheSame()
{1}}方法。
例如,如果我总是从这两个方法返回false:
areContentsTheSame()
图书馆认为所有项目都是新的,因此它跳到顶部以显示所有新项目。
因此请确保您仔细检查DiffUtil.ItemCallback<Mention>() {
@Override
public boolean areItemsTheSame(Item oldItem, Item newItem) {
return false;
}
@Override
public boolean areContentsTheSame(Item oldItem, Item newItem) {
return false;
}
}
和oldItem
并根据比较结果正确返回newItem
或true