文章目录
- 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。 |