RecyclerView的工作原理,缓存原理,以及用到的设计模式

163 阅读8分钟

RecyclerView 是 Android 提供的一个用于高效显示大量数据的视图组件。它比早期的 ListViewGridView 更加灵活和强大。为了更好地理解 RecyclerView,我们需要深入了解它的工作原理,包括其核心组件和主要流程。

一、核心组件

RecyclerView 的核心组件包括:

  1. Adapter:用于提供数据和创建 ViewHolder
  2. ViewHolder:用于缓存视图的引用,避免重复查找视图。
  3. LayoutManager:负责布局视图和回收视图。
  4. Recycler:管理视图的缓存和回收。
  5. ItemDecoration:用于装饰 RecyclerView 的子视图。
  6. ItemAnimator:处理视图的添加、移除和移动的动画。

二、工作流程

1. Adapter

Adapter 的主要作用是创建新的 ViewHolder 对象以及绑定数据到 ViewHolder 上。Adapter 有三个主要方法:

  • onCreateViewHolder(ViewGroup parent, int viewType): 创建一个新的 ViewHolder
  • onBindViewHolder(ViewHolder holder, int position): 将数据绑定到指定位置的 ViewHolder
  • getItemCount(): 返回数据集的总数。

2. ViewHolder

ViewHolder 是一个静态内部类,通常用于缓存视图,以避免每次都调用 findViewById。它的主要作用是提高 RecyclerView 的性能。

kotlin
复制代码
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    val textView: TextView = itemView.findViewById(R.id.textView)
}

3. LayoutManager

LayoutManager 负责测量和定位 RecyclerView 的所有子视图,处理视图的回收和重用。RecyclerView 提供了三种内置的 LayoutManager

  • LinearLayoutManager:以垂直或水平列表方式排列项目。
  • GridLayoutManager:以网格方式排列项目。
  • StaggeredGridLayoutManager:以交错网格方式排列项目。

4. Recycler

Recycler 负责管理 ViewHolder 的缓存,包括从 Scrap 缓存和 Recycler 缓存中获取 ViewHolder 以复用已有的视图。

5. ItemDecoration

ItemDecoration 用于在 RecyclerView 的子视图之间添加装饰效果,如分隔线或边距。

6. ItemAnimator

ItemAnimator 处理 RecyclerView 子视图的添加、删除和移动的动画。默认的 DefaultItemAnimator 提供了基本的动画效果。

三、工作原理

1. 初始化

RecyclerView 初始化时,会创建一个 Recycler 和一个 LayoutManagerLayoutManager 决定了 RecyclerView 的布局方式,如线性布局、网格布局等。

2. 布局阶段

RecyclerView 的布局阶段,LayoutManager 负责测量和定位子视图。LayoutManager 会从 Recycler 获取 ViewHolder,如果没有可用的 ViewHolder,则通过 Adapter 创建一个新的。

3. 数据绑定

ViewHolder 被创建或复用时,RecyclerView 会调用 AdapteronBindViewHolder 方法,将数据绑定到 ViewHolder 上。

4. 回收机制

当视图滚动出屏幕时,RecyclerView 会将这些视图移出可视范围,并将其放入 Recycler 的缓存中,以便下次滚动回来时复用这些视图。

5. 缓存管理

Recycler 维护两种缓存:

  • Scrap 缓存:保存当前屏幕上不可见但最近使用的视图。
  • Recycler 缓存:保存已经从屏幕上移除的视图。

6. 动画处理

当数据发生变化时,RecyclerView 会使用 ItemAnimator 来处理视图的动画效果,如添加、删除和移动视图时的动画。

四、示例代码

下面是一个使用 RecyclerView 的简单示例:

kotlin
复制代码
class MyAdapter(private val items: List<String>) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {

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

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

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.textView.text = items[position]
    }

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

class MyActivity : AppCompatActivity() {

    private lateinit var recyclerView: RecyclerView
    private lateinit var adapter: MyAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        recyclerView = findViewById(R.id.recyclerView)
        recyclerView.layoutManager = LinearLayoutManager(this)

        val items = listOf("Item 1", "Item 2", "Item 3")
        adapter = MyAdapter(items)
        recyclerView.adapter = adapter
    }
}

