☰
【Android面试】RecylerView专题
2026/10/19 1:35:15 网站建设 项目流程

文章目录

  • 一、基础原理与复用缓存
    • 1. RecyclerView 与 ListView 的核心区别是什么?
    • 2. 四级缓存(mAttachedScrap、mCachedViews、mViewCacheExtension、mRecyclerPool)各自作用、优先级与降级策略,不同滑动 / 刷新场景的命中逻辑?
    • 3. mAttachedScrap 与 mCachedViews 的本质区别,为何 detach 而非 remove?
    • 4. ViewHolder 定义、复用触发条件、创建时机,放入 RecycledViewPool 的条件?如何自定义复用池大小与策略?
    • 5.onBindViewHolder 重复调用的原因及优化方案?
    • 6. 预取(Prefetch)机制原理、LinearLayoutManager 预取距离 / 数量计算逻辑?
    • 7. mAttachedScrap和mChangedScrap的区别
  • 二、LayoutManager 布局
    • 1. LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager 的布局逻辑差异?
    • 2. onLayoutChildren 完整执行流程(锚点定位、填充、回收),自定义 LayoutManager 如何处理异常布局?
    • 5. 自定义 LayoutManager 核心方法(measureChild、layoutDecorated、scrollVerticallyBy/scrollHorizontallyBy)作用,dx/dy 如何正确消费?
    • 6. GridLayoutManager Span 分配机制,动态 SpanSize 布局冲突解决方案?
    • 7. 实现多布局的思路,无限循环 LayoutManager 需规避的边界问题?
  • 三、Adapter 与数据刷新
    • 8. notifyDataSetChanged、notifyItemChanged、notifyItemRangeChanged 的区别与性能影响?
    • 9. DiffUtil 核心算法(Myers)原理、时间 / 空间复杂度,自定义 DiffUtil.Callback 方式?
    • 10.AsyncListDiffer 异步计算逻辑、线程池策略,如何避免主线程阻塞?
    • 11. payload 精准局部刷新原理
    • 12. notifyItemMoved 底层实现,为何移动操作会导致其他 Item 重复 bind?
    • 13. RecylerView实现局部刷新的原理
  • 四、分割线、动画与交互
    • 13. 自定义 ItemDecoration 核心方法(getItemOffsets、onDraw、onDrawOver)作用?
    • 14. DefaultItemAnimator 原理,自定义增删改动画的思路?
    • 15. ItemTouchHelper 工作原理,侧滑删除、拖拽排序的实现逻辑?
    • 16. RecyclerView 点击 / 长按事件最佳实现方式?
    • 17. ItemAnimator 各动画方法(animateChange/animateRemove)执行时机与视图层级操作?
  • 五、滑动机制
    • 18. NestedScrollingChild2/3 嵌套滑动完整流程?
    • 19. 内外层 RecyclerView 嵌套滑动事件分发拦截逻辑,如何实现协同滑动?
    • 20. RecyclerView 与 CoordinatorLayout/AppBarLayout 联动的 Behavior 原理?
    • 21. 自定义惯性滑动(fling),修改摩擦系数与速度的方式?
    • 22. onScrollStateChanged 三种状态切换逻辑?
    • 23. 请完整描述 RecyclerView 从手指按下到滑动停止的全流程机制?包括事件分发、scroll 处理、View 填充与回收、惯性滑动、嵌套滑动的整体逻辑。***
    • 24. RecyclerView 为什么滑动时不会一次性把所有 Item 布局出来?它是如何做到 “边滑边布局、边滑边回收” 的?***
  • 六、性能优化与内存
    • 1. 原题:RecyclerView 卡顿、OOM 常见原因及优化方案?
    • 2. 大数据量(万级 / 10w+)分页加载、缓存池优化方案?
    • 3. 滑动时频繁 GC 规避方案,过度绘制排查?
    • 4. ViewHolder/Adapter 内存泄漏场景及根治方案,离屏渲染使用场景与副作用?
  • 七、源码级细节
    • 1. RecyclerView 绘制流程(dispatchDraw)与 ItemView 绘制顺序?
    • 2. GapWorker 机制在预取与异步布局中的作用?
    • 3. tryGetViewHolderForPosition 方法完整执行逻辑?
    • 4. “scrap” 与 “recycle” 核心概念理解?
    • 5. 粘性头部(Sticky Header)实现思路?
    • 八. 补充题目
    • 1. RecyclerView 的核心组件有哪些,各自如何协作?
    • 2. setHasFixedSize(true) 何时用
    • 3. Paging 3 如何接入 RecyclerView?
    • 4. ItemTouchHelper 拖拽排序?
    • 5. 关于PagingDataAdapter
    • 6. 快速连续 submitList,旧结果会覆盖新列表吗?
    • 7. 返回页面或刷新后,滚动位置为什么丢失?
    • 8. 补充:Paging3的getRefreshKey

