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)
}
相关推荐
网络研究院1 小时前
Android 安卓内存安全漏洞数量大幅下降的原因
android·安全·编程·安卓·内存·漏洞·技术
凉亭下1 小时前
android navigation 用法详细使用
android
小比卡丘4 小时前
C语言进阶版第17课—自定义类型:联合和枚举
android·java·c语言
前行的小黑炭5 小时前
一篇搞定Android 实现扫码支付:如何对接海外的第三方支付;项目中的真实经验分享;如何高效对接,高效开发
android
落落落sss6 小时前
MybatisPlus
android·java·开发语言·spring·tomcat·rabbitmq·mybatis
代码敲上天.7 小时前
数据库语句优化
android·数据库·adb
GEEKVIP9 小时前
手机使用技巧:8 个 Android 锁屏移除工具 [解锁 Android]
android·macos·ios·智能手机·电脑·手机·iphone
model200511 小时前
android + tflite 分类APP开发-2
android·分类·tflite
彭于晏68911 小时前
Android广播
android·java·开发语言
与衫12 小时前
掌握嵌套子查询:复杂 SQL 中 * 列的准确表列关系
android·javascript·sql