• RecyclerView高效使用第二节


    开发中列表用的最多,但性能可能也是最容易忽略的一部分,在做性能优化时,我们都知在编写布局时尽量采用约束布局,减少布局的嵌套层次,减少冗余的背景以提升布局解析渲染的速度,但这只是优化的其中一点。接下来参考官方提供的API结合实际场景对列表相关的做进一步的优化。

    一,DiffUtil差异化刷新

    相较于ListView,RecyclerView支持了局部的刷新,极大的提升了刷新时的性能,但平时开发中,大多数人为了简单或偷懒,直接调用了RecyclerView的全局刷新,毕竟只用写一行代码,也不会出什么差错,但在做性能优化时,如果想更近一步,我们可以结合DiffUtil进行差异化刷新。
    如下:
    1,创建xml布局

    1)Activity布局
    
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <androidx.appcompat.widget.AppCompatButton
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintHorizontal_weight="1"
            app:layout_constraintRight_toLeftOf="@+id/btn_loadMore"
            android:id="@+id/btn_refresh"
            android:text="refresh"
            android:gravity="center"
            android:layout_width="0dp"
            android:layout_height="wrap_content"/>
    
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rv_study"
            android:layout_width="match_parent"
            app:layout_constraintTop_toBottomOf="@+id/btn_refresh"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            android:orientation="vertical"
            tools:listitem="@layout/item_study"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_height="0dp"/>
    
    androidx.constraintlayout.widget.ConstraintLayout>
    2) item布局
    
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        xmlns:app="http://schemas.android.com/apk/res-auto">
    
        <androidx.appcompat.widget.AppCompatImageView
            android:id="@+id/iv_icon"
            android:layout_width="48dp"
            app:layout_constraintLeft_toLeftOf="parent"
            android:src="@mipmap/ic_launcher"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_height="48dp"/>
    
        <TextView
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintLeft_toRightOf="@+id/iv_icon"
            android:layout_width="wrap_content"
            android:id="@+id/tv_title"
            android:gravity="center"
            android:text="Study"
            android:textSize="22sp"
            android:layout_marginLeft="16dp"
            app:layout_constraintVertical_chainStyle="packed"
            app:layout_constraintBottom_toTopOf="@+id/tv_second"
            android:textColor="@android:color/black"
            android:layout_height="wrap_content"/>
    
        <TextView
            android:id="@+id/tv_subtitle"
            android:layout_width="wrap_content"
            app:layout_constraintLeft_toRightOf="@+id/iv_icon"
            app:layout_constraintTop_toBottomOf="@+id/tv_study"
            android:textSize="16sp"
            android:layout_marginLeft="16dp"
            app:layout_constraintVertical_chainStyle="packed"
            android:text="zhansan"
            android:textColor="@color/cardview_dark_background"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_height="wrap_content"/>
    
        <TextView
            android:id="@+id/tv_gender"
            android:layout_width="wrap_content"
            android:textSize="18sp"
            android:text=""
            android:textColor="@color/material_on_surface_emphasis_high_type"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:textStyle="bold"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_height="wrap_content"/>
    
    
    androidx.constraintlayout.widget.ConstraintLayout>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89

    2,继承ListAdapter

    package com.example.recyclerviewstudy.two
    
    import android.view.LayoutInflater
    import android.view.View
    import android.view.ViewGroup
    import android.widget.TextView
    import androidx.recyclerview.widget.DiffUtil
    import androidx.recyclerview.widget.ListAdapter
    import androidx.recyclerview.widget.RecyclerView
    import androidx.recyclerview.widget.SortedList
    import com.example.recyclerviewstudy.R
    import com.example.recyclerviewstudy.StudentInfo
    
    class StudentAdapter:ListAdapter<StudentInfo,
            StudentAdapter.ViewHolder>(StudentDiffCallback()) {
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
            return ViewHolder(LayoutInflater.from(parent.context)
                .inflate(R.layout.item_study,parent,false))
        }
    
        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
            holder.bind(getItem(position))
        }
    
        class ViewHolder(itemView:View):RecyclerView.ViewHolder(itemView){
            val tv_name: TextView = itemView.findViewById(R.id.tv_title)
            val tv_age: TextView = itemView.findViewById(R.id.tv_subtitle)
            val tv_gender:TextView = itemView.findViewById(R.id.tv_gender)
    
            fun bind(entity:StudentInfo){
                entity.apply {
                    tv_name.text = name
                    tv_age.text = "$age"
                    tv_gender.text = gender
                }
            }
        }
    }
    
    //核心函数
    class StudentDiffCallback:DiffUtil.ItemCallback<StudentInfo>(){
        override fun areItemsTheSame(oldItem: StudentInfo, newItem: StudentInfo): Boolean {
            return oldItem.id == newItem.id
        }
    
        override fun areContentsTheSame(oldItem: StudentInfo, newItem: StudentInfo): Boolean {
            return oldItem == newItem
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    Adapter中核心就两点:
    1)继承ListAdapter
    2)实现DiffUtil.ItemCallback,判断两个对象是否相同

    3,Activity中初始化RecyclerView及Adapter

       //学生数据列表
        private val studentEntities = ArrayList<StudentInfo>()
        //适配器
        private val  studentAdapter by lazy {
            StudentAdapter()
        }
        
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    初始化Adapter

     val rv_student = findViewById<RecyclerView>(R.id.rv_student)
     rv_student.adapter = studentAdapter
    
    • 1
    • 2

    更新数据时

     studentAdapter.submitList(studentEntities)
    
    • 1

    当请求接口成功后,直接调用submitList进行数据更新,刷新就交给DiffUtil内部自行处理。
    如上这种方式使用场景也有一定的局限性,适合列表固定的,如果想支持下拉刷新类型的,需要基于DiffUtil.Callback进行实现
    例如:

      /**
         * getOldListSize():旧数据集的长度。
    
        getNewListSize():新数据集的长度
    
        areItemsTheSame():判断是否是同一个Item。
    
        areContentsTheSame():如果是通一个Item,此方法用于判断是否同一个 Item 的内容也相同
         */
        private fun diffCallBack(oldDataSource:ArrayList<StudentInfo>,
                                 newDataSource:ArrayList<StudentInfo>) =
            object :DiffUtil.Callback(){
    
                override fun getOldListSize() = oldDataSource.size
    
                override fun getNewListSize() = newDataSource.size
    
                override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                    return oldDataSource[oldItemPosition].name == newDataSource[newItemPosition].name
                }
    
                override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                    if (oldDataSource[oldItemPosition].name != newDataSource[newItemPosition].name||
                        oldDataSource[oldItemPosition].age == newDataSource[newItemPosition].age){
                        return false
                    }
                    return true
                }
    
                override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
                    return super.getChangePayload(oldItemPosition, newItemPosition)
    
                }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    DiffUtil内部使用了差法算法,计算出差异化的对象,然后调用RecyclerView的insert,delete等函数进行局部的刷新,提升刷新效率。

    二,AsyncListDiffer 异步差异化刷新

    DiffUtil可以解决局部刷新问题,但也存在缺陷,当数据量大时,会卡主线程,它的数据对比是在主线程进行的,所以当数据量大时,则推荐使用AsyncListDiffer进行异步差异化对比刷新,提升性能。
    相对于DiffUtil用法的差异主要体现在Adapter上,详见如下代码 :

    
    import android.view.LayoutInflater
    import android.view.View
    import android.view.ViewGroup
    import android.widget.TextView
    import androidx.recyclerview.widget.AsyncListDiffer
    import androidx.recyclerview.widget.DiffUtil
    import androidx.recyclerview.widget.RecyclerView
    import com.example.recyclerviewstudy.R
    import com.example.recyclerviewstudy.StudentInfo
    
    class AsyncStudentAdapter:RecyclerView.Adapter<AsyncStudentAdapter.ViewHolder>() {
    	//创建AsyncListDiffer对象,传入DiffUtil.ItemCallback
        private val mDiffer = AsyncListDiffer(this,StudentDiffCallback())
    
        fun submit(list:List<StudentInfo>){
            mDiffer.submitList(list)
        }
        
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
            return ViewHolder(
                LayoutInflater.from(parent.context)
                    .inflate(R.layout.item_study, parent, false)
            )
        }
    
        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
            holder.bind(mDiffer.currentList[position])
        }
    
        override fun getItemCount() = mDiffer.currentList.size
    
        class ViewHolder(itemView: View):RecyclerView.ViewHolder(itemView){
            val tv_name: TextView = itemView.findViewById(R.id.tv_title)
            val tv_age: TextView = itemView.findViewById(R.id.tv_subtitle)
            val tv_gender: TextView = itemView.findViewById(R.id.tv_gender)
    
            fun bind(entity: StudentInfo){
                entity.apply {
                    tv_name.text = name
                    tv_age.text = "$age"
                    tv_gender.text = gender
                }
            }
        }
    
        class StudentDiffCallback: DiffUtil.ItemCallback<StudentInfo>(){
            override fun areItemsTheSame(oldItem: StudentInfo, newItem: StudentInfo): Boolean {
                return oldItem.id == newItem.id
            }
    
            override fun areContentsTheSame(oldItem: StudentInfo, newItem: StudentInfo): Boolean {
                return oldItem == newItem
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    其核心步骤如下:
    1)实现 StudentDiffCallback在里面定义对象实体的差异化。
    2)创建AsyncListDiffer对象,传入DiffUtil.ItemCallback对象
    3)定义一个submit函数,用于对外数据的刷新
    在使用时,直接调用adapter的submit对数据列表进行刷新操作。

    三,SortedList对列表快速排序

    在有些情况下我们需要对数据列表进行排序时,很多人想到的是,自己遍历比较进行逐个排序,效率低下,当遇到数据量大时,条件多时,如果处理不好就会出现很多问题,如果使用SortedList则极大的提升了排序的效率,也使代码逻辑更加明晰。
    具体示例也是基于开始的布局问题,核心代码主要在Adapter中,如下:

    class TeacherAdapter:RecyclerView.Adapter<TeacherAdapter.ViewHolder>() {
    	//核心点
        private val sortedList by lazy {
            SortedList(TeacherInfo::class.java,sortListCallback(this))
        }
    
        fun submit(list:List<TeacherInfo>){
            sortedList.beginBatchedUpdates()
            sortedList.addAll(list)
            sortedList.endBatchedUpdates()
        }
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
            return ViewHolder(LayoutInflater.from(parent.context)
                .inflate(R.layout.item_study,parent,false))
        }
    
        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
            holder.bind(sortedList.get(position))
        }
    
        override fun getItemCount() = sortedList.size()
    
        class ViewHolder(itemView:View):RecyclerView.ViewHolder(itemView){
            val tv_name: TextView = itemView.findViewById(R.id.tv_title)
            val tv_age: TextView = itemView.findViewById(R.id.tv_subtitle)
            val tv_gender: TextView = itemView.findViewById(R.id.tv_gender)
    
            fun bind(entity: TeacherInfo){
                entity.apply {
                    tv_name.text = name
                    tv_age.text = "$age"
                    tv_gender.text = gender
                }
            }
        }
    
    //继承SortedListAdapterCallback定义排序规则
     class sortListCallback(adapter:RecyclerView.Adapter<*>):
        SortedListAdapterCallback<TeacherInfo>(adapter){
            //用来排序
            override fun compare(o1: TeacherInfo, o2: TeacherInfo): Int {
              return  o1.id.compareTo(o2.id)
            }
    
            override fun areContentsTheSame(oldItem: TeacherInfo, newItem: TeacherInfo): Boolean {
               return oldItem.hashCode() == newItem.hashCode()
            }
    
            override fun areItemsTheSame(item1: TeacherInfo, item2: TeacherInfo): Boolean {
               return item1.id == item2.id
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    如上便是SortedList的核心代码,在使用时,直接submit进行数据的刷新,当然其内部还提供了很多插入单个数据或删除单个数据的api,更多的用法参考官方API文档

    四,ConcatAdapter连接多个Adapter

    当页面由多个不同类别组成时,可以使用ConcatAdapter进行Adapter拼接,但其也存在一定的局限性,因为使用的同一个RecyclerView,所以其列表布局方向必须一致,用法也很简单直接创建ConcatAdapter对象,传入相应的Adapter即可

      val rv_student = findViewById<RecyclerView>(R.id.rv_student)
      val concatAdapter = ConcatAdapter(studentAdapter, asyncStudentAdapter)
      rv_student.adapter = concatAdapter
    
    • 1
    • 2
    • 3

    关键RecyclerView的使用,第二章节到此结束。文中示例安全仅作为参考,开发中可结合实际需求进行扩展,如果错误或更多高效的用法多多指教。

  • 相关阅读:
    外贸是什么意思?和跨境电商的区别是什么?
    自主设计,模拟实现 RabbitMQ - 多虚拟主机管理
    高速电路设计笔记----第二章
    ESP32设备通信-Mesh网络通信
    Hbase安装(保姆级)---------yarn逻辑解析
    应该如何发展美妆行业?
    性能优化:JIT即时编译与AOT提前编译
    2、Jvm类加载器和双亲委派机制
    嬴彻科技日: 发布《自动驾驶卡车量产白皮书》分享从量产走向无人技术路线
    VScode通过ssh连接树莓派进行远程开发调试(踩坑总结)
  • 原文地址:https://blog.csdn.net/zdc9023/article/details/126037262