λ:
# 仓库地址: https://github.com/lzyprime/android_demos/tree/recyclerview
git clone -b recyclerview https://github.com/lzyprime/android_demos
RecyclerView
作 Android 列表项的展示组件。相比ListView
,缓存机制做的更细致,提升流畅度。以空间换时间
两个重要参数:
LayoutManager
: 排版RecyclerView.Adapter
: 列表项获取方式
LayoutManager
LayoutManager
可以在xml
中直接配置. 也可在逻辑代码中设置。
// xml
<androidx.recyclerview.widget.RecyclerView
...
// LayoutManager类型
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
// 几栏
app:spanCount="1"
/>
全部可配参数:
1. LinearLayoutManager
public class LinearLayoutManager extends RecyclerView.LayoutManager implements
ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider
单栏线性布局。无法多栏展示。构造函数参数:
- orientation: 方向
- reverseLayout: 反转,倒序列表项
stackFromEnd 用来兼容 android.widget.AbsListView.setStackFromBottom(boolean)。相当于reverseLayout 的效果。
同时实现了ItemTouchHelper.ViewDropHandler
, RecyclerView.SmoothScroller.ScrollVectorProvider
2. GridLayoutManager
public class GridLayoutManager extends LinearLayoutManager
网格布局。LinearLayoutManager
升级版,可以通过spanCount
设置分几栏
3. StaggeredGridLayoutManager
public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager implements
RecyclerView.SmoothScroller.ScrollVectorProvider
流布局。 当列表项尺寸不一致时, GridLayoutManager
根据尺寸较大项确定网格尺寸。导致较小项会有空白部分。StaggeredGridLayoutManager
则紧凑拼接每一项。 通过 setGapStrategy(int)
设置间隙处理策略。
Adapter
RecyclerView.Adapter<VH : RecyclerView.ViewHolder>
public abstract static class Adapter<VH extends ViewHolder> {
...
@NonNull
public abstract VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType);
public abstract void onBindViewHolder(@NonNull VH holder, int position);
public abstract int getItemCount();
}
public abstract static class ViewHolder {
public ViewHolder(@NonNull View itemView) {
... }
}
一个Adapter
至少需要override
这三个函数。
getItemCount
返回列表项的个数。
onCreateViewHolder
, getItemViewType
创建一个ViewHolder
, 如果 ViewHolder
有多种类型,可以通过viewType
参数判断。 viewType
的值来自 getItemViewType(position: Int)
函数。默认返回0。 0 <= position < getItemCount()
以聊天消息为例:
sealed class Msg {
data class Text(val content: String) : Msg()
data class Image(val url: String) : Msg()
data class Video(...) : Msg()
...
}
class MsgListAdapter : RecyclerView.Adapter<MsgListAdapter.MsgViewHolder>() {
sealed class MsgViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
class Text(...) : MsgViewHolder(...)
class Image(...) : MsgViewHolder(...)
...
}
private var dataList: List<Msg> = listOf()
override fun getItemViewType(position: Int): Int =
when (dataList[position]) {
is Msg.Text -> 1
is Msg.Image -> 2
...
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MsgViewHolder =
when (viewType) {
1 -> MsgViewHolder.Text(...)
2 -> MsgViewHolder.Image(...)
...
}
}
onBindViewHolder
View
创建完成,开始绑定数据。包括事件监听注册。
class VBViewHolder<VB : ViewBinding>(private val binding : VB) : ViewHolder(binding.root) {
fun bind(data: T, onClick:() -> Unit) {
binding.data = data
...
binding.anyView.setOnClickListener {
onClick() }
...
}
}
class Adapter(private val onItemClick: () -> Unit) : RecyclerView.Adapter<VBViewHolder<XXX>>() {
override fun onBindViewHolder(holder: VBViewHolder<XXX>, position: Int) =
holder.bindHolder(dataList[position], onItemClick)
}
更新
由于缓存机制,更新完数据源, ViewHolder
也并不会立刻刷新。需要通过Adapter
的一系列方法,显式通知发生变化的列表项。
notifyDataSetChanged()
notifyItemChanged(position: Int), notifyItemChanged(position: Int, payload: Any?)
notifyItemRangeChanged(positionStart: Int, itemCount: Int), notifyItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?)
notifyItemMoved(fromPosition: Int, toPosition: Int)
notifyItemInserted(position: Int)
notifyItemRangeInserted(positionStart: Int, itemCount: Int)
notifyItemRemoved(position: Int)
notifyItemRangeRemoved(positionStart: Int, itemCount: Int)
payload: Any?
要配合 Adapter
的 onBindViewHolder(holder: VH, position: Int, payloads: MutableList<Any>)
实现 View
的局部刷新。否则,执行 onBindViewHolder(holder: VBViewHolder<VH>, position: Int)
缓存机制
主要逻辑在 RecyclerView.Recycler
。 缓存主要有 Scrap
, CachedView
, RecycledViewPool
。 ViewCacheExtension
用于额外自定义缓存。
Scrap
: 当前正在展示的部分。CachedView
: 刚划出展示区域的部分,默认最大存储DEFAULT_CACHE_SIZE = 2
。FIFO
更新RecycledViewPool
:CachedView
淘汰后,只保留ViewHolder
, 清空数据绑定。 复用时需要重新执行onBindViewHolder
。
RecycledViewPool
内部是一个SparseArray<ScrapData>
下标为 holder.viewType
。ScrapData
内嵌ArrayList<ViewHolder>
, 默认最大存储 DEFAULT_MAX_SCRAP = 5
个 ViewHolder
。 所以简化一下RecycledViewPool ~= SparseArray<ArrayList<ViewHolder>>
。
public final class Recycler {
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>()