Android 高级工程师:RecyclerView 高频 20 问,从数据更新到复用与性能排查
【Android 源码】RecylerView的深入理解

一、基础原理与复用缓存

1. RecyclerView 与 ListView 的核心区别是什么?

  • 架构解耦:RecyclerView将布局(LayoutManager)、复用(Recycler)、动画(ItemAnimator)、分割线(ItemDecoration)模块化,ListView高度耦合。
  • 缓存机制:RecyclerView采用四级缓存(mAttachedScrap、mCachedViews、mViewCacheExtension、mRecyclerPool),ListView仅两级缓存。
  • 布局能力:RecyclerView 支持线性、网格、瀑布流及自定义布局,ListView 仅支持线性布局。
  • 刷新机制:RecyclerView 支持 DiffUtil 局部精准刷新,ListView 仅支持全局
    notifyDataSetChanged。
  • 嵌套滑动:RecyclerView 实现NestedScrolling 接口,支持嵌套滑动,ListView 嵌套易卡顿。
    快速记忆:解耦强、缓存多、布局全、刷新准、嵌套顺。

2. 四级缓存(mAttachedScrap、mCachedViews、mViewCacheExtension、mRecyclerPool)各自作用、优先级与降级策略,不同滑动 / 刷新场景的命中逻辑?

  • 优先级:mAttachedScrap > mCachedViews > mViewCacheExtension > mRecyclerPool
  • mAttachedScrap/mChangedScrap(屏幕内缓存):存储屏幕内 detach 的 View,保留完整状态,布局重构时直接复用,无需 rebind。单次 onLayoutChildren 结束自动清空。
  • mCachedViews(屏外缓存):缓存刚滑出屏幕的 ViewHolder(默认 2 个),保留状态,回滑时优先命中,无需rebind。销毁时机:被复用 / 队列满下沉 / 全局刷新 / 页面销毁
  • mViewCacheExtension(自定义缓存):开发者扩展的中间层缓存,优先级高于复用池。
  • mRecyclerPool(复用池):按 ViewType 分组缓存,清空 View 状态,复用需重新 bind,支持跨 Adapter 复用。销毁时机:手动 clear / RecyclerView 销毁
  • 场景:快速滑动优先命中 Pool;回滑优先命中 Cache;布局刷新优先命中 Scrap。

快速记忆:屏内 Scrap → 近屏 Cache → 自定义 → 复用池;越靠前,复用成本越低。

3. mAttachedScrap 与 mCachedViews 的本质区别,为何 detach 而非 remove?

  • mAttachedScrap:存储屏幕内可见 View,通过 detach 临时分离,用于布局重构(如旋转、局部刷新),视图层级保留。
  • mCachedViews:存储刚滑出屏幕的 View,完整保留 View 状态,用于快速回滑复用。
  • detach vs remove:detach 仅临时脱离父容器,保留视图属性与状态;remove 完全移除视图,需重新 addView 重建

