如何使用 LiveData 正确更新 Android 的 RecyclerView?

Posted

技术标签:

【中文标题】如何使用 LiveData 正确更新 Android 的 RecyclerView?【英文标题】:How to properly update Android's RecyclerView using LiveData? 【发布时间】:2018-11-10 18:25:58 【问题描述】:

底线问题

如果我使用MutableLiveData<List<Article>>,是否有办法在文章的标题/内容发生更改、添加新文章和删除文章时正确通知观察者?

似乎只有在 LiveData 上设置了全新的集合时才可能收到通知,这似乎会导致 UI 刷新效率非常低。

假设示例

假设以下...

我的 LiveData 类看起来像这样:

public class ArticleViewModel extends ViewModel 

    private final MutableLiveData<List<Article>> mArticles = new MutableLiveData<>();


我想使用 RecyclerView 在列表中显示文章。因此,每当我的 Fragment 观察到 ArticleViewModel 的 LiveData 发生变化时,它都会在我的自定义 RecyclerView.Adapter 类上调用以下方法:

public class ArticleRecyclerViewAdapater extends RecyclerView.Adapter<Article> 

    private final ArrayList<Article> mValues = new ArrayList<>();


    public void resetValues(Collection<Article> articles) 
        mValues.clear();
        mValues.addAll(articles);
        notifyDataSetChanged();
    

最后,我的应用程序将允许用户添加新文章、删除现有文章以及更改现有文章的名称(需要在 RecyclerView 列表中更新)。我怎样才能正确地做到这一点?

添加/删除文章

如果您从基础集合中添加/删除项目,LiveData 构造似乎不会通知观察者。看来您必须调用 LiveData#setValue,也许 ArticleViewModel 需要一个看起来像这样的方法:

public void deleteArticle(int index) 
    final List<Article> articles = mArticles.getValue();
    articles.remove(index);
    mArticles.setValue(articles);

这不是真的低效吗,因为它会触发 RecyclerView.Adapter 中的完全刷新,而不是仅仅添加/删除一行?

更改名称

如果您更改基础集合中项目的内容,LiveData 构造似乎不会通知观察者。因此,如果我想更改现有文章的标题并将其反映在 RecyclerView 中,那么我的 ArticleViewModel 将不得不修改对象并使用整个集合调用 LiveData#setValue。

同样,这不是真的低效吗,因为它会触发 RecyclerView.Adapter 中的完全刷新?

【问题讨论】:

您可以在 recyclerview 旧数据和来自观察者的新数据上使用 diffutil。这样您就不必清除底层数据集并调用 notifydatasetcahged(​​) 。 Diffutil 将处理适当的通知数据调用。 参考这个answer并将其与DiffUtil一起使用 【参考方案1】:

案例1:

当您添加或删除 因此,当您在列表中添加或删除元素时,您不会更改列表项的引用,因此每次修改 liveData 项时,您都必须通过调用 setValue 方法更新实时数据值(如果您正在更新主线程上的项目)或发布值(当您在后台线程上更新值时)

问题是效率不高

解决方案 使用 diffutil

案例 2:当您通过编辑项目来更新项目属性时。

问题

LiveData 只会在顶层值发生变化时提醒其观察者发生变化。但是,对于复杂对象,这意味着仅在对象引用发生更改时,而不是在对象的某些属性发生更改时。

解决方案

要观察属性的变化,你需要 PropertyAwareMutableLiveData

class PropertyAwareMutableLiveData<T: BaseObservable>: MutableLiveData<T>() 
private val callback = object: Observable.OnPropertyChangedCallback() 
    override fun onPropertyChanged(sender: Observable?, propertyId: Int) 
        value = value
    

override fun setValue(value: T?) 
    super.setValue(value)

    value?.addOnPropertyChangedCallback(callback)


这里需要注意两点:

1.首先我们的值是一个泛型类型,它实现了 BaseObservable 接口。这使我们可以访问 OnPropertyChangedCallback。

2.接下来是,每当 BaseObservable 的某些属性发生变化时,我们只需将顶层 value 属性重置为其当前值,从而提醒 LiveData 的观察者发生了某些变化。

【讨论】:

嗨 Roshan Kumar,欢迎您。请考虑添加更多信息,因为如果链接过时,那么您的答案质量就会非常低。【参考方案2】:

LiveData 只会在其包装对象引用发生更改时通知。当您将新的List 分配给LiveData 时,它会通知,因为它的包装对象引用已更改,但如果从LiveDataList 添加/删除项目,它不会通知,因为它仍然具有相同的List 引用为包装对象。因此,您可以通过扩展 MutableLiveData 来解决这个问题,正如另一个 *** 问题中的 here 所解释的那样。

【讨论】:

【参考方案3】:

我知道现在回答为时已晚。 但我希望它能帮助其他开发人员寻找类似问题的解决方案。

看看LiveAdapter。

你只需要在 Gradle 中添加最新的依赖即可。

dependencies 
    implementation 'com.github.RaviKoradiya:LiveAdapter:1.3.4'
    // kapt 'com.android.databinding:compiler:GRADLE_PLUGIN_VERSION' // this line only for Kotlin projects

并将适配器与您的 RecyclerView 绑定

// Kotlin sample
LiveAdapter(
        data = liveListOfItems,
        lifecycleOwner = this@MainActivity,
        variable = BR.item )
       .map<Header, ItemHeaderBinding>(R.layout.item_header) 
           onBind

           
           onClick

           
           areContentsTheSame  old: Header, new: Header ->
               return@areContentsTheSame old.text == new.text
           
           areItemSame  old: Header, new: Header ->
               return@areContentsTheSame old.text == new.text
           
       
       .map<Point, ItemPointBinding>(R.layout.item_point) 
           onBind

           
           onClick

           
           areContentsTheSame  old: Point, new: Point ->
               return@areContentsTheSame old.id == new.id
           
           areItemSame  old: Header, new: Header ->
               return@areContentsTheSame old.text == new.text
           
       
       .into(recyclerview)

就是这样。适配器实现无需额外编写代码,观察LiveData并通知适配器。

【讨论】:

以上是关于如何使用 LiveData 正确更新 Android 的 RecyclerView?的主要内容,如果未能解决你的问题,请参考以下文章

如何观察数据库的变化以更新 LiveData

为啥 LiveData 没有从协程更新?

如何使用 Room 和 LiveData 保持 RecyclerView 顺序的更改?

如何从后台服务更新 ViewModel 的 LiveData 并更新 UI

Android Room LiveData观察器未更新

jetpack-liveData 原理解析