ListAdapter不更新RecyclerView中的项目


89

我正在使用新的支持库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)
        }
    }
}

我正在更新适配器

musicViewModel.getAllArtists().observe(this, Observer {
            it?.let {
                artistAdapter.submitList(it)
            }
        })

我的差异课

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
    }
}

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

当我滚动列表时,它会重新渲染视图,然后依次调用 bindView()

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

有人能帮我一下吗?


该问题可能与自身ArtistsDiff的实现有关,因而也与其有关Artist
tynn

是的,我也是如此,但我似乎无法指出这一点
Veeresh Charantimath 18-4-9的

您可以调试它或添加日志语句。您也可以将相关代码添加到问题中。
tynn

也检查这个问题,我以不同的方式解决了它stackoverflow.com/questions/58232606/…–
MisterCat

Answers:


100

编辑:我明白为什么发生这种情况不是我的意思。我的观点是,它至少需要发出警告或调用该notifyDataSetChanged()函数。因为显然我在调用该submitList(...)函数是有原因的。我敢肯定,人们一直在努力找出问题出在几个小时之内,直到他们弄清楚submitList()忽略了该调用。

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

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

ListAdapter如果它不能处理同一列表中的更改,我真的不理解它的全部要点。如果要更改传递给的列表中的项目ListAdapter并查看更改,则需要创建列表的深层副本,或者需要对RecyclerView自己的DiffUtill类使用常规。


5
因为它需要先前的状态才能执行差异。当然,如果您覆盖以前的状态,它将无法处理。O_o
EpicPandaForce

29
是的,但是到那时,我有一个理由叫submitList,对吗?它至少应该调用,notifyDataSetChanged()而不是无声地忽略该调用。我敢肯定,人们一直在努力找出问题出在几个小时之前,直到他们submitList()默默地忽略了忽略的呼叫。
insa_c

5
所以我回RecyclerView.Adapter<VH>notifyDataSetChanged()。生活现在很好。浪费大量时间
Udayaditya Barua

@insa_c您可以增加3个小时,这就是我在试图理解为什么某些情况下我的列表视图没有更新的过程中浪费的时间……
Bencri

notifyDataSetChanged()是昂贵的,并且将完全挫败基于DiffUtil的实现。您可能会谨慎而专心地submitList仅调用新数据,但这实际上只是性能陷阱。
刘大卫

62

该库假定您正在使用Room或每次更新时都会提供新的异步列表的任何其他ORM,因此只需在其上调用SubmitList即可使用,并且对于草率的开发人员,如果调用相同的列表,它将阻止进行两次计算。

公认的答案是正确的,它提供了说明,但没有解决方案。

如果您不使用任何此类库,可以执行以下操作:

submitList(null);
submitList(myList);

另一个解决方案是像这样重写submitList(不会引起快速闪烁):

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

或使用Kotlin代码:

override fun submitList(list: List<CatItem>?) {
    super.submitList(list?.let { ArrayList(it) })
}

逻辑值得怀疑,但是效果很好。我的首选方法是第二种方法,因为它不会导致每一行都收到onBind调用。


4
那是骇客。只需传递列表的副本即可。.submitList(new ArrayList(list))
Paul Woitaschek '18年

2
我花了最后一个小时试图弄清楚我的逻辑问题所在。如此奇怪的逻辑。
杰里·奥卡福

7
@PaulWoitaschek这不是一个技巧,它使用的是JAVA :)它用于解决开发人员正在“睡觉”的库中的许多问题。之所以选择此列表而不是传递.submitList(new ArrayList(list)),是因为您可以在代码中的多个位置提交列表。您可能会忘记每次都创建一个新数组,这就是为什么您要重写。
RJFares '18 -10-10

1
@ Po10cio这很奇怪,主要是因为当他们这样写时,假设它只会与每次提供新列表的ORM库一起使用。如果您要传递相同的列表但已更新,则必须绕开它,这将是最好的方法
RJFares

1
即使使用Room,我也会遇到类似的问题。
宾克(Bink)

21

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

.observe(this, Observer {
            adapter.submitList(it?.toMutableList())
        })

太奇怪了,但是将列表转换为mutableList对我有用。谢谢!
Thanh-Nhon Nguyen

3
为什么这在地狱中起作用?它可以工作,但很好奇为什么会这样。
March3April4

在我看来,ListAdapter必须与列表引用无关,所以。toMutableList()会向适配器提交新的实例列表。希望对您足够清楚。@ March3April4
米娜·萨米尔