快速记忆:Scrap 管屏内、Cache 管屏外;detach 暂离、remove 销毁。

4. ViewHolder 定义、复用触发条件、创建时机,放入 RecycledViewPool 的条件?如何自定义复用池大小与策略?

  • 定义:ViewHolder 是一种用于优化列表项(如 ListView 或RecyclerView)性能的设计模式。它的核心思想是将视图的引用缓存起来,避免每次 getView() 调用时都去查找视图控件(即减少 findViewById() 的调用次数)
  • 复用条件:ViewType 一致、position 匹配、缓存命中。
  • 创建时机:各级缓存均未命中时,通过 Adapter.onCreateViewHolder 创建。
  • 入池条件:滑出屏幕、CachedViews 已满、非固定位置 View。
  • 自定义复用池大小与策略:通过 setRecycledViewPool () 设置自定义池,setMaxRecycledViews (viewType, max) 配置单类型容量。

快速记忆:同类型复用;无缓存创建;Cache 满入池;可自定义容量。

5.onBindViewHolder 重复调用的原因及优化方案?

  • 原因:数据刷新、布局重测、复用池重建、焦点变更、嵌套滑动冲突。
  • 优化:使用 payload 实现局部刷新;开启 setHasFixedSize (true) 减少重测;避免 Item 布局动态变更;降低notify 调用频率。

快速记忆:刷新 / 布局 / 复用导致;payload + 固定尺寸 + 少通知。

6. 预取(Prefetch)机制原理、LinearLayoutManager 预取距离 / 数量计算逻辑?

  • 原理:通过GapWorker 异步线程,在滑动时提前加载下一屏 Item,降低滑动卡顿。
  • 计算:LinearLayoutManager 根据滑动速度、屏幕剩余空间计算预取距离(默认一屏),预取数量 = 当前可见 Item 数 +预取 Item 数

快速记忆:GapWorker 异步预取;按速度 + 空间取一屏。

7. mAttachedScrap和mChangedScrap的区别

在 RecyclerView 中,mAttachedScrap和mChangedScrap都属于屏幕内缓存。

  • mAttachedScrap缓存的是屏幕内位置发生改变,但数据未发生变化的ViewHolder 。

  • mChangedScrap用于缓存数据已改变的 ViewHolder

二、LayoutManager 布局

1. LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager 的布局逻辑差异?

  • LinearLayoutManager:线性排列,单行或单列,布局方向可控。
  • GridLayoutManager:网格布局,均分 Span 宽度 / 高度,支持跨行跨列。
  • StaggeredGridLayoutManager:瀑布流布局,Item 高度 / 宽度不定,自动填充空白,无均分规则。

快速记忆:Linear 线、Grid 格、Staggered 流。

2. onLayoutChildren 完整执行流程(锚点定位、填充、回收),自定义 LayoutManager 如何处理异常布局?

流程:

  • 确定布局锚点(锚 View 或锚位置)
  • 清空 Scrap 与缓存
  • 向上下 / 左右填充可见区域
  • 回收超出屏幕的 View
  • 调整边界偏移。

异常处理:处理边界越界、数据为空、锚点失效等场景,避免布局崩溃

快速记忆:定锚→清缓存→填充→回收→调偏移。

5. 自定义 LayoutManager 核心方法(measureChild、layoutDecorated、scrollVerticallyBy/scrollHorizontallyBy)作用,dx/dy 如何正确消费?

  • measureChild:测量子 View 尺寸。
  • layoutDecorated:布局子 View(包含分割线偏移)。
  • scrollBy:处理滑动偏移,返回实际消费的 dx/dy。
  • dx/dy 消费:表示横向/纵向滑动的距离(水平/垂直方向 )。根据滑动方向与边界限制,计算可滑动距离,返回实际滚动值。

