背景
我在开发一个应用程序,其中有一个可以上下滚动的RecyclerView。
数据项是从服务器加载的,因此如果您即将到达底部或顶部,则应用程序会获取新数据以显示。
为避免奇怪的滚动行为并保持在当前项目上,我使用 'DiffUtil.Callback',覆盖 'getOldListSize'、'getNewListSize'、'areItemsTheSame' 和 'areContentsTheSame'。
我在这里询问过关于此的问题,因为我从服务器获取的只是一个全新的项目列表,而不是与先前列表的差异。
问题
RecyclerView不仅有要显示的数据,还有一些特殊的项目:
由于互联网连接可能很慢,因此在这个RecyclerView中有一个标题项和一个页脚项,它们只有一个特殊的进度视图,以显示您已经到达了边缘,并且即将加载。
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
...
}
}
}
看起来没问题?实际上是错的。如果用户在列表顶部,显示标题,并且列表被更新了新项目,标题将会停留在顶部,这意味着之前看到的项目将被新项目推开。
例如:
- 之前:标题、0、1、2、3、页脚
- 之后:标题、-3、-2、-1、0、1、2、3、页脚
因此,如果您停留在标题处,服务器向您发送新列表,则仍会看到标题,下面是新项目,而不会看到旧项目。它会为您滚动而不是停留在同一位置。
这里有一个草图展示了这个问题。黑色矩形表示列表的可见部分。
正如您所见,在加载之前,可见部分有标题和一些项目,在加载之后,它仍然有标题和一些项目,但那些是推开旧项目的新项目。在这种情况下,我需要标题消失,因为真正的内容在它下面。除了标题区域外,它可能会显示其他项目(或其中的一部分),但当前项目的可见位置应保持不变。
此问题仅在列表顶部显示标题时才会出现。在所有其他情况下,它都可以正常工作,因为只有普通项目显示在可见区域的顶部。
我尝试了一些方法,例如找到如何设置DiffUtil.Callback以忽略某些项目,但我认为这样的方法不存在。
我考虑了一些解决方法,但每种方法都有其缺点:
一个NestedScrollView(或RecyclerView),它将容纳头部和底部以及中间的RecyclerView,但这可能会导致一些滚动问题,特别是由于我已经有一个依赖于RecyclerView的复杂布局(视图折叠等)。
也许在普通项目的布局中,我也可以放置页眉和页脚的布局(或者只是页眉,因为这个是有问题的)。但这对性能来说是不好的,因为它会为无用的视图充气额外的视图。此外,它要求我切换新视图的隐藏和查看。
每当有来自服务器的更新时,我都可以为页眉设置一个新ID,使其好像之前的页眉已经消失了,并且在新列表的顶部有一个全新的页眉。然而,在没有真正更新列表顶部的情况下,这可能是有风险的,因为页眉将被显示为已删除,然后重新添加。
问题
有没有办法解决这个问题,而不需要这样的变通方法?
有没有一种方法可以告诉DiffUtil.Callback
:“这些项(头和尾)不是真正的滚动项,而这些项(真实数据项)应该是”?