如何借助LiveData高效更新Android RecyclerView?解决增删改刷新痛点
你遇到的确实是MutableLiveData结合集合使用时的典型痛点——默认情况下,LiveData只会在集合的引用发生变化时(也就是调用setValue/postValue传入全新集合对象)才通知观察者,而集合内部的增删改操作不会触发通知。直接全量刷新RecyclerView确实效率很低,尤其是当列表数据量大的时候,下面给你分场景讲高效的实现方案:
一、先优化RecyclerView的刷新逻辑:用ListAdapter代替普通Adapter
首先最基础的优化是把你的ArticleRecyclerViewAdapter换成ListAdapter,它内置了DiffUtil,可以自动对比新旧列表的差异,只刷新真正变化的item,哪怕你传入整个集合,也不会全量刷新。
示例代码:
public class ArticleRecyclerViewAdapter extends ListAdapter<Article, ArticleViewHolder> { // 实现DiffUtil.ItemCallback,用来判断两个item是否是同一个,以及内容是否变化 private static final DiffUtil.ItemCallback<Article> DIFF_CALLBACK = new DiffUtil.ItemCallback<Article>() { @Override public boolean areItemsTheSame(@NonNull Article oldItem, @NonNull Article newItem) { // 假设Article有唯一id,用id判断是否是同一个对象 return oldItem.getId() == newItem.getId(); } @Override public boolean areContentsTheSame(@NonNull Article oldItem, @NonNull Article newItem) { // 对比内容是否一致,比如标题、内容等 return oldItem.getTitle().equals(newItem.getTitle()) && oldItem.getContent().equals(newItem.getContent()); } }; public ArticleRecyclerViewAdapter() { super(DIFF_CALLBACK); } @NonNull @Override public ArticleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_article, parent, false); return new ArticleViewHolder(view); } @Override public void onBindViewHolder(@NonNull ArticleViewHolder holder, int position) { Article article = getItem(position); holder.titleTextView.setText(article.getTitle()); holder.contentTextView.setText(article.getContent()); } // ViewHolder类 public static class ArticleViewHolder extends RecyclerView.ViewHolder { TextView titleTextView; TextView contentTextView; public ArticleViewHolder(@NonNull View itemView) { super(itemView); titleTextView = itemView.findViewById(R.id.tv_title); contentTextView = itemView.findViewById(R.id.tv_content); } } }
之后更新数据时,只需要调用submitList方法,不需要自己维护集合:
// 在Fragment的观察者回调中 viewModel.getArticles().observe(getViewLifecycleOwner(), articles -> { adapter.submitList(articles); });
这样即使你调用mArticles.setValue(articles)传入整个集合,ListAdapter也只会刷新变化的项,效率大幅提升。
二、新增/删除Article:高效更新集合并通知LiveData
方案1:基于现有集合创建新集合(推荐配合ListAdapter)
不需要直接修改原集合,而是创建一个新的集合副本,修改后再传给LiveData,这样既触发LiveData的通知,又能让ListAdapter高效计算差异:
public class ArticleViewModel extends ViewModel { private final MutableLiveData<List<Article>> mArticles = new MutableLiveData<>(); // 初始化时给一个空集合 public ArticleViewModel() { mArticles.setValue(new ArrayList<>()); } public void addArticle(Article newArticle) { List<Article> currentList = new ArrayList<>(mArticles.getValue()); currentList.add(newArticle); mArticles.setValue(currentList); } public void deleteArticle(int index) { List<Article> currentList = new ArrayList<>(mArticles.getValue()); if (index >= 0 && index < currentList.size()) { currentList.remove(index); mArticles.setValue(currentList); } } public MutableLiveData<List<Article>> getArticles() { return mArticles; } }
这种方式逻辑简单,配合ListAdapter的DiffUtil,新增/删除只会刷新对应的item,不会全量刷新。
方案2:使用Observable集合(自定义LiveData监听集合变化)
如果你不想每次都创建新集合,可以用ObservableArrayList(AndroidX中的类),然后自定义一个LiveData来监听集合的变化,当集合内部增删元素时自动通知观察者:
public class ArticleListLiveData extends LiveData<List<Article>> { private final ObservableArrayList<Article> mObservableList = new ObservableArrayList<>(); private final ObservableList.OnListChangedCallback<ObservableArrayList<Article>> mCallback = new ObservableList.OnListChangedCallback<ObservableArrayList<Article>>() { @Override public void onChanged(ObservableArrayList<Article> sender) { setValue(sender); } @Override public void onItemRangeChanged(ObservableArrayList<Article> sender, int positionStart, int itemCount) { onChanged(sender); } @Override public void onItemRangeInserted(ObservableArrayList<Article> sender, int positionStart, int itemCount) { onChanged(sender); } @Override public void onItemRangeRemoved(ObservableArrayList<Article> sender, int positionStart, int itemCount) { onChanged(sender); } @Override public void onItemRangeMoved(ObservableArrayList<Article> sender, int fromPosition, int toPosition, int itemCount) { onChanged(sender); } }; @Override protected void onActive() { super.onActive(); mObservableList.addOnListChangedCallback(mCallback); } @Override protected void onInactive() { super.onInactive(); mObservableList.removeOnListChangedCallback(mCallback); } public void addArticle(Article article) { mObservableList.add(article); } public void deleteArticle(int index) { if (index >= 0 && index < mObservableList.size()) { mObservableList.remove(index); } } public ObservableArrayList<Article> getList() { return mObservableList; } }
然后在ViewModel中使用这个自定义LiveData:
public class ArticleViewModel extends ViewModel { private final ArticleListLiveData mArticles = new ArticleListLiveData(); public ArticleListLiveData getArticles() { return mArticles; } public void addArticle(Article newArticle) { mArticles.addArticle(newArticle); } public void deleteArticle(int index) { mArticles.deleteArticle(index); } }
这种方式不需要每次创建新集合,集合内部变化时会自动通知LiveData,同样配合ListAdapter可以高效刷新UI。
三、修改Article的标题/内容:让数据本身可观察
当你需要修改Article的属性时,有两种高效的方式:
方案1:让Article的属性用ObservableField
把Article的标题、内容换成ObservableField,这样属性变化时可以直接通知UI更新,不需要修改整个集合:
public class Article { private final int id; public final ObservableField<String> title = new ObservableField<>(); public final ObservableField<String> content = new ObservableField<>(); public Article(int id, String title, String content) { this.id = id; this.title.set(title); this.content.set(content); } public int getId() { return id; } }
然后在ViewHolder中监听属性变化:
@Override public void onBindViewHolder(@NonNull ArticleViewHolder holder, int position) { Article article = getItem(position); // 监听标题变化 article.title.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() { @Override public void onPropertyChanged(Observable sender, int propertyId) { holder.titleTextView.setText(article.title.get()); } }); // 监听内容变化 article.content.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() { @Override public void onPropertyChanged(Observable sender, int propertyId) { holder.contentTextView.setText(article.content.get()); } }); // 初始化显示 holder.titleTextView.setText(article.title.get()); holder.contentTextView.setText(article.content.get()); }
之后修改Article的标题时,直接设置article.title.set("新标题"),UI会自动更新,不需要通知LiveData或者Adapter。
方案2:修改Article后更新LiveData(配合ListAdapter)
如果不想用ObservableField,也可以修改Article的属性后,创建一个新的集合副本传给LiveData,ListAdapter会通过DiffUtil检测到内容变化,只刷新对应的item:
public void updateArticleTitle(int index, String newTitle) { List<Article> currentList = new ArrayList<>(mArticles.getValue()); if (index >= 0 && index < currentList.size()) { Article article = currentList.get(index); article.setTitle(newTitle); mArticles.setValue(currentList); } }
因为ListAdapter的areContentsTheSame会对比新旧Article的内容,所以只会刷新这个修改了的item,不会全量刷新。
总结
最推荐的组合是:ListAdapter + 创建新集合副本更新LiveData,这种方式逻辑简单,而且能保证高效的UI刷新。如果需要更细粒度的属性变化通知,可以让Article的属性使用ObservableField,这样不需要依赖LiveData的通知就能更新UI。
内容的提问来源于stack exchange,提问作者Ed LaFave

