背景
我正在开发一个具有 RecyclerView 的应用程序,您可以随意上下滚动。
数据项是从服务器加载的,因此如果您即将到达底部或顶部,应用程序会在那里显示新数据。
为了避免奇怪的滚动行为,并停留在当前项目上,我使用' DiffUtil.Callback ' ,覆盖 'getOldListSize'、'getNewListSize'、'areItemsTheSame'、'areContentsTheSame'。
我问过这个 here ,因为我从服务器得到的只是一个全新的项目列表,而不是与之前列表的区别。
问题
RecyclerView 不仅要显示数据。里面也有一些特别的东西:
由于 Internet 连接可能很慢,因此 RecyclerView 中有一个页眉项和一个页脚项,它们只有一个特殊的 Progress View ,以显示您已经到达边缘并且它将很快加载。
页眉页脚始终存在于列表中,不会从服务器接收。它纯粹是 UI 的一部分,只是为了显示即将加载的内容。
事情是,就像其他项目一样,它需要由 DiffUtil.Callback 处理,所以对于 areItemsTheSame
和 areContentsTheSame
, 如果旧页眉是新页眉,并且旧页脚是新页脚,我只返回 true:
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldItems[oldItemPosition]
val newItem = newItems[newItemPosition]
when {
oldItem.itemType != newItem.itemType -> return false
oldItem.itemType == ItemType.TYPE_FOOTER || oldItem.itemType == AgendaItem.TYPE_HEADER -> return true
...
}
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldItems[oldItemPosition]
val newItem = newItems[newItemPosition]
return when {
oldItem.itemType == ItemType.TYPE_FOOTER || oldItem.itemType == ItemType.TYPE_HEADER -> true
...
}
}
}
看起来对吗?好吧,这是错误的。如果用户位于列表顶部,显示标题,并且列表更新了新项目,则标题将保持在顶部,这意味着您之前看到的项目将被新项目推开。
例子:
因此,如果您停留在标题上,并且服务器向您发送了新列表,您仍然会看到标题和新项目下方,而不会看到旧项目。它会为您滚动而不是停留在同一位置。
这是显示问题的草图。黑色矩形显示列表的可见部分。
正如你所看到的,在加载之前,可见部分有标题和一些项目,加载后它仍然有标题和一些项目,但这些是推开旧项目的新项目。
我需要在这种情况下去掉标题,因为真正的内容在它下面。它可能会显示其上方的其他项目(或其中的一部分)而不是标题区域,但当前项目的可见位置应保持在原处。
只有在列表顶部显示标题时才会出现此问题。在所有其他情况下,它都可以正常工作,因为在可见区域的顶部仅显示正常项目。
我试过的
我试图找到如何设置 DiffUtil.Callback 以忽略某些项目,但我认为不存在这样的事情。
我正在考虑一些解决方法,但每个方法都有自己的缺点:
问题
没有这种变通办法,有没有办法解决这个问题?
有没有办法告诉
DiffUtil.Callback
:“这些项目(页眉和页脚)不是要滚动到的真实项目,而这些项目(真实数据项目)应该是”?
最佳答案
我将尝试解释我所看到的问题的解决方案:
第一步:删除 FOOTER 和 HEADER View 的所有代码。
第 2 步:添加这些根据用户滚动方向在适配器中添加和删除虚拟模型项的方法:
/**
* Adds loader item in the adapter based on the given boolean.
*/
public void addLoader(boolean isHeader) {
if (!isLoading()) {
ArrayList<Model> dataList = new ArrayList<>(this.oldDataList);
if(isHeader) {
questions.add(0, getProgressModel());
else {
questions.add(getProgressModel());
setData(dataList);
}
}
/**
* Removes loader item from the UI.
*/
public void removeLoader() {
if (isLoading() && !dataList.isEmpty()) {
ArrayList<Model> dataList = new ArrayList<>(this.oldDataList);
dataList.remove(getDummyModel());
setData(questions);
}
}
public MessageDetail getChatItem() {
return new Model(0, 0, 0, "", "", "")); // Here the first value is id which is set as zero.
}
这是您需要确定该项目是加载程序项目还是实际数据项目的其余适配器逻辑:
@Override
public int getItemViewType(int position) {
return dataList.get(position).getId() == 0 ? StaticConstants.ItemViewTypes.PROGRESS : StaticConstants.ItemViewTypes.CONTENT;
}
根据 View 类型,您可以在适配器中添加进度条 View 支架。
第 3 步:在数据加载逻辑中使用这些方法:
在
onScrolled()
中进行 API 调用时recyclerView
的方法,需要在api调用前添加一个loader项,在api调用后删除。使用上面给定的适配器方法。 onScrolled
中的编码应该看起来像这样:recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy < 0) { //This is top scroll, so add a loader as the header.
recyclerViewAdapter.addLoader(true);
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
if (!recyclerViewAdapter.isLoading(true)) {
if (linearLayoutManager.findFirstCompletelyVisibleItemPosition() <= 2) {
callFetchDataApi();
}
}
}
} else {
if (!recyclerViewAdapter.isLoading(false)) {
if (linearLayoutManager.findLastCompletelyVisibleItemPosition() >= linearLayoutManager.getItemCount() - 2) {
callFetchDataApi();
}
}
});
现在在 api 调用为您提供所需的数据之后。简单地从列表中删除添加的加载器,如下所示:
private void onGeneralApiSuccess(ResponseModel responseModel) {
myStreamsDashboardAdapter.removeLoader();
if (responseModel.getStatus().equals(SUCCESS)) {
// Manage your pagination and other data loading logic here.
dataList.addAll(responseModel.getDataList());
recyclerViewAdapter.setData(dataList);
}
}
最后,您需要在数据加载操作期间避免任何滚动,为此添加一个逻辑方法是
isLoading()
方法。用于方法onScrolled()
的代码中:public boolean isLoading(boolean isFromHeader) {
if (isFromHeader) {
return dataList.isEmpty() || dataList.get(0).getId() == 0;
} else {
return dataList.isEmpty() || dataList.get(dataList.size() -1).getId() == 0;
}
}
如果你不明白这些,请告诉我。
关于android - 如何将 DiffUtil.Callback 与具有页眉和页脚的 RecyclerView 一起使用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49747957/