快速记忆:测尺寸、布视图、滑偏移;返回实际滚动量。

6. GridLayoutManager Span 分配机制,动态 SpanSize 布局冲突解决方案?

  • 通过setSpanSizeLookup() 自定义每个 Item 所占 Span 数量;
  • 动态变更 Span 时需调用 notify 触发重布局,避免布局错位;处理边界 Span 分配防止越界。

快速记忆:SpanLookup 定占比;动态改需重布局。

7. 实现多布局的思路,无限循环 LayoutManager 需规避的边界问题?

  • 多布局:通过getItemViewType区分类型,对应多 ViewHolder。
  • 无限循环:getItemCount 返回极大值,position 取模映射;规避滑动卡顿、边界闪烁、焦点错乱问题。

快速记忆:ViewType 分类型;取模做循环。

三、Adapter 与数据刷新

8. notifyDataSetChanged、notifyItemChanged、notifyItemRangeChanged 的区别与性能影响?

  • notifyDataSetChanged:全局刷新,所有 Item 重建 rebind,性能最差。
  • notifyItemChanged:单 Item 刷新,支持 payload 局部刷新,性能中等。
  • notifyItemRangeChanged:批量局部刷新,性能最优。

快速记忆:全局最差、单条中等、批量最优。

9. DiffUtil 核心算法(Myers)原理、时间 / 空间复杂度,自定义 DiffUtil.Callback 方式?

  • 原理:基于 Eugene W. Myers 差分算法,计算新旧数据集最小差异集合(增删改移)。复杂度:O (N+D),N 为数据量,D为差异数。
  • 自定义:继承 DiffUtil.Callback,实现getOldListSize、getNewListSize、areItemsTheSame、areContentsTheSame、getChangePayload。

快速记忆:差分找最小差异;O (N+D);五步回调自定义。

10.AsyncListDiffer 异步计算逻辑、线程池策略,如何避免主线程阻塞?

在后台线程执行 Diff 计算,计算完成后主线程应用更新结果;使用独立线程池避免阻塞;通过锁保证数据一致性。
快速记忆:后台算、主线更;线程池防阻塞。

11. payload 精准局部刷新原理

  • 在 Android 中,RecyclerView 的 局部刷新是通过 notifyItemRangeChanged(int
    positionStart, int itemCount, Object payload) 实现的。其核心原理是 利用 payload
    参数传递差异信息,只刷新 item 中需要更新的部分 UI,而不是整个 item。

12. notifyItemMoved 底层实现,为何移动操作会导致其他 Item 重复 bind?

  • 移动操作改变相邻 Item 位置,触发位置校验与 rebind;
  • 优化:减少移动范围,使用 DiffUtil 批量处理移动。

快速记忆:移动改位置→相邻重绑;Diff 批量优化。

13. RecylerView实现局部刷新的原理

RecyclerView 实现局部刷新主要依赖于精准的数据集变化计算以及对应的视图更新机制

  • DiffUtil 算法确认数据集变化,生成DiffResult
  • 通知 RecyclerView 更新,将DiffResult中的变化信息传递给 RecyclerView。notifyItemX(position) 或者 notifyItemChanged(int position, @Nullable Object payload)用于通知指定位置的 item 发生了变化,并且可以携带“payload”信息。

四、分割线、动画与交互

13. 自定义 ItemDecoration 核心方法(getItemOffsets、onDraw、onDrawOver)作用?

  • getItemOffsets:设置 Item 四周间距,占用布局空间。

  • onDraw:在 ItemView 下层绘制(如分割线)。

  • onDrawOver:在 ItemView 上层绘制(如角标、遮罩)。

快速记忆:Offsets 占空、Draw 下层、Over 上层。

14. DefaultItemAnimator 原理,自定义增删改动画的思路?

  • 原理:默认处理 Item 增删改移的过渡动画;
  • 自定义:继承 SimpleItemAnimator,重写animateAdd、animateRemove、animateChange、animateMove。
    快速记忆:四动画(增删改移);重写方法自定义。

