文章目录
- Adapter、ViewHolder
- child view
- LayoutManager
- Recycler
- Scrap
- Dirty
- Index
- Position
- layout position 和 adapter position
- 四级缓存
浏览本文前推荐先阅读 Android入门(九)| 滚动控件 ListView 与 RecyclerView
Adapter、ViewHolder
Adapter: A subclass of RecyclerView.Adapter responsible for providing views that represent items in a data set.
-
翻译:RecyclerView.Adapter 的子类。
Adapter(适配器) 负责提供表示data set(数据集) 中items(子项) 的views(视图)。 -
解析:
RecyclerView只是一个ViewGroup,它只认识View,不清楚构成 前端界面View 的 后端Data数据的具体结构。因此,RecyclerView需要一个Adapter将Data转换为RecyclerView认识的ViewHolder。 -
ViewHolder: 对
view进行操作,在ViewHolder中会将view中的各个控件实例化,然后进行管理,如:设置控件的点击事件等。
child view
RecyclerView滚动控件 中的 最小子元素,比如对于布局方式为 LinearLayout(线性布局) 的 RecyclerView 来说,child view(子视图) 就是每一行。
我个人理解为 RecyclerView 是由 data set 的所有数据构建而成的,而每个 child view 都是由某个 data item(数据子项) 构建而成的。
LayoutManager
虽然 Adapter 已经将 data set 转换为了 views,但是以怎样的布局显示这些 views 也是一个问题。因此 RecyclerView 委托 LayoutManager 负责 view 布局的显示管理。有多种布局方式供选择,如:线性布局、网格布局等。
PS:LayoutManager 只负责将 view 呈现在 Recycle 中,并不直接负责对 view 的管理,view 的管理由下面的 Recycler 负责。
Recycler
管理不在前台的 View,对 View 进行缓存,以便后续重用,避免每次都需要加载 view,显著提高性能。LayoutManager 在需要 View 的时候会向 Recycler 进行索取,当 LayoutManager 不需要 View (试图滑出)的时候,就直接将废弃的 View 丢给 Recycler。
Scrap
在加载布局期间已进入 临时分离(temporarily detached) 状态的子视图。 Scrap views 可以在不与 parent RecyclerView 完全分离(fully detached) 的情况下重用。 重用时需要做进一步判定是否需要修改 scrap views:
- 如果不需要 rebinding重新绑定 则不需要修改。
- 如果该
view被视为dirty,则由 适配器Adapter 进行修改。
Dirty
在显示之前必须由 适配器 重新绑定rebound 的 子视图child view。
Index
调用 ViewGroup.getChildAt() 时使用的参数,已经添加到 RecyclerView 中的 子view 的索引。 与 Position 形成对比,Position 是数据的位置,Index 是视图的位置。
Position
Position: The position of a data item within an Adapter.
- 适配器中
data item(数据子项) 的位置。
Position 从大的方向可以分为两种情况:
- 方法
onBindViewHolder()中的参数position; - 通过
ViewHolder的getLayoutPosition()/getAdapterPosition()方法得到的layout position/adapter position。
对于第一种 positon,我们通常使用它来得到子视图,举个例子:
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {// 通过 position 获取 DataSet 数据集(如数组等)中对应的子项 DateDate date = DataSet.get(position);
}
对于第二种
layout position 和 adapter position
与 ListView 不同, RecyclerView 将 跟踪 Adapter 的工作从 RecyclerView.LayoutManager 中抽离,交给 RecyclerView.Adapter 类。ListView 是没有 “ListView.Adapter” 的,ListView 中需要用到适配器的时候,都是自定义一个 BaseAdapter类 的子类,而 RecyclerView 已经为开发者封装好了 RecyclerView.Adapter ,如此一来 RecyclerView 便能够在更新布局期间对 data set(数据集)进行批处理(等待数据修改完成再传递给布局,此等待时间小于 16 毫秒)。这可以将 LayoutManager 从跟踪 Adapter 的工作中解脱出来,而去负责 calculate animations(更新界面)的工作。这有助于提高性能,因为所有 view bindings(视图绑定) 都同时发生,并且避免了不必要的绑定。
不过这种抽象方式导致了在 RecyclerView 中有两种与 位置 相关的方法:
- layout position: 在最近一次布局更新后
view item在布局中的位置,这个位置是站在LayoutManager的角度得到的view的位置,也是布局更新后用户直观看到的布局。通过getLayoutPosition()得到。 - adapter position:
ViewHolder item在适配器中的位置,这是站在Adapter的角度得到的ViewHolder所在的位置,通常是用户单击某个ViewHolder item时,询问Adapter得到的。通过getAdapterPosition()得到。
当适配器内容改变时,并且调用 adapter.notify*方法 从 RecyclerView 请求一个新的布局。从那一刻起,新布局更新完成(此时间小于 16 毫秒),两个 position 可能不匹配,因为布局还没有反映适配器的变化。除此之外,这两个 position 在大多数时候是相等的。
getAdapterPosition() 使用时的注意事项:
-
由于调用
notifyDataSetChanged()会使所有内容无效,因此RecyclerView在更新下一个布局之前不知道ViewHolder的adapter position。在这种情况下,getAdapterPosition()将返回RecyclerView#NO_POSITION( -1)。 -
但是假设调用了
notifyItemInserted(0),先前adapter position = 0的ViewHolder调用getAdapterPosition()将立即返回adapter position = 1。因此,只要是对granular(最小粒度,指单元子项)调用notify events(应该指的是notifyItem*方法),那么即使布局尚未更新完成,也能立刻获得adapter position。 -
如果用户点击时
getAdapterPosition()返回NO_POSITION,那么最好忽略那个点击,因为不知道用户点击了什么(除非有一些其他的机制能够确认被点击的是什么,例如用于查找单元子项的稳定ID)。
四级缓存
| 缓存级别 | 详细描述 |
|---|---|
| 一级缓存 mAttachedScrap/mChangedScrap | 缓存屏幕可见范围的 ViewHolder |
| 二级缓存 mCachedViews | 按 child View 的 position 或 id 缓存滑动时即将与 RecyclerView 分离的 ViewHolder。 |
| 三级缓存 mViewCacheExtension | 开发者自行实现的缓存。 |
| 四级缓存 mRecyclerPool | ViewHolder缓存池,本质上是一个 android.util.SparseArray,其中 key 是 ViewType(int类型),value 存放的是 ArrayList< ViewHolder> ,默认每个 ArrayList 中最多存放5个 ViewHolder。 |