Android UI篇之RecyclerView(四)

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)
}
相关推荐
li_liuliu10 分钟前
Android4.4 在系统中添加自己的System Service
android
C4rpeDime2 小时前
自建MD5解密平台-续
android
鲤籽鲲4 小时前
C# Random 随机数 全面解析
android·java·c#
m0_548514778 小时前
2024.12.10——攻防世界Web_php_include
android·前端·php
凤邪摩羯8 小时前
Android-性能优化-03-启动优化-启动耗时
android
凤邪摩羯8 小时前
Android-性能优化-02-内存优化-LeakCanary原理解析
android
喀什酱豆腐9 小时前
Handle
android
m0_7482329210 小时前
Android Https和WebView
android·网络协议·https
m0_7482517210 小时前
Android webview 打开本地H5项目(Cocos游戏以及Unity游戏)
android·游戏·unity
m0_7482546612 小时前
go官方日志库带色彩格式化
android·开发语言·golang