我使用的是具有RecyclerView的应用程序,您可以根据需要向上和向下滚动。
数据项是从服务器加载的,因此如果您要到达底部或顶部,应用会获取新数据以显示在那里。
为了避免奇怪的滚动行为,并保持当前项目,我使用'DiffUtil.Callback',重写'getOldListSize','getNewListSize','areItemsTheSame','areContentsTheSame'。
我已经问过这个here,因为我从服务器获得的是一个全新的项目列表,而不是与之前列表的区别。
RecyclerView没有只显示的数据。其中也有一些特殊项目:
由于互联网连接速度可能很慢,因此此RecyclerView中有一个标题项和一个页脚项,它只有一个特殊的进度视图,以显示您已到达边缘并且很快就会加载。
页眉和页脚始终存在于列表中,并且不会从服务器接收它们。它纯粹是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
...
}
}
}
好像对吗?嗯,这是错的。如果用户位于列表的顶部,显示标题,并且列表会使用新项目进行更新,则标题将保留在顶部,这意味着您看到的先前项目将被新项目推开。< / p>
示例:
因此,如果您留在标题上,并且服务器向您发送了新列表,您仍然可以看到标题,并在新项目下方,而不会看到旧标题。它会滚动而不是停留在相同的位置。
这是一个显示问题的草图。黑色矩形显示列表的可见部分。
正如你所看到的,在加载之前,可见部分有标题和一些项目,加载后它仍然有标题和一些项目,但这些是推翻旧项目的新项目。
我需要在这种情况下使用标题,因为真实内容低于它。它可能会显示其上方的其他项目(或部分项目),而不是标题区域,但当前项目的可见位置应保持原样。
仅当列表顶部显示标题时才会出现此问题。在所有其他情况下,它工作正常,因为只有正常项目显示在可见区域的顶部。
我试图找到如何设置DiffUtil.Callback以忽略某些项目,但我不认为存在这样的事情。
我在考虑一些变通方法,但每种方法都有其缺点:
一个NestedScrollView(或RecyclerView),它将把页眉和页脚和RecyclerView放在中间,但这可能会导致一些滚动问题,特别是由于我已经有一个依赖于RecyclerView的复杂布局(崩溃的意见等......)。
也许在正常项目的布局中,我也可以设置页眉和页脚的布局(或者只是标题,因为这个是有问题的)。但这对性能来说是一件糟糕的事情,因为它会毫无意义地夸大额外的观点。此外,它需要我切换隐藏和查看内部的新视图。
每次有来自服务器的更新时,我都可以为标头设置一个新ID,使其好像上一个标头消失了,并且新列表顶部有一个全新的标题。但是,如果顶部的列表没有真正的更新,这可能会有风险,因为标题将被显示为删除然后重新添加。
有没有办法在没有这些解决方法的情况下解决这个问题?
有没有办法告诉DiffUtil.Callback
:“这些项目(页眉和页脚)不是要滚动到的真实项目,这些项目(真实数据项目)应该是”?
答案 0 :(得分:1)
我将尽力解释我认为对您的问题的解决方案:
步骤1::删除FOOTER和HEADER视图的所有代码。
步骤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;
}
根据视图类型,您可以在适配器中添加进度条视图支架。
第3步:在数据加载逻辑中使用以下方法:
在使用onScrolled()
的{{1}}方法进行API调用时,您需要在api调用之前添加加载程序项,然后在api调用之后将其删除。使用上面给定的适配器方法。 recyclerView
中的代码应如下所示:
onScrolled
现在,在api调用之后,将为您提供所需的数据。只需从列表中删除添加的加载器,就像这样:
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();
}
}
});
最后,您需要避免在数据加载操作期间发生任何滚动,为此添加一种逻辑方法,即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()
如果您不了解其中任何一个,请告诉我。
答案 1 :(得分:0)
我想现在,我采取的解决方案就足够了。这有点奇怪,但我认为它应该有效:
每当列表在其第一个真实项目中不同时,标题项就会获得一个新ID。页脚总是具有相同的ID,因为它可以以当前工作方式移动。我甚至不需要检查它的id是否相同。对areItemsTheSame
的检查就是这样:
oldItem.agendaItemType == AgendaItem.TYPE_HEADER -> return oldItem.id == newItem.id
oldItem.agendaItemType == AgendaItem.TYPE_FOOTER -> return true
这样,如果标题属于新的列表数据,旧标题将被删除,新标题将位于顶部。
这不是完美的解决方案,因为它并没有真正推动原始标题位于顶部,理论上它使我们“有点”同时拥有2个标题(一个被删除而另一个被添加)但是我认为这很好。
另外,出于某种原因,我不能在页眉和页脚上使用notifyItemChanged
,以防它们得到更新(互联网连接改变其状态,因此需要单独更改页眉和页脚)。只有notifyDataSetChanged
出于某种原因才有效。
不过,如果有更正式的方式,可能很高兴知道。