ListAdapter不会更新reyclerview中的项目

Vee*_*ath 46 android listadapter kotlin

我正在使用新的支持库ListAdapter.这是我的适配器代码

class ArtistsAdapter : ListAdapter<Artist, ArtistsAdapter.ViewHolder>(ArtistsDiff()) {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(parent.inflate(R.layout.item_artist))
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(getItem(position))
    }

    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        fun bind(artist: Artist) {
            itemView.artistDetails.text = artist.artistAlbums
                    .plus(" Albums")
                    .plus(" \u2022 ")
                    .plus(artist.artistTracks)
                    .plus(" Tracks")
            itemView.artistName.text = artist.artistCover
            itemView.artistCoverImage.loadURL(artist.artistCover)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我正在更新适配器

musicViewModel.getAllArtists().observe(this, Observer {
            it?.let {
                artistAdapter.submitList(it)
            }
        })
Run Code Online (Sandbox Code Playgroud)

我的差异类

class ArtistsDiff : DiffUtil.ItemCallback<Artist>() {
    override fun areItemsTheSame(oldItem: Artist?, newItem: Artist?): Boolean {
        return oldItem?.artistId == newItem?.artistId
    }

    override fun areContentsTheSame(oldItem: Artist?, newItem: Artist?): Boolean {
        return oldItem == newItem
    }
}
Run Code Online (Sandbox Code Playgroud)

发生的事情是在适配器第一次呈现所有项目时调用submitList,但是当使用更新的对象属性再次调用submitList时,它不会重新呈现已更改的视图.

当我滚动列表时,它会重新呈现视图,而列表又会调用 bindView()

此外,我注意到adapter.notifyDatasSetChanged()提交列表后调用呈现更新值的视图,但我不想调用,notifyDataSetChanged()因为列表适配器内置了diff utils

有人能帮我一下吗?

ins*_*a_c 58

编辑:我理解为什么会发生这不是我的观点.我的观点是,它至少需要发出警告或调用该notifyDataSetChanged()函数.因为显然我正在调用这个submitList(...)函数是有原因的.我很确定人们会在几个小时之前弄清楚出了什么问题,直到他们发现submitList()无声地忽略了这个调用.

这是因为Google奇怪的逻辑.因此,如果您将相同的列表传递给适配器,它甚至不会调用DiffUtil.

public void submitList(final List<T> newList) {
    if (newList == mList) {
        // nothing to do
        return;
    }
....
}
Run Code Online (Sandbox Code Playgroud)

ListAdapter如果无法处理同一列表中的更改,我真的不明白这一点.如果您想要更改列表中的项目并转到ListAdapter并查看更改,则需要创建列表的深层副本,或者需要定期RecyclerView使用自己的DiffUtill类.

  • 是的但是在那时,有一个原因我称之为`submitList`,对吗?它应该至少调用`notifyDataSetChanged()`而不是默默地忽略调用.我很确定人们试图弄清楚几个小时出了什么问题,直到他们发现`submitList()`忽略了这个调用. (19认同)
  • 所以我回到了 `RecyclerView.Adapter&lt;VH&gt;` 和 `notifyDataSetChanged()` 。现在生活很好。浪费了好几个小时 (16认同)
  • @insa_c你可以在你的计数中添加3个小时,这就是我浪费了多少时间来试图理解为什么我的列表视图在某些边缘情况下没有更新...... (8认同)
  • 因为它需要先前的状态来执行diff.当然,如果你覆盖以前的状态,它就无法处理它.O_O (3认同)
  • 这就是为什么可变列表是邪恶的。提交列表,修改后并提交相同的实例?哎哟!为什么要再次提交相同的列表?如果您默默地修改了列表,只需调用“notifyDataSetChanged()”来通知适配器,则无需再次传递相同的引用。 (3认同)

Min*_*mir 29

使用 Kotlin,您只需根据您的使用情况将列表转换为新的MutableList像这样或其他类型的列表

.observe(this, Observer {
            adapter.submitList(it?.toMutableList())
        })
Run Code Online (Sandbox Code Playgroud)

  • 为什么这能起作用?它有效,但很好奇为什么会发生这种情况。 (4认同)

RJF*_*res 27

该库假设您正在使用Room或任何其他ORM,每次更新时都会提供一个新的异步列表,因此只需在其上调用submitList即可,对于草率开发人员,如果调用相同的列表,则会阻止执行两次计算.

接受的答案是正确的,它提供了解释,但没有提供解决方案.

如果您没有使用任何此类库,您可以做的是:

submitList(null);
submitList(myList);
Run Code Online (Sandbox Code Playgroud)

另一个解决方案是覆盖submitList(不会导致快速闪烁):

@Override
public void submitList(final List<Author> list) {
    super.submitList(list != null ? new ArrayList<>(list) : null);
}
Run Code Online (Sandbox Code Playgroud)

有点迟钝的逻辑,但工作完美.我首选的方法是第二个方法,因为它不会导致每一行获得onBind调用.

  • 我花了最后一个小时试图弄清楚我的逻辑有什么问题。好诡异的逻辑。 (5认同)
  • @PaulWoitaschek这不是一个技巧,它使用的是JAVA :)它用于解决开发人员正在“睡觉”的库中的许多问题。之所以选择此列表而不是传递.submitList(new ArrayList(list)),是因为您可以在代码中的多个位置提交列表。您可能会忘记每次都创建一个新数组,这就是为什么您要覆盖它。 (4认同)
  • 显然,这在使用新项目更新视图模型中的列表时有效,但是当我更新列表中项目的属性(布尔值 - isSelected)时,这仍然不起作用。我检查过。知道问题可能发生在哪里吗? (3认同)
  • 那是骇客。只需传递列表的副本即可。`.submitList(new ArrayList(list))` (2认同)
  • 即使使用 Room 我也遇到了类似的问题。 (2认同)

