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 核心优化策略

  1. 复用 ViewHolder:严禁在onBindViewHolder中findViewById,所有子视图引用必须在 ViewHolder 中初始化
  2. 精准更新数据:使用notifyItemXXX或DiffUtil,禁止使用notifyDataSetChanged
  3. 减少过度绘制:item 布局避免多层嵌套(建议≤3 层),减少透明背景
  4. 图片加载优化:使用 Coil/Glide 加载图片,设置合适的分辨率和缓存策略,回收时取消请求
  5. 避免主线程耗时操作:onBindViewHolder中禁止网络请求、数据库读写等耗时操作,通过异步加载
  6. 设置合理的缓存池大小:通过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 在大数据量下依然流畅运行。

posted @ 2025-09-04 22:12  yfceshi  阅读(11)  评论(0)    收藏  举报