前言
Android的RecyclerView
是一个非常强大且灵活的组件,用于在Android应用中显示动态或静态数据列表。它是ListView
和GridView
的现代替代品,提供了更好的性能和更多的定制选项。
ViewHolder
ViewHolder用于保存视图引用,每个视图项在滚动时都会被重用。
public class MyViewHolder extends RecyclerView.ViewHolder {
public TextView textView;
public MyViewHolder(View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.textview);
}
}
为什么要存在ViewHolder
ViewHolder
的主要目的是提高列表滚动的性能和流畅性。在传统的 ListView
中,每次滚动到新的列表项时,都会调用 findViewById()
来获取视图对象,这个过程是相当耗时的,特别是在处理大量数据时,因为 findViewById()
需要在视图层级中逐一查找,这对性能有较大影响。
- 视图缓存:
ViewHolder
提供了一种方式来缓存视图引用,即每个列表项的视图在初次创建时就缓存下来,之后可以直接重用视图引用而无需重新查找,大幅减少了findViewById()
的调用次数。 - 快速绑定数据:因为视图已经被缓存,所以可以快速地将数据绑定到这些视图上,而不是每次都重新创建视图。
Adapter
Adapter负责将数据绑定到ViewHolder的视图上。
public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
private String[] mDataset;
public MyAdapter(String[] myDataset) {
mDataset = myDataset;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.my_text_view, parent, false);
MyViewHolder vh = new MyViewHolder(v);
return vh;
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.textView.setText(mDataset[position]);
}
@Override
public int getItemCount() {
return mDataset.length;
}
}
Adapter
在 RecyclerView
设计中扮演着桥梁的角色,它将底层数据与 RecyclerView
显示的视图连接起来。
Adapter
管理所有需要显示在RecyclerView
中的数据。它将这些数据转换成视图,让用户可以在屏幕上看到。这种模式让视图显示逻辑和数据处理逻辑分离,更容易维护和扩展。Adapter
负责根据数据创建相应的ViewHolder
。这包括决定使用哪种类型的视图(列表、网格或自定义视图)并将数据绑定到这些视图上。- 当底层数据发生变化时,
Adapter
提供了一套方法(如notifyDataSetChanged()
,notifyItemInserted()
等)来通知RecyclerView
需要重新绘制界面。这使得数据的变更可以高效且动态地反映在用户界面上。 - 通过使用
Adapter
和ViewHolder
的组合,RecyclerView
可以有效地重用已经不在屏幕上的视图。这种重用机制减少了内存的使用和垃圾回收的频率,从而提升了应用的性能。
布局管理器
- LinearLayoutManager:显示垂直或水平滚动的列表。
- GridLayoutManager:显示二维滚动网格。
- StaggeredGridLayoutManager:显示交错网格,用于瀑布流布局。
动态数据更新
1. 数据更新 利用Adapter的notifyItemInserted()
, notifyItemRemoved()
, 和 notifyItemChanged()
方法来通知数据变更。
2. 效率优化 使用DiffUtil
类来计算数据差异,并提供更平滑的动画效果。
DiffUtil
使用一种差异算法来比较两个列表的内容差异。这个算法能够找出最小数量的修改步骤来将一个列表转换成另一个列表,这些步骤包括:
- 插入
- 移除
- 替换
创建 DiffUtil.Callback 这个回调类需要实现几个方法来判断两个列表项是否是同一项,以及同一项的内容是否发生了变化。
DiffUtil.Callback diffCallback = new DiffUtil.Callback() {
@Override
public int getOldListSize() {
return oldList.size();
}
@Override
public int getNewListSize() {
return newList.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return oldList.get(oldItemPosition).getId().equals(newList.get(newItemPosition).getId());
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return oldList.get(oldItemPosition).equals(newList.get(newItemPosition));
}
};
计算差异 使用 DiffUtil.calculateDiff()
方法计算差异。这个过程是同步进行的,因此建议在后台线程中执行,以避免阻塞主线程。
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback);
应用差异结果 在主线程中,使用 dispatchUpdatesTo()
方法将差异结果应用到适配器上,这会触发 RecyclerView
的局部更新。
diffResult.dispatchUpdatesTo(adapter);
它只更新变更的部分,而不是重新渲染整个列表。
Recycler View的缓存机制
RecyclerView
的缓存机制是设计来优化滚动性能的核心部分,通过复用和有效管理视图对象来减少创建和销毁视图的开销,RecyclerView
提供了四个不同层次的缓存,以确保滚动时的高效率和流畅性。
mAttachedScrap:缓存屏幕中可见范围的ViewHolder
mAttachedScrap
集合存储了当前屏幕上可见的ViewHolder
。这些ViewHolder
实际上已经被绑定到了其对应的数据,并且处于活跃状态,也就是说它们目前仍然附加在RecyclerView
上。
当进行数据更新(如调用notifyDataSetChanged()
)时,RecyclerView
可以快速访问这些ViewHolder
,进行必要的更新而无需重新创建或进行复杂的查找操作。
mCachedViews:缓存滑动时即将与RecyclerView分离的ViewHolder
mCachedViews
缓存了那些已经滑出屏幕,但是可能很快会再次滑入屏幕的ViewHolder
。这个缓存通常包含少量的ViewHolder
,默认情况下是最多2个。
这种缓存策略可以显著提高滚动的流畅性,尤其是在用户进行快速滑动时,减少了创建和绑定ViewHolder
的开销。
ViewCacheExtension:自定义实现的缓存
ViewCacheExtension
是一个可选的扩展,允许开发者根据自己的特定需求来实现自定义的缓存策略。
通过继承ViewCacheExtension
并实现其getViewForPositionAndType()
方法,开发者可以控制如何缓存视图。这对于处理那些有特殊缓存需求或是不常规大小的ViewHolder
特别有用。
RecycledViewPool:ViewHolder缓存池
RecycledViewPool
允许ViewHolder
跨多个RecyclerView
实例共享。它主要用于存储那些已经完全从RecyclerView
中分离出来的ViewHolder
。
此缓存池特别适合于应用中有多个RecyclerView
需要显示相似数据的情况。通过共享ViewHolder
,可以显著减少内存的使用和提高性能。