首页 > 其他分享 >RecyclerView 高效使用与常见问题解决

RecyclerView 高效使用与常见问题解决

时间:2024-09-07 13:25:10浏览次数:13  
标签:高效 常见问题 val RecyclerView override fun position holder

RecyclerView 是 Android 应用开发中最常用的 UI 组件之一,通常用于显示大量数据列表。尽管功能强大,但如果使用不当,会导致性能问题、数据错乱或滚动卡顿等问题。在本篇文章中,我们将探讨 RecyclerView 的一些常见坑点,提供解决方案,并附带代码示例。

1. 坑点:ViewHolder 重用导致数据错乱

RecyclerView 的核心设计思想是重用 ViewHolder,通过限制创建视图的数量提升性能。但是,重用机制会导致视图错位或数据显示不正确,尤其是在滑动列表时。

避坑建议:
  • onBindViewHolder() 中,每次都需要为 ViewHolder 中的所有 UI 元素设置数据,即使元素是复选框、单选按钮等状态控件。
  • 避免将不需要重用的视图状态留在 ViewHolder 中,确保数据绑定准确无误。
示例代码:
class MyAdapter(private val dataList: List<MyData>) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {

    class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val textView: TextView = itemView.findViewById(R.id.textView)
        val checkBox: CheckBox = itemView.findViewById(R.id.checkBox)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_view, parent, false)
        return MyViewHolder(view)
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val data = dataList[position]
        holder.textView.text = data.text

        // 每次绑定时重置复选框状态
        holder.checkBox.isChecked = data.isChecked

        // 监听复选框的状态变化
        holder.checkBox.setOnCheckedChangeListener { _, isChecked ->
            dataList[position].isChecked = isChecked
        }
    }

    override fun getItemCount(): Int = dataList.size
}

解释: 在此代码中,每次绑定数据时都会重置 CheckBox 的状态,避免因 ViewHolder 重用导致复选框的选中状态在不同项之间传递。

2. 坑点:RecyclerView 的性能瓶颈

RecyclerView 数据量过大时,可能会遇到滚动卡顿或内存占用过高等问题。这通常是由于布局层级过深、图片加载不当或没有充分利用缓存机制导致的。

避坑建议:
  • 使用 ViewHolder 缓存视图,避免重复调用 findViewById()
  • 使用 DiffUtil 来优化数据更新时的效率,而不是每次都重新刷新整个列表。
  • 对于图片加载,使用 GlidePicasso 这类图片库进行异步加载,避免在主线程进行图像处理。
  • 如果需要在列表中嵌套其他 RecyclerView,务必启用 setHasFixedSize(true),减少重新计算的开销。
示例代码:
class MyAdapter(private val dataList: List<MyData>) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {

    private val diffCallback = object : DiffUtil.ItemCallback<MyData>() {
        override fun areItemsTheSame(oldItem: MyData, newItem: MyData): Boolean {
            return oldItem.id == newItem.id
        }

        override fun areContentsTheSame(oldItem: MyData, newItem: MyData): Boolean {
            return oldItem == newItem
        }
    }

    private val differ = AsyncListDiffer(this, diffCallback)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_view, parent, false)
        return MyViewHolder(view)
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val data = differ.currentList[position]
        holder.textView.text = data.text

        // 使用 Glide 异步加载图片,提升性能
        Glide.with(holder.itemView.context)
            .load(data.imageUrl)
            .into(holder.imageView)
    }

    override fun getItemCount(): Int = differ.currentList.size

    fun submitList(newList: List<MyData>) {
        differ.submitList(newList)
    }
}

解释: 通过使用 AsyncListDifferDiffUtil,我们可以高效处理数据集的变化,从而避免刷新整个列表,提升性能。同时,使用 Glide 异步加载图片,确保不会阻塞主线程。

3. 坑点:RecyclerView 数据更新错乱