15. ItemTouchHelper 工作原理,侧滑删除、拖拽排序的实现逻辑?

  • 监听滑动 / 拖拽手势,回调 onSwiped(侧滑)、onMove(拖拽)
  • 实时更新 ViewHolder 偏移,同步修改数据源。

快速记忆:侧滑删、拖拽移;回调改数据。

16. RecyclerView 点击 / 长按事件最佳实现方式?

在 ViewHolder 中绑定点击事件,避免重复设置;通过接口回调解耦 Adapter 与业务层。

17. ItemAnimator 各动画方法(animateChange/animateRemove)执行时机与视图层级操作?

  • animateChange:Item 内容变更时执行,处理新旧视图过渡。
  • animateRemove:Item 移除时执行,处理视图退场动画。
  • 层级:在 dispatchDraw 阶段执行动画,不阻塞布局流程。

快速记忆:改时 Change、删时 Remove;绘制阶段执行。

五、滑动机制

18. NestedScrollingChild2/3 嵌套滑动完整流程?

  • 子 View 发起滑动 → 父 View 预拦截 → 父子协同消费滑动距离 → 惯性滑动联动;Child3 支持链式传递。

快速记忆:子发→父拦→协同消费。

19. 内外层 RecyclerView 嵌套滑动事件分发拦截逻辑,如何实现协同滑动?

  • 按方向分流:父层拦截一个方向,子层处理另一方向;
  • 通过requestDisallowInterceptTouchEvent控制父层拦截。

快速记忆:分方向拦截;禁止父拦截。

20. RecyclerView 与 CoordinatorLayout/AppBarLayout 联动的 Behavior 原理?

  • Behavior监听嵌套滑动,根据偏移量动态调整关联 View(如折叠 AppBar)
  • 重写onNestedScroll处理联动逻辑。

快速记忆:监听滑动;联动偏移。

21. 自定义惯性滑动(fling),修改摩擦系数与速度的方式?

  • 重写 fling () 方法;修改 VelocityTracker 速度、ViewConfiguration 摩擦系数,控制惯性滑动距离。

快速记忆:改速度 + 摩擦;控惯性。

22. onScrollStateChanged 三种状态切换逻辑?

  • IDLE:静止状态。
  • DRAGGING:手指触摸拖动。
  • SETTLING:手指离开后的惯性滑动。
  • 切换:触摸→DRAGGING;松开→SETTLING;停止→IDLE。

快速记忆:静、拖、滑。

23. 请完整描述 RecyclerView 从手指按下到滑动停止的全流程机制?包括事件分发、scroll 处理、View 填充与回收、惯性滑动、嵌套滑动的整体逻辑。***

  • 事件分发:触摸事件经由 onInterceptTouchEvent 判断是否拦截,按下时记录起点坐标,移动时判定为滑动意图后开始消费事件。
  • 手指拖动:计算偏移 dy/dx → 通过 scrollByInternal 消费距离 → 触发LayoutManager 的 scrollVerticallyBy/scrollHorizontallyBy → 调用 fill()方法填充新可见区域、回收滑出屏幕的 Item。
  • 填充与回收逻辑:滑动时先从缓存获取 ViewHolder,填充到新位置;超出屏幕的 View 按优先级存入 CachedViews 或RecycledViewPool。
  • 惯性滑动:手指抬起时通过 VelocityTracker 获取滑动速度 → 交给 OverScroller 计算减速轨迹 → 通过 computeScroll() 持续回调更新偏移 → 同步执行填充与回收。
  • 嵌套滑动:支持遵循 NestedScrollingChild2/3 机制,滑动前先询问父 View 是否消费部分距离,实现协同滑动(如与AppBarLayout 联动)。
  • 滑动停止: 惯性速度衰减为 0 或手动打断滑动时,状态切换为 IDLE,完成一次完整滑动流程。

