如何将DiffUtil.Callback与具有页眉和页脚的RecyclerView一起使用?

and*_*per 5 android android-recyclerview

背景

我正在开发一个带有RecyclerView的应用程序,您可以根据需要上下滚动。

数据项是从服务器加载的,因此,如果您要到达底部或顶部,则应用程序将获取新数据以显示在那里。

为了避免奇怪的滚动行为并停留在当前项目上,我使用了' DiffUtil.Callback ',覆盖了'getOldListSize','getNewListSize','areItemsTheSame','areContentsTheSame'。

我在这里问过这个问题,因为我从服务器获得的只是一个全新的项目列表,而不是与上一个列表的区别。

问题

RecyclerView没有仅要显示的数据。也有一些特殊的项目:

由于Internet连接速度可能很慢,因此RecyclerView中有一个标题项和一个脚注项,它们只有一个特殊的“进度”视图,以表明您已经到达边缘并且将很快加载。

页眉和页脚始终存在于列表中,并且不会从服务器收到它们。它纯粹是UI的一部分,只是为了显示即将加载的内容。

就像其他项一样,它需要由DiffUtil.Callback处理,因此对于areItemsTheSameareContentsTheSame,如果旧页眉是新页眉,而旧页脚是新页脚,则只返回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
            ...
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

似乎正确吗?好吧,这是错误的。如果用户位于列表顶部,显示标题,并且列表中有新项目更新,则标题将停留在顶部,这意味着您之前看到的项目将被新项目推开。

例:

  • 之前:页眉,0、1、2、3,页脚
  • 之后:页眉,-3,-2,-1、0、1、2、3,页脚

因此,如果您停留在标题上,并且服务器向您发送了新列表,您仍然会在新项目下方看到标题,而看不到旧项目。它为您滚动而不是停留在同一位置。

这是显示问题的草图。黑色矩形显示列表的可见部分。

在此处输入图片说明

如您所见,在加载之前,可见部分具有标题和一些项目,而在加载之后,它仍然具有标题和一些项目,但是那些是新项目,它们将旧项目推开。

我需要在这种情况下使用标头,因为实际内容在标头下方。它可能会在其上方显示其他项目(或其中的一部分),而不是标题的区域,但当前项目的可见位置应保持在原位置。

仅在列表顶部显示标题时,才会发生此问题。在所有其他情况下,它都可以正常工作,因为在可见区域的顶部仅显示普通项目。

我尝试过的

我试图找到如何将DiffUtil.Callback设置为忽略某些项目的方法,但是我不认为存在这种情况。

我在考虑一些解决方法,但是每种方法都有其自身的缺点:

  • 一个NestedScrollView(或RecyclerView)将在中间保留页眉和页脚以及RecyclerView,但这可能会导致一些滚动问题,特别是由于我已经拥有一个依赖RecyclerView的复杂布局(视图折叠等)的事实。 )。

  • 也许在常规项目的布局中,我也可以放置页眉和页脚的布局(或仅放置页眉,因为这是有问题的)。但这对性能而言是一件坏事,因为它会虚增任何额外的视图。另外,它还要求我切换隐藏和查看其中的新视图。

  • 每当服务器进行更新时,我都可以为标题设置一个新的ID,就好像以前的标题消失了一样,并且在新列表的顶部有一个全新的标题。但是,在顶部没有真正更新列表的情况下,这可能会有风险,因为标题将显示为已删除并重新添加。

问题

没有此类解决方法,有没有办法解决这个问题?

有没有办法说DiffUtil.Callback:“这些项目(页眉和页脚)不是要滚动的真实项目,而这些项目(真实数据项目)应该是”?

and*_*per 0

我认为目前我采取的解决方案就足够了。这有点奇怪,但我认为它应该有效:

每当列表中的第一个实际项目不同时,标题项目就会获得一个新的 ID。页脚始终具有相同的 id,因为它可以按照当前的工作方式移动。我什至不需要检查它的 id 是否相同。对他们来说检查areItemsTheSame是这样的:

            oldItem.agendaItemType == AgendaItem.TYPE_HEADER -> return oldItem.id == newItem.id
            oldItem.agendaItemType == AgendaItem.TYPE_FOOTER -> return true
Run Code Online (Sandbox Code Playgroud)

这样,如果标题属于新的列表数据,旧的将被删除,新的将位于顶部。

这不是完美的解决方案,因为它并没有真正将原始标头推到顶部,理论上它使我们“有点”同时有 2 个标头(一个被删除,一个被添加),但我认为这是够好了。

另外,由于某种原因,我不能notifyItemChanged在页眉和页脚上使用,以防它们被更新(互联网连接改变其状态,因此需要单独更改页眉和页脚)。仅notifyDataSetChanged因某种原因才有效。

不过,如果有更官方的方式,我们还是很高兴知道。