RecyclerView 的数据发生变化时,如果没有正确处理数据更新,可能会导致 UI 不刷新或数据错乱。例如,当数据集的某一部分发生变化时,很多开发者会使用 notifyDataSetChanged() 刷新整个列表,这样不仅影响性能,还容易导致不必要的视图重绘。

避坑建议:
  • 使用 notifyItemInserted(), notifyItemRemoved() 等方法精确更新列表中的某一项,而不是每次都刷新整个列表。
  • 在数据发生批量变化时,使用 DiffUtil 来高效计算差异,并精确更新 RecyclerView
示例代码:
fun addItem(newItem: MyData, position: Int) {
    dataList.add(position, newItem)
    notifyItemInserted(position)
}

fun removeItem(position: Int) {
    dataList.removeAt(position)
    notifyItemRemoved(position)
}

解释: 使用 notifyItemInserted()notifyItemRemoved(),可以精确地更新列表中单个项目的变化,而不会影响其他列表项。这种方法既能保持界面流畅,又能避免不必要的视图重绘。

4. 坑点:RecyclerView 内存泄漏

RecyclerView 的适配器中往往会持有上下文对象(如 ActivityFragment),如果处理不当,可能会导致内存泄漏,尤其是在屏幕旋转或 Activity 销毁时。

避坑建议:
  • 避免在 Adapter 中直接持有 ActivityFragment 的引用,改为使用 WeakReference 或使用 Context 进行操作。
  • 确保在 onViewDetachedFromWindow()onViewRecycled() 中及时清理资源,防止内存泄漏。
示例代码:
class MyAdapter(private val context: Context) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.itemView.setOnClickListener {
            // 避免持有 Activity 的强引用
            val intent = Intent(context, DetailActivity::class.java)
            context.startActivity(intent)
        }
    }

    override fun onViewRecycled(holder: MyViewHolder) {
        super.onViewRecycled(holder)
        // 在视图回收时清理资源
        holder.imageView.setImageDrawable(null)
    }
}

解释: 在此代码中,通过将 Activity 的引用替换为 Context,并在视图被回收时清除图片资源,避免了内存泄漏的风险。

5. 坑点:嵌套 RecyclerView 滚动问题

RecyclerView 中嵌套其他 RecyclerView 时,内层 RecyclerView 的滚动行为常常会出现问题,导致外层无法顺畅滚动或者两者滚动冲突。

避坑建议:
  • 为嵌套的 RecyclerView 设置 isNestedScrollingEnabled = false,避免滚动冲突。
  • 考虑使用 ConcatAdapter 来合并多种类型的数据,而不是使用嵌套的 RecyclerView,从而避免复杂的滚动问题。
示例代码:
class OuterAdapter(private val dataList: List<OuterData>) : RecyclerView.Adapter<OuterAdapter.OuterViewHolder>() {

    class OuterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val innerRecyclerView: RecyclerView = itemView.findViewById(R.id.innerRecyclerView)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OuterViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.outer_item_view, parent, false)
        return OuterViewHolder(view)
    }

    override fun onBindViewHolder(holder: OuterViewHolder, position: Int) {
        val innerAdapter = InnerAdapter(dataList[position].innerData)
        holder.innerRecyclerView.adapter = innerAdapter

        // 禁用嵌套滚动
        holder.innerRecyclerView.isNestedScrollingEnabled = false
    }

    override fun getItemCount(): Int = dataList.size
}

标签:高效,常见问题,val,RecyclerView,override,fun,position,holder
From: https://blog.51cto.com/u_14540126/11944629