快速记忆:事件拦截 → 计算偏移 → 填充回收 → 惯性滚动 → 嵌套协同 → 停止 idle。

24. RecyclerView 为什么滑动时不会一次性把所有 Item 布局出来?它是如何做到 “边滑边布局、边滑边回收” 的?***

  • RecyclerView 不会全量布局,而是采用可见区域驱动的增量布局:
  • 只在onLayoutChildren 和滑动时布局当前屏幕可见范围的 Item。
  • 滑动过程中通过fill() 方法动态向滑动方向填充即将显示的 Item,同时回收反方向超出屏幕的 Item。
  • 配合四级缓存实现 ViewHolder 复用,避免频繁创建销毁 View。
  • 结合预取机制提前加载下一屏 Item,保证滑动流畅。

快速记忆:只布局可见区域;滑动填充 + 回收;缓存复用;预取兜底。

六、性能优化与内存

1. 原题:RecyclerView 卡顿、OOM 常见原因及优化方案?

  • 卡顿:onBind 耗时、布局复杂、过度绘制、频繁 GC。
  • OOM:图片过大、复用池溢出、内存泄漏。
  • 优化:异步加载、简化布局、减少对象创建、复用池调优、图片压缩。

快速记忆:耗时 / 复杂 / 泄漏→卡顿 OOM;异步 + 简化 + 复用。

2. 大数据量(万级 / 10w+)分页加载、缓存池优化方案?

  • 分页加载 + DiffUtil 局部刷新;扩容复用池;图片三级缓存;避免动态布局。

快速记忆:分页 + 差分 + 大复用池 + 图缓存。

3. 滑动时频繁 GC 规避方案,过度绘制排查?

  • GC:避免 onBind 创建对象,使用对象池。
  • 过度绘制:减少重复背景、使用 clipRect、精简 onDrawForeground 逻辑。

快速记忆:少创建防 GC;少背景防过度绘。

4. ViewHolder/Adapter 内存泄漏场景及根治方案,离屏渲染使用场景与副作用?

  • 泄漏:ViewHolder 持有 Context、Adapter 匿名内部类 → 弱引用、静态内部类。

  • 离屏渲染:setLayerType 开启,用于复杂动画;副作用:占用显存,低机型卡顿。

快速记忆:弱引用防泄漏;离屏渲染慎开。

七、源码级细节

1. RecyclerView 绘制流程(dispatchDraw)与 ItemView 绘制顺序?

  • drawBackground → dispatchDraw(绘制子 View)→ drawForeground → 绘制
    ItemDecoration。

快速记忆:背景→子项→前景→分割线。

2. GapWorker 机制在预取与异步布局中的作用?

  • 异步线程执行预取、布局计算,分担主线程压力,提升滑动流畅度。

快速记忆:异步干活;主线减负。

3. tryGetViewHolderForPosition 方法完整执行逻辑?

  • 查找 Scrap → 查找 Cache → 查找 Extension → 查找 Pool → 未命中则创建 → 绑定数据。

快速记忆:逐级查缓存;无则新建。

4. “scrap” 与 “recycle” 核心概念理解?

  • scrap:临时 detach,保留状态,快速复用
  • recycle:完全回收,清空状态,进入复用池

快速记忆:scrap 暂存;recycle 清空。

5. 粘性头部(Sticky Header)实现思路?

  • 通过 ItemDecoration.onDrawOver 在顶层绘制固定头部;
  • 监听滑动,动态更新头部内容与位置。

快速记忆:Decoration 上层画;滑动更内容。

八. 补充题目

1. RecyclerView 的核心组件有哪些,各自如何协作?

