ListAdapter不会更新reyclerview中的项目

时间:2018-04-09 05:55:47

标签: android kotlin listadapter

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

有人可以帮我吗?

18 个答案:

答案 0 :(得分:39)

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

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

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

如果它无法处理同一列表中的更改,我真的不明白这个ListAdapter的重点。如果您要更改列表中的项目,请转到ListAdapter并查看更改,然后您需要创建列表的深层副本,或者需要使用常规的RecyclerView { {1}}上课。

答案 1 :(得分:20)

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

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

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

import dill as pickle

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

submitList(null);
submitList(myList);

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

答案 2 :(得分:5)

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

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

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

答案 3 :(得分:3)

我有一个类似的问题,但是错误的借出是由setHasFixedSize(true)android:layout_height="wrap_content"的组合引起的。适配器第一次提供了一个空列表,因此高度从未更新,并且为0。无论如何,这解决了我的问题。其他人可能有相同的问题,并且会认为这是适配器中的问题。

答案 4 :(得分:3)

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

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

答案 5 :(得分:2)

在我的情况下,我忘记为LayoutManager设置RecyclerView。其效果与上述相同。

答案 6 :(得分:1)

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

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

我的问题方案:

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

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

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

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

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

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

答案 7 :(得分:1)

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

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

此函数(可能)不执行标签上的说明:它不会比较两个项目的内容-除非,您在{中覆盖了equals()函数{1}}类。就我而言,我没有,Artist的定义仅检查了必填字段之一,原因是我在实施该字段时对其进行了监督。这是结构相等vs.参照相等,您可以找到更多关于here

的信息。

答案 8 :(得分:1)

你的 ListAdapter .submitlist 没有被调用的原因是因为对象 您更新的地址在内存中仍然保持不变。

当您使用 .setText 更新对象时,它会更改原始对象中的值。

这样当你检查 object.id == object2.id 时,它会返回相同的 因为两者都有对内存中相同位置的引用。

解决方案是使用更新后的数据创建一个新对象并将其插入您的列表中。然后将调用 submitList 并且它会正常工作

答案 9 :(得分:1)

它解决了我的问题。我认为最好的方法是不要覆盖 submitList 总线添加一个新函数来添加新列表。

    fun updateList(list: MutableList<ScaleDispBlock>?) {
        list?.let {
             val newList = ArrayList<ScaleDispBlock>(list)
            submitList(newList)
        }
    }

答案 10 :(得分:0)

如果在使用时遇到问题

recycler_view.setHasFixedSize(true)

您应该先检查此注释: https://github.com/thoughtbot/expandable-recycler-view/issues/53#issuecomment-362991531

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

答案 11 :(得分:0)

对于我来说,如果我在RecyclerView内使用ScrollView并且RV高度设置为nestedScrollingEnabled="false"的{​​{1}}内使用wrap_content,则会出现此问题。
适配器已正确更新,并调用了绑定函数,但未显示项目-RecyclerView停留在其原始大小。

ScrollView更改为NestedScrollView可以解决此问题。

答案 12 :(得分:0)

我需要修改DiffUtils

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

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

答案 13 :(得分: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)
})

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

答案 14 :(得分: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的滚动状态以及修改后的数据。

答案 15 :(得分:0)

根据官方docs

每当您呼叫submitList 时,它都会提交一个要区别的新列表并显示出来。

这就是为什么上一个(已提交的列表)上调用submitList 时,不计算差异的原因>,并且不通知适配器数据集中的更改。

答案 16 :(得分:0)

最佳解决方案: 对于 Kotlin

        var list :ArrayList<BaseModel> = ArrayList(adapter.currentList)
        list.add(Item("Content"))
        adapter.submitList(list) {
            Log.e("ListAdaptor","List Updated Successfully")
        }

我们不应该维护另一个基本列表,因为adapter.currentList 将返回一个已经计算了差异的列表。

由于 DiffUtil,每次列表更新时我们都必须提供一个新实例 根据安卓文档 DiffUtil 是一个实用程序类,用于计算两个列表之间的差异并输出将第一个列表转换为第二个列表的更新操作列表。 一个列表已经由 AsyncListDiffer 维护,它在后台线程上运行 diffutil,另一个必须使用 adapter.submitList()

答案 17 :(得分:0)

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

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

mViewModel.products.observe(viewLifecycleOwner, {
    mAdapter.submitList(it)
})

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

mViewModel.products.observe(viewLifecycleOwner, {
    mAdapter.submitList(it.toList())
})

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