Android RecyclerView 全接口详解 - 教程
RecyclerView 作为 Android 平台替代 ListView 的核心列表组件,凭借视图复用机制、灵活的布局管理和可定制化扩展,成为所有列表类场景的首选解决方案。它并非单一控件,而是由RecyclerView主类、LayoutManager、Adapter、ViewHolder、ItemDecoration、ItemAnimator等组件构成的完整体系。本文将逐模块拆解每个组件的核心接口,从方法定义、参数含义到使用场景,结合代码示例实现「接口全覆盖 + 实战落地」,帮助开发者彻底掌握 RecyclerView 的所有关键能力。
一、RecyclerView 核心概念与初始化
1.1 核心优势与适用场景
- 视图复用:通过ViewHolder复用 item 视图,避免频繁findViewById和视图创建,性能比 ListView 提升 30%+
- 布局灵活:支持线性、网格、瀑布流等多种布局,且可自定义布局规则
- 扩展能力强:通过ItemDecoration、ItemAnimator轻松实现分割线、item 动画
- 滚动优化:内置滚动监听、预加载支持,适配大数据量列表
1.2 基础依赖与布局引入
RecyclerView 属于 AndroidX 库,需先添加依赖(无需额外引入,AndroidX 项目默认包含):
// 若未自动引入,在模块级build.gradle添加
implementation 'androidx.recyclerview:recyclerview:1.3.2'
布局文件中引入 RecyclerView:
二、RecyclerView 主类核心接口
RecyclerView类是列表容器的核心,提供布局绑定、 Adapter 设置、滚动控制等基础能力,以下是所有常用接口的详细解析:
2.1 构造方法与初始化
RecyclerView 提供 3 个构造方法,分别对应「代码创建」「XML 加载」「XML 加载 + 主题属性」场景:
// 1. 代码创建(无属性)
fun RecyclerView(context: Context)
// 2. XML加载(带属性)
fun RecyclerView(context: Context, attrs: AttributeSet?)
// 3. XML加载(带属性+主题)
fun RecyclerView(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
// 初始化示例(Activity中)
val rvList: RecyclerView = findViewById(R.id.rv_list)
// 或ViewBinding方式
val binding = ActivityRecyclerBinding.inflate(layoutInflater)
val rvList = binding.rvList
2.2 核心配置接口
2.2.1 设置布局管理器(必选)
RecyclerView 本身不处理布局,需通过setLayoutManager绑定LayoutManager(线性 / 网格 / 瀑布流),无默认值,必须手动设置:
/**
* @param layoutManager 布局管理器实例(LinearLayoutManager/GridLayoutManager等)
*/
fun setLayoutManager(layoutManager: LayoutManager?)
// 示例1:线性布局(垂直)
val linearLayoutManager = LinearLayoutManager(this)
linearLayoutManager.orientation = LinearLayoutManager.VERTICAL // 默认为垂直
rvList.setLayoutManager(linearLayoutManager)
// 示例2:网格布局(2列)
val gridLayoutManager = GridLayoutManager(this, 2) // 第二个参数为列数
rvList.setLayoutManager(gridLayoutManager)
// 示例3:瀑布流布局(2列,垂直方向)
val staggeredLayoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
rvList.setLayoutManager(staggeredLayoutManager)
2.2.2 设置 Adapter(必选)
Adapter 是「数据→视图」的桥梁,通过setAdapter绑定自定义 Adapter,无默认值,必须手动设置:
/**
* @param adapter 自定义Adapter实例,继承自RecyclerView.Adapter
*/
fun setAdapter(adapter: Adapter?)
// 示例:绑定自定义Adapter
val myAdapter = MyRecyclerAdapter(dataList)
rvList.setAdapter(myAdapter)
2.2.3 添加 Item 装饰(可选)
ItemDecoration用于绘制 item 间的分割线、间距或额外视图(如悬浮标题),支持添加多个装饰:
/**
* 添加单个ItemDecoration
* @param decor 装饰实例,继承自RecyclerView.ItemDecoration
*/
fun addItemDecoration(decor: ItemDecoration)
/**
* 添加单个ItemDecoration,并指定层级(影响绘制顺序)
* @param decor 装饰实例
* @param index 层级索引(0为最底层,越大约靠上)
*/
fun addItemDecoration(decor: ItemDecoration, index: Int)
/**
* 移除单个ItemDecoration
*/
fun removeItemDecoration(decor: ItemDecoration)
/**
* 移除所有ItemDecoration
*/
fun removeItemDecorations()
// 示例1:使用系统默认分割线(仅线性布局生效)
val divider = DividerItemDecoration(this, LinearLayoutManager.VERTICAL)
rvList.addItemDecoration(divider)
// 示例2:添加自定义分割线(后续章节详解)
rvList.addItemDecoration(CustomDividerDecoration())
2.2.4 设置 Item 动画(可选)
ItemAnimator用于处理 item 增删改时的动画(如插入时的渐入、删除时的渐出),默认使用SimpleItemAnimator:
/**
* 设置Item动画器
* @param animator 动画器实例,继承自RecyclerView.ItemAnimator
*/
fun setItemAnimator(animator: ItemAnimator?)
// 示例1:使用默认动画
rvList.setItemAnimator(SimpleItemAnimator())
// 示例2:使用Google官方扩展的动画(需额外依赖)
implementation 'androidx.recyclerview:recyclerview-selection:1.2.0'
val defaultItemAnimator = DefaultItemAnimator()
defaultItemAnimator.addDuration = 300 // 动画时长(毫秒)
rvList.setItemAnimator(defaultItemAnimator)
2.2.5 设置滚动监听
监听 RecyclerView 的滚动状态和滚动距离,常用于「滑动加载更多」「悬浮导航栏」等场景:
/**
* 添加滚动监听器
* @param listener 监听器实例,实现RecyclerView.OnScrollListener
*/
fun addOnScrollListener(listener: OnScrollListener)
/**
* 移除滚动监听器
*/
fun removeOnScrollListener(listener: OnScrollListener)
/**
* 移除所有滚动监听器
*/
fun clearOnScrollListeners()
// 示例:实现滑动加载更多
rvList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
// 滚动状态变化时回调
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
// newState取值:
// SCROLL_STATE_IDLE:滚动停止
// SCROLL_STATE_DRAGGING:手指拖拽滚动
// SCROLL_STATE_SETTLING:惯性滚动(手指离开后继续滚动)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
// 获取最后一个可见item的位置
val lastVisiblePos = layoutManager.findLastVisibleItemPosition()
// 若最后一个可见item是列表末尾,触发加载更多
if (lastVisiblePos == recyclerView.adapter?.itemCount?.minus(1)) {
loadMoreData()
}
}
}
// 滚动时持续回调(频率高,避免耗时操作)
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
// dx:水平滚动距离(正值向右,负值向左)
// dy:垂直滚动距离(正值向下,负值向上)
// 示例:根据滚动距离控制悬浮按钮显示/隐藏
if (dy > 100) {
hideFloatButton()
} else if (dy < -50) {
showFloatButton()
}
}
})
2.3 滚动控制接口
RecyclerView 提供多种滚动方法,支持精确控制列表滚动位置:
/**
* 滚动到指定位置(平滑滚动,带动画)
* @param position 目标item的Adapter位置
*/
fun smoothScrollToPosition(position: Int)
/**
* 滚动到指定位置(瞬间跳转,无动画)
* @param position 目标item的Adapter位置
*/
fun scrollToPosition(position: Int)
/**
* 滚动到指定位置(带偏移量,需LayoutManager支持)
* @param position 目标item的Adapter位置
* @param offset 偏移量(单位:像素,正值向下偏移)
*/
fun scrollToPositionWithOffset(position: Int, offset: Int)
// 示例1:平滑滚动到第10个item
rvList.smoothScrollToPosition(9)
// 示例2:瞬间跳转到第5个item,且偏移100像素
rvList.scrollToPositionWithOffset(4, 100)
2.4 其他常用接口
/**
* 获取当前绑定的Adapter
*/
fun getAdapter(): Adapter?
/**
* 获取当前绑定的LayoutManager
*/
fun getLayoutManager(): LayoutManager?
/**
* 强制刷新列表(不推荐,建议用Adapter的notify方法)
*/
fun invalidate()
/**
* 设置是否允许滚动
* @param enabled true:允许滚动,false:禁止滚动
*/
fun setScrollEnabled(enabled: Boolean)
/**
* 获取可见item的位置范围(需LayoutManager支持)
*/
fun getVisibleItemPositions(): IntArray {
val layoutManager = layoutManager as LinearLayoutManager
val firstVisible = layoutManager.findFirstVisibleItemPosition()
val lastVisible = layoutManager.findLastVisibleItemPosition()
return IntArray(lastVisible - firstVisible + 1) { firstVisible + it }
}
三、LayoutManager 核心接口
LayoutManager是 RecyclerView 的「布局大脑」,负责 item 视图的测量、布局和回收,Android 提供 3 种默认实现,同时支持自定义。以下详解每种 LayoutManager 的核心接口。
3.1 基类 LayoutManager 核心接口
所有 LayoutManager 都继承自RecyclerView.LayoutManager,基类提供以下核心抽象方法和工具方法:
3.1.1 抽象方法(必须重写)
自定义 LayoutManager 时必须实现以下方法,控制 item 的布局逻辑:
/**
* 测量并布局所有可见item
* @param recycler 用于获取item视图的Recycler
* @param state 当前RecyclerView的状态(如item数量、滚动状态)
*/
abstract fun onLayoutChildren(recycler: Recycler, state: State)
/**
* 判断布局是否支持水平滚动
* @return true:支持水平滚动,false:不支持
*/
abstract fun canScrollHorizontally(): Boolean
/**
* 判断布局是否支持垂直滚动
* @return true:支持垂直滚动,false:不支持
*/
abstract fun canScrollVertically(): Boolean
/**
* 处理水平滚动(canScrollHorizontally返回true时生效)
* @param dx 滚动距离(正值向右,负值向左)
* @return 实际滚动的距离
*/
abstract fun scrollHorizontallyBy(dx: Int, recycler: Recycler, state: State): Int
/**
* 处理垂直滚动(canScrollVertically返回true时生效)
* @param dy 滚动距离(正值向下,负值向上)
* @return 实际滚动的距离
*/
abstract fun scrollVerticallyBy(dy: Int, recycler: Recycler, state: State): Int
3.1.2 常用工具方法
基类提供的工具方法,用于辅助布局和回收:
/**
* 获取item的默认布局参数
* @return 默认参数(如宽高match_parent)
*/
fun generateDefaultLayoutParams(): RecyclerView.LayoutParams
/**
* 将item视图添加到RecyclerView中
* @param child 待添加的item视图
*/
fun addView(child: View)
/**
* 将item视图添加到指定位置
* @param child 待添加的item视图
* @param index 位置索引
*/
fun addView(child: View, index: Int)
/**
* 移除指定item视图
* @param child 待移除的item视图
*/
fun removeView(child: View)
/**
* 回收所有不可见的item视图(复用核心逻辑)
* @param recycler 用于回收的Recycler
*/
fun recycleChildrenOnDetach(recycler: Recycler)
3.2 LinearLayoutManager(线性布局)
LinearLayoutManager实现线性布局(垂直 / 水平),是最常用的 LayoutManager,核心接口如下:
3.2.1 构造方法
/**
* 构造方法1:上下文+方向
* @param context 上下文
* @param orientation 方向(LinearLayoutManager.VERTICAL/LinearLayoutManager.HORIZONTAL)
*/
fun LinearLayoutManager(context: Context, orientation: Int)
/**
* 构造方法2:上下文+方向+是否反向布局(如倒序列表)
* @param reverseLayout true:反向布局(从底部/右侧开始),false:正向布局
*/
fun LinearLayoutManager(context: Context, orientation: Int, reverseLayout: Boolean)
// 示例:水平反向布局(从右向左滚动)
val horizontalReverseLayout = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, true)
3.2.2 核心配置方法
/**
* 设置布局方向
* @param orientation 方向(VERTICAL/HORIZONTAL)
*/
fun setOrientation(orientation: Int)
/**
* 设置是否反向布局
* @param reverseLayout true:反向,false:正向
*/
fun setReverseLayout(reverseLayout: Boolean)
/**
* 设置滚动到指定位置时的对齐方式(仅smoothScrollToPosition生效)
* @param snapPreference 对齐方式:
* - SNAP_TO_START:对齐item顶部/左侧
* - SNAP_TO_END:对齐item底部/右侧
* - SNAP_TO_CENTER:对齐item中心
*/
fun setSmoothScrollbarEnabled(snapPreference: Int)
// 示例:设置平滑滚动时对齐item中心
linearLayoutManager.snapPreference = LinearLayoutManager.SNAP_TO_CENTER
3.2.3 位置查询方法
/**
* 获取第一个可见item的Adapter位置
*/
fun findFirstVisibleItemPosition(): Int
/**
* 获取第一个完全可见item的Adapter位置(item完全在屏幕内)
*/
fun findFirstCompletelyVisibleItemPosition(): Int
/**
* 获取最后一个可见item的Adapter位置
*/
fun findLastVisibleItemPosition(): Int
/**
* 获取最后一个完全可见item的Adapter位置
*/
fun findLastCompletelyVisibleItemPosition(): Int
// 示例:判断第5个item是否完全可见
val isFifthVisible = linearLayoutManager.findFirstCompletelyVisibleItemPosition() = 4
3.3 GridLayoutManager(网格布局)
GridLayoutManager实现网格布局,在 LinearLayoutManager 基础上扩展了「列数控制」「跨列配置」等能力:
3.3.1 构造方法
/**
* 构造方法1:上下文+列数
* @param context 上下文
* @param spanCount 列数(水平布局时为行数)
*/
fun GridLayoutManager(context: Context, spanCount: Int)
/**
* 构造方法2:上下文+列数+方向+是否反向
*/
fun GridLayoutManager(context: Context, spanCount: Int, orientation: Int, reverseLayout: Boolean)
// 示例:3列垂直网格布局
val gridLayoutManager = GridLayoutManager(this, 3)
3.3.2 核心配置方法
/**
* 设置列数(水平布局时为行数)
* @param spanCount 列数/行数
*/
fun setSpanCount(spanCount: Int)
/**
* 设置跨列规则(如某item占2列)
* @param spanSizeLookup 跨列规则回调
*/
fun setSpanSizeLookup(spanSizeLookup: SpanSizeLookup)
// 示例:第0个item占3列(整行),其他item占1列
gridLayoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return if (position == 0) 3 else 1 // spanCount=3时,3表示占满一行
}
}
/**
* 设置是否允许同列item高度自适应(垂直网格)
* @param autoMeasureEnabled true:自适应,false:强制同列高度一致
*/
fun setAutoMeasureEnabled(autoMeasureEnabled: Boolean)
3.4 StaggeredGridLayoutManager(瀑布流布局)
StaggeredGridLayoutManager实现瀑布流布局(item 高度 / 宽度不一致),核心接口如下:
3.4.1 构造方法
/**
* 构造方法:列数+方向
* @param spanCount 列数(垂直瀑布流)/行数(水平瀑布流)
* @param orientation 方向(VERTICAL/HORIZONTAL)
*/
fun StaggeredGridLayoutManager(spanCount: Int, orientation: Int)
// 示例:2列垂直瀑布流
val staggeredLayoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
3.4.2 核心配置方法
/**
* 设置列数/行数
*/
fun setSpanCount(spanCount: Int)
/**
* 设置滚动到指定位置时的对齐方式
* @param snapPreference 对齐方式:
* - SNAP_TO_START:对齐item顶部/左侧
* - SNAP_TO_END:对齐item底部/右侧
* - SNAP_TO_CENTER:对齐item中心
*/
fun setSnapPreference(snapPreference: Int)
/**
* 设置是否启用Gap策略(减少item间的空白间隙)
* @param gapStrategy 策略:
* - GAP_HANDLING_NONE:不处理间隙
* - GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS:自动调整item位置减少间隙
*/
fun setGapStrategy(gapStrategy: Int)
// 示例:启用间隙优化
staggeredLayoutManager.gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
3.4.3 位置查询方法
瀑布流布局的 item 分布在多列,需通过数组获取每列的可见位置:
/**
* 获取每列第一个可见item的位置
* @param into 存储结果的数组(长度=spanCount)
*/
fun findFirstVisibleItemPositions(into: IntArray): IntArray
/**
* 获取每列第一个完全可见item的位置
*/
fun findFirstCompletelyVisibleItemPositions(into: IntArray): IntArray
/**
* 获取每列最后一个可见item的位置
*/
fun findLastVisibleItemPositions(into: IntArray): IntArray
// 示例:获取瀑布流最后一个可见item的位置
val lastVisiblePositions = IntArray(staggeredLayoutManager.spanCount)
staggeredLayoutManager.findLastVisibleItemPositions(lastVisiblePositions)
val lastPosition = lastVisiblePositions.maxOrNull() ?: 0 // 取最大位置(列表末尾)
四、Adapter 与 ViewHolder 核心接口
Adapter和ViewHolder是 RecyclerView「数据绑定」的核心,Adapter 负责数据管理和 ViewHolder 创建,ViewHolder 负责 item 视图的持有和复用。
4.1 ViewHolder 核心接口
ViewHolder是 item 视图的「容器」,存储 item 的子视图引用,避免重复findViewById,核心接口如下:
4.1.1 构造方法
/**
* 构造方法:持有item根视图
* @param itemView item的根视图(通过LayoutInflater加载)
*/
open class ViewHolder(val itemView: View)
// 示例:自定义ViewHolder(持有item的子视图)
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
// 持有子视图引用(仅初始化一次,复用时有值)
val tvTitle: TextView = itemView.findViewById(R.id.tv_title)
val ivIcon: ImageView = itemView.findViewById(R.id.iv_icon)
val btnAction: Button = itemView.findViewById(R.id.btn_action)
}
4.1.2 常用方法
/**
* 获取当前ViewHolder对应的Adapter位置
* @return Adapter位置(若item被删除,可能返回RecyclerView.NO_POSITION)
*/
fun getAdapterPosition(): Int
/**
* 获取当前ViewHolder对应的布局位置(与屏幕显示一致)
* @return 布局位置(比getAdapterPosition更稳定,推荐使用)
*/
fun getLayoutPosition(): Int
/**
* 设置当前ViewHolder是否允许复用
* @param recyclable true:允许复用(默认),false:禁止复用(如编辑中的item)
*/
fun setIsRecyclable(recyclable: Boolean)
/**
* 判断当前ViewHolder是否处于活跃状态(可见且未被回收)
*/
fun isActive(): Boolean
// 示例:禁止编辑中的item被复用
itemView.btnEdit.setOnClickListener {
if (isEditing) {
holder.setIsRecyclable(false)
} else {
holder.setIsRecyclable(true)
}
}
4.2 Adapter 核心接口
RecyclerView.Adapter是抽象类,必须重写 3 个核心方法,同时提供数据更新、视图复用等能力,以下是完整接口解析:
4.2.1 抽象方法(必须重写)
/**
* 创建ViewHolder(初始化item视图,仅在需要新视图时调用)
* @param parent 父容器(RecyclerView)
* @param viewType item类型(用于多类型item)
* @return 自定义ViewHolder实例
*/
abstract fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH
/**
* 绑定ViewHolder(将数据绑定到item视图,复用视图时频繁调用)
* @param holder 待绑定的ViewHolder
* @param position 当前item的Adapter位置
*/
abstract fun onBindViewHolder(holder: VH, position: Int)
/**
* 获取item总数
* @return 数据列表的长度
*/
abstract fun getItemCount(): Int
// 示例:基础Adapter实现
class MyRecyclerAdapter(private val dataList: List) :
RecyclerView.Adapter() {
// 1. 创建ViewHolder
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
// 加载item布局
val itemView = LayoutInflater.from(parent.context)
.inflate(R.layout.item_recycler, parent, false) // 第三个参数必须为false
return MyViewHolder(itemView)
}
// 2. 绑定数据
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val data = dataList[position]
// 将数据绑定到ViewHolder的子视图
holder.tvTitle.text = data.title
holder.ivIcon.load(data.iconUrl) // 使用Coil/Glide加载图片
holder.btnAction.setOnClickListener {
onActionClickListener?.onClick(position, data)
}
}
// 3. 获取item总数
override fun getItemCount(): Int = dataList.size
// 自定义ViewHolder
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val tvTitle: TextView = itemView.findViewById(R.id.tv_title)
val ivIcon: ImageView = itemView.findViewById(R.id.iv_icon)
val btnAction: Button = itemView.findViewById(R.id.btn_action)
}
// 点击事件回调(可选)
private var onActionClickListener: OnActionClickListener? = null
fun setOnActionClickListener(listener: OnActionClickListener) {
this.onActionClickListener = listener
}
interface OnActionClickListener {
fun onClick(position: Int, data: MyData)
}
}
4.2.2 多类型 item 接口
当列表包含多种 item 类型(如文字 item、图片 item)时,需重写以下方法:
/**
* 获取指定位置的item类型(默认返回0,即单一类型)
* @param position 当前item的Adapter位置
* @return item类型标识(整数,自定义)
*/
open fun getItemViewType(position: Int): Int
// 示例:多类型item实现
override fun getItemViewType(position: Int): Int {
return when (dataList[position].type) {
MyData.TYPE_TEXT -> 0 // 文字类型
MyData.TYPE_IMAGE -> 1 // 图片类型
else -> 2 // 其他类型
}
}
// 对应onCreateViewHolder需根据viewType加载不同布局
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val itemView = when (viewType) {
0 -> LayoutInflater.from(parent.context).inflate(R.layout.item_text, parent, false)
1 -> LayoutInflater.from(parent.context).inflate(R.layout.item_image, parent, false)
else -> LayoutInflater.from(parent.context).inflate(R.layout.item_other, parent, false)
}
return MyViewHolder(itemView)
}
4.2.3 数据更新接口
Adapter 提供notify系列方法,用于通知 RecyclerView 数据变化,严禁直接调用notifyDataSetChanged(会刷新整个列表,性能差),推荐使用精准更新方法:
方法 | 作用 | 适用场景 |
notifyDataSetChanged() | 刷新整个列表 | 数据全量替换,无法确定具体变化 |
notifyItemChanged(position) | 刷新指定位置 item | 单个 item 数据更新 |
notifyItemChanged(position, payload) | 局部刷新指定 item | 仅更新 item 的部分视图(如点赞状态) |
notifyItemInserted(position) | 通知插入新 item | 在指定位置添加 item(带动画) |
notifyItemRemoved(position) | 通知删除 item | 移除指定位置 item(带动画) |
notifyItemRangeChanged(startPos, itemCount) | 刷新指定范围 item | 连续多个 item 数据更新 |
notifyItemRangeInserted(startPos, itemCount) | 通知插入多个 item | 连续添加多个 item |
notifyItemRangeRemoved(startPos, itemCount) | 通知删除多个 item | 连续移除多个 item |
notifyItemMoved(fromPos, toPos) | 通知 item 位置移动 | item 在列表中移动(如拖拽排序) |
示例 1:局部刷新(仅更新点赞状态)
// 1. 定义payload标识
private const val PAYLOAD_LIKE = "payload_like"
// 2. 调用带payload的notify方法
fun updateLikeStatus(position: Int) {
dataList[position].isLiked = !dataList[position].isLiked
notifyItemChanged(position, PAYLOAD_LIKE) // 仅传递点赞状态变化的payload
}
// 3. 在onBindViewHolder中处理payload
override fun onBindViewHolder(holder: MyViewHolder, position: Int, payloads: MutableList) {
if (payloads.isEmpty()) {
// 无payload,全量绑定数据
super.onBindViewHolder(holder, position, payloads)
} else {
// 有payload,局部刷新
payloads.forEach { payload ->
if (payload == PAYLOAD_LIKE) {
val isLiked = dataList[position].isLiked
holder.ivLike.setImageResource(if (isLiked) R.drawable.ic_liked else R.drawable.ic_unlike)
}
}
}
}
示例 2:插入新 item
fun addItem(position: Int, data: MyData) {
dataList.add(position, data)
notifyItemInserted(position) // 带动画插入,比notifyDataSetChanged高效
// 可选:滚动到新插入的item
rvList.smoothScrollToPosition(position)
}
4.2.4 视图回收与状态保存接口
Adapter 提供以下方法,用于在 ViewHolder 回收 / 复用前处理资源释放或状态保存:
/**
* ViewHolder被回收时调用(item滚出屏幕)
* @param holder 被回收的ViewHolder
*/
open fun onViewRecycled(holder: VH)
/**
* ViewHolder复用失败时调用(如setIsRecyclable(false)的item)
* @param holder 复用失败的ViewHolder
* @return true:允许回收,false:禁止回收
*/
open fun onFailedToRecycleView(holder: VH): Boolean
/**
* ViewHolder即将显示时调用(item滚入屏幕)
* @param holder 即将显示的ViewHolder
*/
open fun onViewAttachedToWindow(holder: VH)
/**
* ViewHolder即将隐藏时调用(item滚出屏幕)
* @param holder 即将隐藏的ViewHolder
*/
open fun onViewDetachedFromWindow(holder: VH)
// 示例:回收时释放图片资源(避免内存泄漏)
override fun onViewRecycled(holder: MyViewHolder) {
super.onViewRecycled(holder)
// 取消Glide/Coil的图片加载请求
Glide.with(holder.itemView.context).clear(holder.ivIcon)
// 重置按钮状态
holder.btnAction.isEnabled = true
}
4.2.5 稳定 ID 接口
当 item 的位置变化但唯一 ID 不变时(如列表排序),可通过稳定 ID 避免视图复用错误:
/**
* 设置是否启用稳定ID
* @param hasStableIds true:启用,false:禁用(默认)
*/
fun setHasStableIds(hasStableIds: Boolean)
/**
* 获取item的稳定ID(启用hasStableIds后必须重写)
* @param position 当前item的Adapter位置
* @return item的唯一ID(如数据的id字段)
*/
override fun getItemId(position: Int): Long
// 示例:启用稳定ID
init {
setHasStableIds(true) // 必须在setAdapter前调用
}
override fun getItemId(position: Int): Long {
return dataList[position].id.toLong() // 使用数据的唯一ID作为稳定ID
}
五、ItemDecoration 核心接口
ItemDecoration用于为 item 添加「额外视图」或「间距」,如分割线、分组标题、悬浮导航等,核心是 3 个抽象方法,以下详解其用法和实现。
5.1 核心抽象方法
所有自定义 ItemDecoration 都需继承RecyclerView.ItemDecoration,并重写以下方法:
5.1.1 getItemOffsets(设置间距)
用于为 item 设置四周的间距(类似 margin),不会绘制内容,仅预留空间:
/**
* 设置item的间距
* @param outRect 存储间距的矩形(left/top/right/bottom)
* @param view 当前item的视图
* @param parent RecyclerView实例
* @param state RecyclerView状态
*/
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val position = parent.getChildAdapterPosition(view)
// 示例1:所有item顶部间距10dp
outRect.top = 10.dpToPx()
// 示例2:最后一个item底部间距20dp
if (position == parent.adapter?.itemCount?.minus(1)) {
outRect.bottom = 20.dpToPx()
}
// 示例3:网格布局中,左右间距5dp
val layoutManager = parent.layoutManager as GridLayoutManager
outRect.left = 5.dpToPx()
outRect.right = 5.dpToPx()
}
// DP转PX工具方法
fun Int.dpToPx(): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
this.toFloat(),
Resources.getSystem().displayMetrics
).toInt()
}
5.1.2 onDraw(绘制背景装饰)
在 item 视图下方绘制装饰内容(如分割线、背景色),绘制顺序早于 item 视图:
/**
* 在item下方绘制装饰
* @param c 画布(Canvas)
* @param parent RecyclerView实例
* @param state RecyclerView状态
*/
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDraw(c, parent, state)
val dividerHeight = 1.dpToPx() // 分割线高度
val dividerPaint = Paint().apply {
color = Color.parseColor("#E5E5E5") // 分割线颜色
strokeWidth = dividerHeight.toFloat()
}
// 遍历所有可见item,绘制分割线
for (i in 0 until parent.childCount) {
val child = parent.getChildAt(i)
val position = parent.getChildAdapterPosition(child)
// 最后一个item不绘制分割线
if (position == parent.adapter?.itemCount?.minus(1)) continue
// 计算分割线位置(item底部)
val dividerTop = child.bottom.toFloat()
val dividerBottom = dividerTop + dividerHeight
val dividerLeft = parent.paddingLeft.toFloat()
val dividerRight = (parent.width - parent.paddingRight).toFloat()
// 绘制分割线
c.drawLine(dividerLeft, dividerTop, dividerRight, dividerBottom, dividerPaint)
}
}
5.1.3 onDrawOver(绘制前景装饰)
在 item 视图上方绘制装饰内容(如悬浮标题、小红点),绘制顺序晚于 item 视图:
/**
* 在item上方绘制装饰
* @param c 画布(Canvas)
* @param parent RecyclerView实例
* @param state RecyclerView状态
*/
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDrawOver(c, parent, state)
val layoutManager = parent.layoutManager as LinearLayoutManager
val firstVisiblePos = layoutManager.findFirstVisibleItemPosition()
if (firstVisiblePos == RecyclerView.NO_POSITION) return
// 示例:绘制悬浮分组标题(如联系人列表的A/B/C分组)
val groupName = getGroupName(firstVisiblePos) // 自定义方法:根据位置获取分组名
val groupPaint = Paint().apply {
color = Color.WHITE
textSize = 16.spToPx().toFloat()
isAntiAlias = true
}
val backgroundPaint = Paint().apply {
color = Color.parseColor("#F5F5F5")
}
// 计算标题位置(屏幕顶部)
val titleWidth = groupPaint.measureText(groupName)
val titleHeight = groupPaint.textSize
val backgroundTop = 0f
val backgroundBottom = titleHeight + 10.dpToPx()
val backgroundLeft = 0f
val backgroundRight = parent.width.toFloat()
val titleX = 16.dpToPx().toFloat()
val titleY = backgroundBottom - 5.dpToPx()
// 绘制标题背景
c.drawRect(backgroundLeft, backgroundTop, backgroundRight, backgroundBottom, backgroundPaint)
// 绘制标题文字
c.drawText(groupName, titleX, titleY, groupPaint)
}
// SP转PX工具方法
fun Int.spToPx(): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,
this.toFloat(),
Resources.getSystem().displayMetrics
).toInt()
}
5.2 系统默认 ItemDecoration
Android 提供DividerItemDecoration,用于快速实现线性布局的分割线,无需自定义:
/**
* 构造方法1:上下文+方向
* @param context 上下文
* @param orientation 方向(VERTICAL/HORIZONTAL)
*/
fun DividerItemDecoration(context: Context, orientation: Int)
// 示例:垂直线性布局的默认分割线
val divider = DividerItemDecoration(this, LinearLayoutManager.VERTICAL)
// 自定义分割线图片(可选)
divider.setDrawable(ContextCompat.getDrawable(this, R.drawable.divider_custom)!!)
rvList.addItemDecoration(divider)
六、ItemAnimator 核心接口
ItemAnimator用于处理 item 增删改时的动画效果,Android 提供SimpleItemAnimator和DefaultItemAnimator(Google 扩展库),以下详解其核心接口和自定义方法。
6.1 基类 ItemAnimator 核心接口
所有 ItemAnimator 都继承自RecyclerView.ItemAnimator,基类提供以下抽象方法:
/**
* 判断是否支持item动画
* @return true:支持,false:不支持
*/
abstract fun animateAdd(holder: ViewHolder): Boolean
/**
* 判断是否支持item删除动画
* @return true:支持,false:不支持
*/
abstract fun animateRemove(holder: ViewHolder): Boolean
/**
* 判断是否支持item移动动画
* @return true:支持,false:不支持
*/
abstract fun animateMove(holder: ViewHolder, fromX: Int, fromY: Int, toX: Int, toY: Int): Boolean
/**
* 判断是否支持item更新动画
* @return true:支持,false:不支持
*/
abstract fun animateChange(oldHolder: ViewHolder, newHolder: ViewHolder): Boolean
/**
* 结束所有动画
*/
abstract fun endAnimations()
/**
* 判断是否有动画正在执行
* @return true:有动画,false:无动画
*/
abstract fun isRunning(): Boolean
6.2 常用默认实现:DefaultItemAnimator
DefaultItemAnimator是 Google 提供的扩展动画器,支持增删改移的默认动画,核心配置方法:
/**
* 设置添加动画时长(毫秒)
*/
var addDuration: Long
/**
* 设置删除动画时长(毫秒)
*/
var removeDuration: Long
/**
* 设置移动动画时长(毫秒)
*/
var moveDuration: Long
/**
* 设置更新动画时长(毫秒)
*/
var changeDuration: Long
/**
* 设置是否支持动画叠加(多个item同时动画)
*/
var supportsChangeAnimations: Boolean
// 示例:配置DefaultItemAnimator
val defaultAnimator = DefaultItemAnimator()
defaultAnimator.addDuration = 300
defaultAnimator.removeDuration = 300
defaultAnimator.supportsChangeAnimations = true // 启用更新动画
rvList.setItemAnimator(defaultAnimator)
6.3 自定义 ItemAnimator(示例:渐入渐出动画)
通过继承SimpleItemAnimator实现自定义动画:
class FadeItemAnimator : SimpleItemAnimator() {
private val pendingAnimations = ArrayList()
// 实现添加动画(渐入)
override fun animateAdd(holder: ViewHolder): Boolean {
holder.itemView.alpha = 0f // 初始透明度0(完全透明)
pendingAnimations.add(holder)
return true
}
// 实现删除动画(渐出)
override fun animateRemove(holder: ViewHolder): Boolean {
pendingAnimations.add(holder)
return true
}
// 执行动画
override fun runPendingAnimations() {
if (pendingAnimations.isEmpty()) return
for (holder in pendingAnimations) {
when {
isRemoving(holder) -> {
// 删除动画:渐出
holder.itemView.animate()
.alpha(0f)
.setDuration(300)
.withEndAction {
dispatchRemoveFinished(holder)
pendingAnimations.remove(holder)
}
.start()
}
isAdding(holder) -> {
// 添加动画:渐入
holder.itemView.animate()
.alpha(1f)
.setDuration(300)
.withEndAction {
dispatchAddFinished(holder)
pendingAnimations.remove(holder)
}
.start()
}
}
}
}
// 其他抽象方法实现(默认不支持移动和更新动画)
override fun animateMove(holder: ViewHolder, fromX: Int, fromY: Int, toX: Int, toY: Int): Boolean {
dispatchMoveFinished(holder)
return false
}
override fun animateChange(oldHolder: ViewHolder, newHolder: ViewHolder): Boolean {
dispatchChangeFinished(oldHolder, true)
dispatchChangeFinished(newHolder, false)
return false
}
override fun endAnimation(holder: ViewHolder) {
holder.itemView.animate().cancel()
pendingAnimations.remove(holder)
}
override fun endAnimations() {
for (holder in pendingAnimations) {
holder.itemView.animate().cancel()
}
pendingAnimations.clear()
}
override fun isRunning(): Boolean {
return pendingAnimations.isNotEmpty()
}
}
// 使用自定义动画
rvList.setItemAnimator(FadeItemAnimator())
七、高级特性:DiffUtil 与 ItemTouchHelper
7.1 DiffUtil(高效数据更新)
DiffUtil是 Android 提供的「数据差异计算工具」,通过对比新旧数据列表,自动计算出新增、删除、更新的 item,避免全量刷新,核心接口是DiffUtil.Callback:
7.1.1 DiffUtil.Callback 核心方法
abstract class Callback {
/**
* 获取旧列表的item数量
*/
abstract fun getOldListSize(): Int
/**
* 获取新列表的item数量
*/
abstract fun getNewListSize(): Int
/**
* 判断两个item是否为同一个(根据唯一ID)
* @param oldItemPosition 旧列表位置
* @param newItemPosition 新列表位置
* @return true:同一item,false:不同item
*/
abstract fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean
/**
* 判断同一item的内容是否变化(areItemsTheSame返回true时调用)
* @param oldItemPosition 旧列表位置
* @param newItemPosition 新列表位置
* @return true:内容相同,false:内容不同
*/
abstract fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean
/**
* 获取item内容变化的payload(可选,用于局部刷新)
* @return payload对象(如变化的字段标识)
*/
open fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
return null
}
}
7.1.2 使用示例(结合 Adapter)
// 1. 定义DiffUtil.Callback
class MyDiffCallback(
private val oldList: List,
private val newList: List
) : DiffUtil.Callback() {
override fun getOldListSize(): Int = oldList.size
override fun getNewListSize(): Int = newList.size
override fun areItemsTheSame(oldPos: Int, newPos: Int): Boolean {
// 根据唯一ID判断是否为同一item
return oldList[oldPos].id == newList[newPos].id
}
override fun areContentsTheSame(oldPos: Int, newPos: Int): Boolean {
// 对比item的所有字段(或关键字段)
return oldList[oldPos] == newList[newPos]
}
override fun getChangePayload(oldPos: Int, newPos: Int): Any? {
val oldData = oldList[oldPos]
val newData = newList[newPos]
// 仅返回变化的字段(如点赞状态)
val payload = Bundle()
if (oldData.isLiked != newData.isLiked) {
payload.putBoolean("isLiked", newData.isLiked)
}
return if (payload.isEmpty) null else payload
}
}
// 2. 在Adapter中使用DiffUtil
class MyRecyclerAdapter : RecyclerView.Adapter() {
private var dataList: List = emptyList()
// 更新数据(使用DiffUtil)
fun updateData(newData: List) {
val diffCallback = MyDiffCallback(dataList, newData)
val diffResult = DiffUtil.calculateDiff(diffCallback)
// 更新数据列表
dataList = newData
// 应用差异更新(自动调用对应的notify方法)
diffResult.dispatchUpdatesTo(this)
}
// 其他方法(onCreateViewHolder、onBindViewHolder等)...
}
7.2 ItemTouchHelper(侧滑删除与拖拽排序)
ItemTouchHelper用于实现 item 的「侧滑删除」和「拖拽排序」,核心是ItemTouchHelper.Callback接口:
7.2.1 ItemTouchHelper.Callback 核心方法
abstract class Callback {
/**
* 设置item可滑动/拖拽的方向
* @param recyclerView RecyclerView实例
* @param viewHolder 当前item的ViewHolder
* @return 方向标识(如LEFT|RIGHT表示可左右滑动,UP|DOWN表示可上下拖拽)
*/
abstract fun getMovementFlags(recyclerView: RecyclerView, viewHolder: ViewHolder): Int
/**
* 处理item拖拽逻辑
* @param recyclerView RecyclerView实例
* @param viewHolder 被拖拽的ViewHolder
* @param target 目标ViewHolder(拖拽到的位置)
* @return true:处理拖拽,false:不处理
*/
abstract fun onMove(
recyclerView: RecyclerView,
viewHolder: ViewHolder,
target: ViewHolder
): Boolean
/**
* 处理item侧滑逻辑
* @param viewHolder 被侧滑的ViewHolder
* @param direction 滑动方向(LEFT/RIGHT/UP/DOWN)
*/
abstract fun onSwiped(viewHolder: ViewHolder, direction: Int)
/**
* 自定义侧滑/拖拽时的背景(可选)
*/
override fun onChildDraw(
c: Canvas,
recyclerView: RecyclerView,
viewHolder: ViewHolder,
dX: Float,
dY: Float,
actionState: Int,
isCurrentlyActive: Boolean
) {
// 绘制侧滑背景(如删除按钮、颜色)
}
}
7.2.2 使用示例(侧滑删除 + 拖拽排序)
// 1. 定义ItemTouchHelper.Callback
class MyItemTouchCallback(
private val onSwipeListener: (Int) -> Unit, // 侧滑删除回调
private val onMoveListener: (Int, Int) -> Unit // 拖拽排序回调
) : ItemTouchHelper.Callback() {
// 设置支持的方向:上下拖拽+左右侧滑
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: ViewHolder): Int {
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN // 拖拽方向
val swipeFlags = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT // 滑动方向
return makeMovementFlags(dragFlags, swipeFlags)
}
// 处理拖拽
override fun onMove(
recyclerView: RecyclerView,
viewHolder: ViewHolder,
target: ViewHolder
): Boolean {
val fromPos = viewHolder.adapterPosition
val toPos = target.adapterPosition
if (fromPos != toPos) {
onMoveListener(fromPos, toPos)
return true
}
return false
}
// 处理侧滑删除
override fun onSwiped(viewHolder: ViewHolder, direction: Int) {
val position = viewHolder.adapterPosition
onSwipeListener(position)
}
// 自定义侧滑背景(左侧红色删除,右侧灰色)
override fun onChildDraw(
c: Canvas,
recyclerView: RecyclerView,
viewHolder: ViewHolder,
dX: Float,
dY: Float,
actionState: Int,
isCurrentlyActive: Boolean
) {
val itemView = viewHolder.itemView
val itemHeight = itemView.bottom - itemView.top
val iconMargin = (itemHeight - 24.dpToPx()) / 2 // 图标间距
if (dX
// 处理侧滑删除
dataList.removeAt(position)
adapter.notifyItemRemoved(position)
},
onMoveListener = { fromPos, toPos ->
// 处理拖拽排序
Collections.swap(dataList, fromPos, toPos)
adapter.notifyItemMoved(fromPos, toPos)
}
)
val itemTouchHelper = ItemTouchHelper(itemTouchCallback)
itemTouchHelper.attachToRecyclerView(rvList) // 绑定到RecyclerView
八、性能优化与最佳实践
8.1 核心优化策略
- 复用 ViewHolder:严禁在onBindViewHolder中findViewById,所有子视图引用必须在 ViewHolder 中初始化
- 精准更新数据:使用notifyItemXXX或DiffUtil,禁止使用notifyDataSetChanged
- 减少过度绘制:item 布局避免多层嵌套(建议≤3 层),减少透明背景
- 图片加载优化:使用 Coil/Glide 加载图片,设置合适的分辨率和缓存策略,回收时取消请求
- 避免主线程耗时操作:onBindViewHolder中禁止网络请求、数据库读写等耗时操作,通过异步加载
- 设置合理的缓存池大小:通过RecyclerView.setRecycledViewPool设置全局缓存池,复用不同 RecyclerView 的 ViewHolder
8.2 常见问题解决方案
1.item 点击事件冲突:在 ViewHolder 中设置 itemView 的点击监听,避免在onBindViewHolder中频繁创建匿名对象:
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
init {
// 初始化时设置点击监听(仅一次)
itemView.setOnClickListener {
val position = adapterPosition
if (position != RecyclerView.NO_POSITION) {
onItemClickListener?.onClick(position)
}
}
}
}
2.瀑布流布局 item 高度闪烁:禁用自动测量,手动设置 item 高度:
staggeredLayoutManager.isAutoMeasureEnabled = false
// 在onBindViewHolder中设置item高度
holder.itemView.layoutParams.height = getRandomHeight() // 根据数据动态计算高度
3.滑动卡顿:检查是否在onScrolled中执行耗时操作,关闭硬件加速(极端情况):
九、总结
RecyclerView 的强大之处在于其「模块化设计」和「高度可定制性」,核心组件间的分工明确:
- RecyclerView:容器核心,负责组件绑定和滚动控制
- LayoutManager:布局大脑,控制 item 的测量、布局和回收
- Adapter:数据桥梁,管理数据和创建 ViewHolder
- ViewHolder:视图容器,实现视图复用
- ItemDecoration:装饰扩展,实现分割线和额外视图
- ItemAnimator:动画引擎,处理 item 增删改的动画
掌握本文涵盖的所有接口,不仅能实现基础列表功能,还能轻松应对多类型 item、瀑布流布局、侧滑删除、拖拽排序等复杂场景。在实际开发中,需牢记「性能优先」原则,通过精准更新、视图复用、异步加载等策略,确保 RecyclerView 在大数据量下依然流畅运行。