RecyclerView 的缓存机制通过 Recycler 类来实现,它管理和维护 ViewHolder 的缓存以提高性能。Recycler 有两种主要的缓存:

  1. Scrap 缓存:存储当前屏幕上不可见但最近使用的视图。
  2. Recycler 缓存:存储已经从屏幕上移除的视图。

下面详细解释这两种缓存的实现原理,并提供相应的代码示例。

一、Scrap 缓存

Scrap 缓存用于存储当前屏幕上不可见但最近使用的视图。RecyclerView 在布局过程中会将这些视图放入 Scrap 缓存中,以便快速复用。

kotlin
复制代码
class RecyclerView {
    private val mRecycler = Recycler()

    inner class Recycler {
        private val mAttachedScrap = ArrayList<ViewHolder>()

        fun getScrapViewForPosition(position: Int): ViewHolder? {
            for (i in mAttachedScrap.indices) {
                val holder = mAttachedScrap[i]
                if (holder.layoutPosition == position) {
                    mAttachedScrap.removeAt(i)
                    return holder
                }
            }
            return null
        }

        fun scrapView(view: View) {
            val holder = getChildViewHolder(view)
            holder.setScrapContainer(this, true)
            mAttachedScrap.add(holder)
        }
    }

    fun getChildViewHolder(view: View): ViewHolder {
        // 获取 view 对应的 ViewHolder
    }
}

在布局过程中,如果一个视图变得不可见,RecyclerView 会将其放入 Scrap 缓存中。在下一次布局时,如果需要复用该视图,可以从 Scrap 缓存中快速获取。

二、Recycler 缓存

Recycler 缓存用于存储已经从屏幕上移除的视图。当一个视图完全从屏幕上移除时,RecyclerView 会将其放入 Recycler 缓存中。

kotlin
复制代码
class RecyclerView {
    private val mRecycler = Recycler()

    inner class Recycler {
        private val mCachedViews = ArrayList<ViewHolder>()

        fun getViewForPosition(position: Int): ViewHolder {
            // 先从 Scrap 缓存中获取 ViewHolder
            val scrapView = getScrapViewForPosition(position)
            if (scrapView != null) {
                return scrapView
            }

            // 再从 Recycler 缓存中获取 ViewHolder
            for (i in mCachedViews.indices) {
                val holder = mCachedViews[i]
                if (holder.layoutPosition == position) {
                    mCachedViews.removeAt(i)
                    return holder
                }
            }

            // 最后如果缓存中没有,则创建新的 ViewHolder
            return createViewHolder()
        }

        fun recycleView(view: View) {
            val holder = getChildViewHolder(view)
            if (holder.isScrap) {
                // 如果视图在 Scrap 缓存中,先移除
                mAttachedScrap.remove(holder)
            } else {
                // 否则将视图放入 Recycler 缓存中
                mCachedViews.add(holder)
            }
        }

        private fun createViewHolder(): ViewHolder {
            // 创建新的 ViewHolder
        }
    }

    fun getChildViewHolder(view: View): ViewHolder {
        // 获取 view 对应的 ViewHolder
    }
}

RecyclerView缓存原理

RecyclerView 需要显示新的数据项时,会优先从 Scrap 缓存中获取 ViewHolder,如果没有再从 Recycler 缓存中获取,如果还没有,则创建新的 ViewHolder。使用

使用到的设计模式

1. 设计模式

1.1 ViewHolder 模式

ViewHolder 模式用于缓存视图引用,避免重复调用 findViewById,从而提高性能。这一模式在 RecyclerView 中通过 ViewHolder 类实现。

kotlin
复制代码
class MyAdapter(private val items: List<String>) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {

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

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

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.textView.text = items[position]
    }

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

1.2 Adapter 模式

Adapter 模式用于将数据源适配到视图组件。在 RecyclerView 中,通过 Adapter 类实现,负责将数据绑定到 ViewHolder

kotlin
复制代码
class MyAdapter(private val items: List<String>) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {

    // ViewHolder 定义略
    // onCreateViewHolder、onBindViewHolder、getItemCount 方法略
}

1.3 责任链模式

RecyclerView 的布局和绘制过程通过 LayoutManagerItemDecorationItemAnimator 等组件的协调配合,类似于责任链模式。每个组件在自己的职责范围内处理相应的任务。

1.4 工厂模式