相关文章

  • 我司使用了两年的高效日志打印工具,非常牛逼!
    为了更方便地排查问题,电商交易系统的日志中需要记录用户id和订单id等字段。然而,每次打印日志都需要手动设置用户id,这一过程非常繁琐,需要想个办法优化下。log.warn("user:{},orderId:{}订单提单成功",userId,orderId);log.warn("user:{},orderId:{}订单支付成功",userId,orde......
  • 深入理解动态内存(一):动态内存使用常见问题
    目录对NULL指针的解引用操作对动态开辟空间的越界访问对非动态开辟内存使用free释放使用free释放⼀块动态开辟内存的⼀部分对同⼀块动态内存多次释放动态开辟内存忘记释放(内存泄漏)对NULL指针的解引用操作#include<stdio.h>#include<stdlib.h>intmain(){ int*p......
  • 深入浅出孪生神经网络,高效训练模型
    大家好,在深度学习领域,神经网络几乎能处理各种任务,但通常需要依赖于海量数据来达到最佳效果。然而,对于像面部识别和签名验证这类任务,我们不可能总是有大量的数据可用。由此产生了一种新型的神经网络架构,称为孪生网络。孪生神经网络能够基于少量数据实现精准预测,本文将介绍孪生......
  • Java 线程池:参数、配置和常见问题以及案例示范
    Java线程池:参数、配置和常见问题以及案例示范线程池是提高系统性能和资源利用率的关键组件之一。通过合理配置线程池,可以有效地管理线程资源,避免系统过载,提升并发处理能力。本文将以电商交易系统为案例,详细讲解Java线程池的参数、配置、以及常见问题和解决方案以及在spr......
  • 25届毕设选题推荐-图书管理系统用小程序开发,如何实现快速借阅?uniapp 帮你高效搞定!
    博主介绍:✌十余年IT大项目实战经验、在某机构培训学员上千名、专注于本行业领域✌技术范围:Java实战项目、Python实战项目、微信小程序/安卓实战项目、爬虫+大数据实战项目、Nodejs实战项目、PHP实战项目、.NET实战项目、Golang实战项目。主要内容:系统功能设计、开题报告......
  • MySQL 日期函数语法介绍和案例示范以及常见问题解决
    本文将以电商交易系统为例,详细讲解MySQL日期类型及其转化,常用的日期函数,以及一些解决常见问题的方案。一、MySQL日期数据类型MySQL提供了多种日期数据类型,适用于不同的使用场景。常见的日期类型包括DATE、DATETIME、TIMESTAMP、TIME和YEAR。DATE:只存储日期,不包含......
  • DeepMind新成果被批像广告?AlphaProteo可高效设计靶蛋白结合物,亲和力提高300倍
    AlphaFold在蛋白质预测领域一骑绝尘,AlphaFold3更是突破限制,实现了所有生命分子的预测,其发布时便有声音称,AlphaFold3的结构化预测与生成将加速AIDD(ArtificialIntelligence-drivenDrugDesign,AI驱动药物设计)的发展。如今,DeepMind面向AI药物设计再开一枪——发布用于......
  • 掌握Git分支管理策略:让团队协作更高效
    在现代软件开发过程中,版本控制系统(VCS)是不可或缺的一部分。Git作为目前最流行的分布式版本控制系统之一,为开发者提供了强大的工具集来管理代码变更历史。然而,仅仅掌握Git的基本命令并不足以应对大型项目和团队协作的需求。有效的分支管理策略对于保持代码库的整洁、促进团......
  • 掌握检索技术:构建高效知识检索系统的架构与算法6
    在检索专业知识层需要涵盖更高级的检索技术,包括工程架构和算法策略。一、工程架构工程架构在构建检索系统中决定了系统的可扩展性、高可用性和性能。比如需要考虑的基本点:分布式架构:水平扩展:采用分布式架构,将检索任务分布到多个节点上,实现水平扩展。这可以通过将索引数据......
  • 掌握 Ansible:高效自动化运维的完整教程
    Ansible自动化运维全解指南感谢浪浪云支持发布浪浪云活动链接:https://langlangy.cn/?i8afa52文章目录Ansible自动化运维全解指南一、Ansible概述1.Ansible特点二、Ansible的角色1.使用者2.Ansible工具集3.作用对象三、Ansible的配置1.Ansible安装1)通过......