RecyclerView 将数据绑定、布局、装饰和动画拆分为不同职责:Adapter提供条目数量、类型并创建和绑定 ViewHolder;ViewHolder保存条目视图引用;LayoutManager负责条目的测量、布局与滚动;ItemDecoration提供间距和装饰绘制;ItemAnimator处理条目更新动画。RecyclerView 协调这些组件,并通过 Recycler 管理 ViewHolder 的获取与复用,支持多类型、动态更新及复杂排布的列表。
为什么要将布局交给 LayoutManager?
将布局与数据绑定分开,同一套条目绑定逻辑可配合线性、网格等布局策略;切换时仍需验证条目尺寸、跨度和滚动状态。
使用 RecyclerView 就能保证列表流畅吗?

2. setHasFixedSize(true) 何时用

一句话:告诉 RecyclerView,列表整体尺寸不会随着 item 内容变化而改变,帮 RecyclerView 跳过重复测量,提升性能。
✅ 适合:RecyclerView 写死宽高(match_parent、固定 dp),item 增删不会改变 RV 容器大小。

比如全屏列表,RV 占满屏幕,item 再多 RV 的大小不变,就可以打开 true。

  • NestedScrollView 包 RV 能设吗?
    不能,严禁设置 true,必须 false。
    原因:
    NestedScrollView 的高度本身是wrap_content特性,它要测量全部子 View 的总高度。
    RV 放在里面,RV 的整体高度取决于 item 的总高度,item 数量变化,RV 的总高度就会变化。
    setHasFixedSize(true)的含义就是告诉 RV:你的容器总高度固定不变,但这里 RV 高度是动态变化的,和这个前提矛盾。
    如果强行写 true,会出现测量异常,列表显示不全、空白、item 不渲染。

结论:NestedScrollView 包裹 RV →setHasFixedSize(false)

  • 和 wrap_content 冲突?
    setHasFixedSize(true)要求 RecyclerView 本身不是 wrap_content。

  • 当 RV 布局宽高是match_parent或者固定 dp:RV 容器尺寸固定,才可以true

  • 当 RV 高度是wrap_content:RV 整体高度会随 item 变化,不能开启 true

3. Paging 3 如何接入 RecyclerView?

Paging3 接入 RecyclerView,核心是使用PagingDataAdapter,它继承 RecyclerView.Adapter,内置 DiffUtil,专门接收 PagingData。整体分 4 步:

  1. 定义 PagingDataAdapter
    继承PagingDataAdapter<T, VH>,传入 DiffUtil.ItemCallback,用来做 item 差异比对,实现增量更新。和普通 RV Adapter 写法接近,只需要实现 onCreateViewHolder、onBindViewHolder,内部用getItem(position)拿数据。
  2. ViewModel 中构建 Pager,生成 Flow<PagingData>
    通过 Pager 配置分页参数、PagingSource,有离线缓存场景就加上 RemoteMediator,最后.cachedIn(viewModelScope)缓存数据流,屏幕旋转不用重新加载。
  3. Activity/Fragment 里订阅 Flow,提交 PagingData 给 Adapter
    用lifecycleScope+collectLatest订阅分页 Flow,拿到 PagingData 后调用adapter.submitData(pagingData)。PagingDataAdapter 内部自动处理分页数据合并、Diff 增量刷新,不用自己维护列表集合。
  4. 绑定 RecyclerView
    rv.adapter = adapter,设置 LayoutManager。还可以监听adapter.loadStateFlow,处理加载中、失败、空态、重试逻辑;也能用withLoadStateFooter添加底部加载 / 错误 Footer。

4. ItemTouchHelper 拖拽排序?

ItemTouchHelper 通过 onMove/onSwiped 提供拖拽和侧滑回调。拖拽时必须先更新 Adapter 实际读取的数据顺序,再发出对应移动通知,确保数据与界面一致。对普通 Adapter,可维护独立的可变工作列表并 notifyItemMoved;对 ListAdapter,不得修改 currentList 或共享的旧对象,且不能只手工通知移动却保留旧数据。