RecyclerViewLayoutManagerItemDecorationItemAnimator 等组件的创建和配置,类似于工厂模式。用户可以根据需求自定义这些组件,以实现不同的布局和效果。

kotlin
复制代码
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL))
recyclerView.itemAnimator = DefaultItemAnimator()

2. 工作原理

RecyclerView 的工作原理可以总结为以下几个步骤:

  1. 初始化RecyclerView 创建 RecyclerLayoutManager,并初始化 AdapterViewHolder
  2. 布局和测量LayoutManager 负责测量和布局 RecyclerView 的子视图,并与 Recycler 协作管理视图的缓存和复用。
  3. 数据绑定Adapter 负责创建和绑定 ViewHolder,将数据绑定到视图上。
  4. 视图回收和复用Recycler 负责管理 ViewHolder 的缓存,包括 Scrap 缓存和 Recycler 缓存,实现视图的高效回收和复用。
  5. 动画和装饰ItemAnimator 负责处理视图的动画效果,ItemDecoration 负责装饰 RecyclerView 的子视图,如添加分隔线。

3. 启发和代码示例

RecyclerView 的设计和实现中可以获得以下启发:

3.1 分离关注点

将视图的创建、数据绑定、布局管理、视图缓存等功能分离到不同的类中,每个类只负责自己的职责,使代码更加清晰和易于维护。

kotlin
复制代码
// ViewHolder 类
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    val textView: TextView = itemView.findViewById(R.id.textView)
}

// Adapter 类
class MyAdapter(private val items: List<String>) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_layout, parent, false)
        return MyViewHolder(view)
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.textView.text = items[position]
    }

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

// Activity 中使用 RecyclerView
class MyActivity : AppCompatActivity() {
    private lateinit var recyclerView: RecyclerView
    private lateinit var adapter: MyAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        recyclerView = findViewById(R.id.recyclerView)
        recyclerView.layoutManager = LinearLayoutManager(this)
        
        val items = listOf("Item 1", "Item 2", "Item 3")
        adapter = MyAdapter(items)
        recyclerView.adapter = adapter
    }
}

3.2 使用缓存提高性能

通过缓存和复用视图提高性能。RecyclerViewRecycler 类通过 Scrap 缓存和 Recycler 缓存管理 ViewHolder,避免重复创建和销毁视图。

kotlin
复制代码
class RecyclerView {
    private val mRecycler = Recycler()

    inner class Recycler {
        private val mAttachedScrap = ArrayList<ViewHolder>()
        private val mCachedViews = ArrayList<ViewHolder>()

        fun getViewForPosition(position: Int): ViewHolder {
            // 先从 Scrap 缓存中获取 ViewHolder
            val scrapView = getScrapViewForPosition(position)
            if (scrapView != null) {
                return scrapView
            }

            // 再从 Recycler 缓存中获取 ViewHolder
            for (i in mCachedViews.indices) {
                val holder = mCachedViews[i]
                if (holder.layoutPosition == position) {
                    mCachedViews.removeAt(i)
                    return holder
                }
            }

            // 最后如果缓存中没有,则创建新的 ViewHolder
            return createViewHolder()
        }

        fun recycleView(view: View) {
            val holder = getChildViewHolder(view)
            if (holder.isScrap) {
                // 如果视图在 Scrap 缓存中,先移除
                mAttachedScrap.remove(holder)
            } else {
                // 否则将视图放入 Recycler 缓存中
                mCachedViews.add(holder)
            }
        }

        private fun createViewHolder(): ViewHolder {
            // 创建新的 ViewHolder
        }
    }

    fun getChildViewHolder(view: View): ViewHolder {
        // 获取 view 对应的 ViewHolder
    }
}

3.3 提供扩展点

通过接口和抽象类提供扩展点,使得 RecyclerView 可以根据需求定制不同的布局管理、装饰和动画效果。

kotlin
复制代码
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL))
recyclerView.itemAnimator = DefaultItemAnimator()

4. 总结

RecyclerView 采用了多种设计模式,如 ViewHolder 模式、Adapter 模式、责任链模式和工厂模式,使其具有高效、灵活和可扩展的特点。这些设计模式的应用,不仅提高了 RecyclerView 的性能,还使其易于定制和扩展。通过学习 RecyclerView 的设计和实现,可以获得许多关于分离关注点、使用缓存和提供扩展点的启发,这些都是编写高效和可维护代码的重要原则。