谢谢。根据您的评论,我猜想ListAdapter以List <T>的形式接收它的数据集,它可以是可变列表,甚至是不可变列表。如果我传递了一个不可变的列表,则我所做的更改将被数据集本身而不是ListAdapter阻止。
3

我认为您已经@ March3April4了,另外,请注意与diff utils一起使用的机制,因为它也有责任计算列表中的项目是否应该更改;)
Mina Samir

9

我有类似的问题,但渲染错误是由setHasFixedSize(true)和引起的android:layout_height="wrap_content"。适配器首次带有空列表,因此高度从未被更新过,而是为0。无论如何,这解决了我的问题。其他人可能有相同的问题,并且会认为这是适配器中的问题。


1
是的,将recycleview设置为wrap_content会更新列表,如果将其设置为match_parent则不会调用适配器
Exel Staderlin

5

如果使用时遇到一些问题

recycler_view.setHasFixedSize(true)

您应该最终检查此评论:https : //github.com/thoughtbot/expandable-recycler-view/issues/53#issuecomment-362991531

它解决了我这方面的问题。

(以下是根据要求提供的评论的屏幕截图)

在此处输入图片说明


欢迎使用指向解决方案的链接,但是请确保没有该链接的情况下,您的回答是有用的:在该链接周围添加上下文,以便您的其他用户可以了解它的含义和含义,然后引用您所使用页面中最相关的部分如果目标页面不可用,请重新链接。
Mostafa Arian Nejad

4

今天我也偶然发现了这个“问题”。借助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
 * /programming/49726385/listadapter-not-updating-item-in-reyclerview
 */
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)
}

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

viewModel.foundDevices.observe(this, Observer {
    binding.recyclerViewDevices.adapter.updateList(it)
})

如果列表与当前加载的列表相同,则仅(始终)复制列表。


3

根据官方文档

每当您调用SubmitList时,它都会提交一个新列表以进行差异显示。

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


2

对我来说,如果我RecyclerViewScrollViewwith内使用nestedScrollingEnabled="false"并且RV height设置为,则会出现此问题wrap_content
适配器已正确更新,并调用了bind函数,但未显示项目-RecyclerView卡住了其原始大小。

进行更改ScrollViewNestedScrollView解决此问题。


2

在我来说,我忘了设定LayoutManagerRecyclerView。其效果与上述相同。


1

对于与我的情况相同的任何人,我在这里留下我不知道为什么它起作用的解决方案。

对我有用的解决方案来自@Mina Samir,它正在将列表提交为可变列表。

我的问题方案:

-在片段中加载朋友列表。

  1. ActivityMain附加FragmentFriendList(观察到朋友数据库项的实时数据),同时,向服务器请求一个http请求以获取我的所有朋友列表。

  2. 更新或插入http服务器中的项目。

  3. 每次更改都会触发实时数据的onChanged回调。但是,当我第一次启动该应用程序时,这意味着我的表上没有任何内容,submitList成功执行,没有任何类型的错误,但是屏幕上没有任何显示。

  4. 但是,当我第二次启动该应用程序时,数据正在加载到屏幕上。

如上所述,解决方案是将列表提交为mutableList。


1

我有一个类似的问题。问题出在Diff功能上,而功能没有充分比较项目。遇到此问题的任何人,请确保您的Diff函数(并通过扩展您的数据对象类)包含正确的比较定义-即比较所有可能在新项目中更新的字段。例如在原始帖子中

    override fun areContentsTheSame(oldItem: Artist?, newItem: Artist?): Boolean {
    return oldItem == newItem
}

该函数(可能)不执行标签上的说明:不会比较两个项目的内容-除非equals()Artist类中重写了该函数。就我而言,我没有这样做,并且areContentsTheSame由于实施时的疏忽,我的定义仅检查了必填字段之一。这是结构相等与参照相等,您可以在此处找到更多信息


0

我需要修改我的DiffUtils

override fun areContentsTheSame(oldItem: Vehicle, newItem: Vehicle): Boolean {

要实际返回内容是否为新内容,而不仅仅是比较模型的ID。


0

使用@RJFares第一个答案会成功更新列表,但不会保持滚动状态。整个RecyclerView从第0个位置开始。作为解决方法,这是我所做的:

   fun updateDataList(newList:List<String>){ //new list from DB or Network

     val tempList = dataList.toMutableList() // dataList is the old list
     tempList.addAll(newList)
     listAdapter.submitList(tempList) // Recyclerview Adapter Instance
     dataList = tempList

   }

这样,我就可以保持滚动状态RecyclerView和修改后的数据。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.