如果每次移动都 submitList,新顺序会异步应用,必须考虑拖拽事件与提交结果的时序。需要即时反馈时,可在拖拽期间使用明确管理工作列表的 Adapter,结束后提交不可变快照并持久化排序;同一次更新不能再重复发 notify 与 Diff 通知。

  • 拖拽与 diff 冲突?

冲突根源:拖拽时手动修改内存数据源,DiffUtil 在后台异步对比新旧数据快照,状态不一致,引发闪烁、位置错乱。
解决方案:拖拽过程只保留 ItemTouchHelper 自带的 UI 视觉动画,不修改数据源、不手动 notify。拖拽松手完成后,生成一份更新后的完整数据集,调用 submitList 提交给 Adapter,由 DiffUtil 统一计算差异刷新。

  • 拖拽期间收到服务端新列表怎么办?

优先做状态互斥:拖拽进行中,暂存服务端返回的新数据,不直接提交给 Adapter;等拖拽手势结束、松手完成之后,再把缓存的新列表执行 submitList 刷新。

5. 关于PagingDataAdapter

普通 Adapter 需要自己维护 List、手动 notify;ListAdapter 自带 Diff,提交完整列表做局部刷新,但一次性载入所有数据。PagingDataAdapter 是 ListAdapter 的子类,基于 PagingData 实现按需预加载、自动释放远端页内存,自带分页状态回调,适合大数据分页场景。

6. 快速连续 submitList,旧结果会覆盖新列表吗?

快速连续调用submitList,旧的 Diff 任务会被直接丢弃,最终只会生效最后一次提交的新列表,不会出现旧结果覆盖新列表。
底层是AsyncListDiffer的版本号机制,每次 submitList 都会递增 generation 版本标记。如果上一次 Diff 还在后台计算,这时又提交新列表,旧 Diff 任务完成后会判断版本号过期,直接放弃应用结果,只执行最新一次提交的 Diff 结果。

7. 返回页面或刷新后,滚动位置为什么丢失?

滚动位置丢失本质:RecyclerView 的 LayoutManager 保存滚动位置,依赖当前内存里的数据集;数据集被完全替换、Adapter 重建或者页面重建时,旧的位置锚点找不到对应的 item,滚动位置就丢了。

  1. 普通 ListAdapter submitList 全量刷新
    调用 submitList 提交全新列表,旧数据集直接替换。LayoutManager 记录的 position 对应的旧 item 不存在了,滚动位置丢失。

注意:如果只是列表局部变更(增删少量 item),Diff 能维持位置;全量替换数据集最容易丢位置。

  1. PagingData 场景(PagingDataAdapter 重点)
    Pager 重新创建、PagingSource 刷新(下拉刷新),旧 PagingData 失效,内存中已加载的页面被清空。LayoutManager 找不到之前的锚点数据,滚动位置丢失。

Paging3 提供getRefreshKey,用来在刷新时计算锚点,恢复滚动位置,这就是它的作用。

  1. 页面重建(屏幕旋转、配置变更)
    默认 ViewModel 能保存数据,但如果没有用cachedIn(viewModelScope),分页 Flow 会重建,重新加载数据,RV 状态丢失;或者没保存 LayoutManager 的状态。

  2. 普通 List:不要频繁全量 submitList;需要保存位置时,在刷新前取出 LayoutManager 的onSaveInstanceState,刷新完成后恢复。

  3. Paging3:正确实现 PagingSource 的 getRefreshKey,用来计算锚点位置,下拉刷新后恢复滚动位置;同时分页 Flow 要加.cachedIn(viewModelScope),配置变更不复加载。

8. 补充:Paging3的getRefreshKey

getRefreshKey是 PagingSource 的锚点计算方法,下拉刷新、页面重新加载的时候,Paging3 调用它,根据当前屏幕正在显示的条目,算出刷新后要加载的页码,用来恢复滚动位置。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询