RecyclerView
RecyclerView是列表控件,用于展示大量的数据。不过会随着数据的增加,RecyclerView性能会受到影响,导致卡顿、内存泄漏等
简单使用
kotlin
mBinding.recyclerView.layoutManager = LinearLayoutManager(context)
val adapter = ItemAdapter(context, data) { title ->
}
mBinding.recyclerView.adapter = adapter
// Adapter
class ItemAdapter(private val context: Context, private var data: List<String>,
private val click: (title: String) -> Unit): RecyclerView.Adapter<ItemAdapter.Holder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder =
Holder(ItemBinding.inflate(LayoutInflater.from(context), parent, false))
override fun onBindViewHolder(holder: OcrHolder, position: Int) {
val title = data[position]
holder.binding.ocrPropTitle.text = title
holder.binding.root.setOnClickListener {
click(title)
}
}
override fun getItemCount(): Int = data.size
@SuppressLint("NotifyDataSetChanged")
fun submit(list: List<String>) {
data = list
notifyDataSetChanged()
}
inner class Holder(val binding: ItemBinding): RecyclerView.ViewHolder(binding.root)
}
优化
- 布局优化:减少嵌套层级,提高布局效率
- 减少绘制:尽可能减少绘制次数,避免过度绘制带来性能消耗
- 滑动优化:滑动过程中减少耗时操作,避免影响滑动效果
- 预加载:预加载即将显示视图,提高展示性能
- 内存优化:减少内存消耗,合理释放内存,避免内存泄漏
布局优化
- 减少布局嵌套
避免item布局使用过多嵌套布局和复杂的层次结构
合理使用ConstraintLayout
- 使用merge标签合并布局
merge标签可将多个布局文件合并为一个,减少布局层级,提高绘制性能
- RecyclerView.setHasFixedSize(true)
针对item高度不变的列表,设置后不会因item改变而触发重新计算布局,避免requestLayout导致的资源浪费
减少绘制
- 局部刷新
只更新变化列表项
- 使用DiffUtil进行数据更新
数据发生变化时,使用DiffUtil进行差异性计算,减少不必要的UI更新,提高性能 DiffUtil可在后台线程中高效计算数据集差异,并将结果用到RecyclerView中
kotlin
val Diff(private val old: ArrayList<String>, private val new: ArrayList<String>): DiffUtil.Callback() {
override fun getOldListSize() = old.size
override fun getNewListSize() = new.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
old[oldItemPosition] == new[newItemPosition]
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
old[oldItemPosition] == new[newItemPosition]
}
val diff = DiffUtil.calculateDiff(Diff(old, new))
diff.dispatchUpdatesTo(adapter)
- 限制列表项数量
采用分页或只加载可见范围内数据,从而减少内存占用和渲染时间
kotlin
// 加载可见范围的数据
recyclerView.layoutManager?.setInitialPrefetchItemCount(10)
滑动优化
- 在onCreateViewHolder()中进行必要初始化工作
设置监听器,避免onBindViewHolder()中进行耗时操作,提高滚动性能
- 滑动时停止加载操作
可通过RecyclerView.addOnScrollListener(listener)方式增加滚动监听器,在监听器中进一步优化滑动效果
kotlin
recyclerView.addOnScrollListener(object: RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
// 判断滚动状态是否为停止滚动状态
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
startLoaidng()
} else {
// 停止加载操作,例如暂停图片加载等
stopLoading()
}
}
})
预加载
- 启动calculateExtraLayoutSpace
calculateExtraLayoutSpace可用来增加RecyclerView预留的额外空间,有助于提前加载屏幕外的Item,避免滑动过程中卡顿
kotlin
class LayoutManager(context: Context, orientation: Int = 1, reverseLayout: Boolean = false):
LinearLayoutManager(context, orientation, reverseLayout) {
constructor(context: Context) : super(context, 1)
override fun calculateExtraLayoutSpace(state: RecyclerView.State, extraLayoutSpace: IntArray) {
super.calculateExtraLayoutSpace(state, extraLayoutSpace)
// 设置额外的布局空间,可以根据需要动态计算
extraLayoutSpace[0] = 300
extraLayoutSpace[1] = 300
}
}
- 重写collectAdjacentPrefetchPositions
collectAdjacentPrefetchPositions是RecyclerView中的方法
用于RecyclerView的预取机制,用于在滑动过程中预取与当前位置相邻的item数据,提高滑动流畅度
kotlin
class LayoutManager(context: Context, orientation: Int = 1, reverseLayout: Boolean = false):
LinearLayoutManager(context, orientation, reverseLayout) {
constructor(context: Context) : super(context, 1)
override fun collectAdjacentPrefetchPositions(dx: Int, dy: Int, state: RecyclerView.State?,
layoutPrefetchRegistry: LayoutPrefetchRegistry) {
super.collectAdjacentPrefetchPositions(dx, dy, state, layoutPrefetchRegistry)
// 根据滑动方向(dx, dy)收集相邻的预取位置
val anchorPos = findFirstVisibleItemPosition()
if (dy > 0) {
// 向下滑动,预取下面的Item数据
for (i in anchorPos + 1 until state?.itemCount ?: 0) {
layoutPrefetchRegistry.addPosition(i, 0)
}
} else {
// 向上滑动,预取上面的Item数据
for (i in anchorPos - 1 downTo 0) {
layoutPrefetchRegistry.addPosition(i, 0)
}
}
}
}
内存优化
- 共用RecyclerViewPool
当多个RecyclerView的Adapter一样时,可让RecyclerView共享一个RecyclerViewPool以提高性能 适用于多个RecyclerView之间的数据或布局结构相似的场景
kotlin
val pool = RecyclerView.RecycledViewPool()
recycler1.setRecycledViewPool(pool)
recycler2.setRecycledViewPool(pool)
- 使用Adapter.setHasStableIds(true)提高item稳定性
setHasStableIds可提高item稳定性,帮助RecyclerView更好识别和复用ViewHolder,避免频繁创建和销毁ViewHolder,减少内存消耗
- RecyclerView.setItemViewCacheSize(30)
设置缓存大小,控制RecyclerView中缓存ViewHolder数量,避免缓存占用过多的内存
- 共享事件
针对点击事件,可创建一个共用的监听器对象,设置给所有itemView,通过id区分执行不同的操作,从而避免对每个item设置监听器对象,减少资源消耗
kotlin
class XxxAdapter: RecyclerView.Adapter<XxxAdapter.Holder>() {
...
val itemClickListener = View.OnClickListener { view ->
when(view.id) {
binding.tv.id -> {}
binding.iv.id -> {}
else -> {}
}
}
inner class Holder(private val binding: ItemBinding): RecyclerView.ViewHolder(binding.root) {
init {
binding.root.setOnClickListener(itemClickListener)
}
}
}
- 重写RecyclerView.onViewRecycled(holder)
回收资源,释放图片资源,移除监听器,以便ViewHolder被回收时及时释放资源,避免内存泄漏和资源浪费
kotlin
override fun onViewRecycled(holder: ViewHolder) {
super.onViewRecycled(holder)
// 释放ViewHolder中的图片资源
holder.binding.iv.setImageDrawable(null)
// 移除ViewHolder中的监听器
holder.binding.root.setOnClickListener(null)
}