Jan*_*elý 20

我也有类似的问题,但不正确的渲染是由共同造成setHasFixedSize(true)android:layout_height="wrap_content"。第一次,适配器提供了一个空列表,因此高度从未更新,并且是0. 无论如何,这解决了我的问题。其他人可能有同样的问题,并会认为这是适配器的问题。

  • 是的,将recycleview设置为wrap_content会更新列表,如果将其设置为match_parent,它将不会调用适配器 (2认同)

Yoa*_*n.G 11

如果您在使用过程中遇到一些问题

recycler_view.setHasFixedSize(true)

你应该绝对检查这个评论:https : //github.com/thoughtbot/expandable-recycler-view/issues/53#issuecomment-362991531

它解决了我这边的问题。

(这是应要求的评论截图)

在此处输入图片说明


Ash*_*agi 9

根据官方文档

每当您调用 submitList 时,它都会提交一个新列表进行比较和显示。

这就是为什么每当您在前一个(已提交的列表)上调用 submitList时,它不会计算 Diff并且不会通知适配器数据集的更改。


Boj*_* P. 7

今天我也偶然发现了这个“问题”。在insa_c 的回答RJFares 的解决方案的帮助下,我为自己做了一个 Kotlin 扩展函数:

/**
 * Update the [RecyclerView]'s [ListAdapter] with the provided list of items.
 *
 * Originally, [ListAdapter] will not update the view if the provided list is the same as
 * currently loaded one. This is by design as otherwise the provided DiffUtil.ItemCallback<T>
 * could never work - the [ListAdapter] must have the previous list if items to compare new
 * ones to using provided diff callback.
 * However, it's very convenient to call [ListAdapter.submitList] with the same list and expect
 * the view to be updated. This extension function handles this case by making a copy of the
 * list if the provided list is the same instance as currently loaded one.
 *
 * For more info see 'RJFares' and 'insa_c' answers on
 * /sf/ask/3480846981/
 */
fun <T, VH : RecyclerView.ViewHolder> ListAdapter<T, VH>.updateList(list: List<T>?) {
    // ListAdapter<>.submitList() contains (stripped):
    //  if (newList == mList) {
    //      // nothing to do
    //      return;
    //  }
    this.submitList(if (list == this.currentList) list.toList() else list)
}
Run Code Online (Sandbox Code Playgroud)

然后可以在任何地方使用,例如:

viewModel.foundDevices.observe(this, Observer {
    binding.recyclerViewDevices.adapter.updateList(it)
})
Run Code Online (Sandbox Code Playgroud)

并且它只(并且总是)复制与当前加载的列表相同的列表。


jus*_*ser 6

就我而言,我忘记LayoutManagerRecyclerView. 其效果与上述相同。


小智 6

在同一个案例中浪费了很多时间来找出问题。

但在我的情况下,问题是我忘记为我的 recyclerView 指定一个 layoutManager: vRecyclerView.layoutManager = LinearLayoutManager(requireContext())

希望没有人重蹈我的覆辙……


tan*_*993 6

我有一些奇怪的行为。我在 LiveDate 中使用 MutableList。

在 kotlin 中,以下代码不起作用:

mViewModel.products.observe(viewLifecycleOwner, {
    mAdapter.submitList(it)
})
Run Code Online (Sandbox Code Playgroud)

但是,当我将其更改为 it.toList() 时,它起作用了

mViewModel.products.observe(viewLifecycleOwner, {
    mAdapter.submitList(it.toList())
})
Run Code Online (Sandbox Code Playgroud)

虽然,“它”是同一个列表。