ListAdapter不更新recyclerview中的项目

问题描述 投票:28回答:4

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

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

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

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

有人能帮我一下吗?

android kotlin listadapter
4个回答
51
投票

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

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

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

如果它无法处理同一列表中的更改,我真的不明白这个ListAdapter的重点。如果要更改列表中的项目,请传递给ListAdapter并查看更改,然后您需要创建列表的深层副本,或者需要使用常规RecyclerView和您自己的DiffUtill类。


27
投票

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

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

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

submitList(null);
submitList(myList);

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

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

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


3
投票

我有一个类似的问题,但错误的rending是由setHasFixedSize(true)android:layout_height="wrap_content"的组合引起的。第一次为适配器提供了一个空列表,因此高度永远不会更新,并且是0。无论如何,这改变了我的问题。其他人可能会遇到同样的问题,并认为它在适配器中存在问题。


0
投票

根据官方docs

每当你调用submitList时,它都会提交一个新的列表以进行diffed和显示。 这就是为什么无论何时在上一个(已经提交的列表)上调用submitList,它都不会计算Diff,也不会通知适配器数据集中的更改。


0
投票

今天我也偶然发现了这个“问题”。在insa_c's answerRJFares's solution的帮助下,我使自己成为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
 * https://stackoverflow.com/questions/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)
})

它只会(并且始终)复制列表,如果它与当前加载的列表相同。


0
投票

如果您在使用时遇到一些问题

recycler_view.setHasFixedSize(true)

你一定要查看这个评论:qazxsw poi

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

© www.soinside.com 2019 - 2024. All rights reserved.