浏览本文前推荐先阅读 Android入门(九)| 滚动控件 ListView 与 RecyclerView
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
中的各个控件实例化,然后进行管理,如:设置控件的点击事件等。
RecyclerView滚动控件
中的 最小子元素,比如对于布局方式为 LinearLayout
(线性布局) 的 RecyclerView
来说,child view
(子视图) 就是每一行。
我个人理解为 RecyclerView
是由 data set
的所有数据构建而成的,而每个 child view
都是由某个 data item
(数据子项) 构建而成的。
虽然 Adapter
已经将 data set
转换为了 views
,但是以怎样的布局显示这些 views
也是一个问题。因此 RecyclerView
委托 LayoutManager
负责 view
布局的显示管理。有多种布局方式供选择,如:线性布局、网格布局等。
PS:LayoutManager 只负责将 view 呈现在 Recycle 中,并不直接负责对 view 的管理,view 的管理由下面的 Recycler 负责。
管理不在前台的 View
,对 View
进行缓存,以便后续重用,避免每次都需要加载 view
,显著提高性能。LayoutManager
在需要 View
的时候会向 Recycler
进行索取,当 LayoutManager
不需要 View
(试图滑出)的时候,就直接将废弃的 View
丢给 Recycler
。
在加载布局期间已进入 临时分离(temporarily detached) 状态的子视图。 Scrap views
可以在不与 parent RecyclerView
完全分离(fully detached) 的情况下重用。 重用时需要做进一步判定是否需要修改 scrap views
:
view
被视为 dirty
,则由 适配器Adapter 进行修改。在显示之前必须由 适配器 重新绑定rebound 的 子视图child view。
调用 ViewGroup.getChildAt()
时使用的参数,已经添加到 RecyclerView
中的 子view
的索引。 与 Position
形成对比,Position
是数据的位置,Index
是视图的位置。
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 数据集(如数组等)中对应的子项 Date
Date date = DataSet.get(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
中有两种与 位置 相关的方法:
view item
在布局中的位置,这个位置是站在 LayoutManager
的角度得到的 view
的位置,也是布局更新后用户直观看到的布局。通过 getLayoutPosition()
得到。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。 |