被我忽视的更新
androidx recyclerview 1.2.0-alpha02
版本添加了新功能 MergeAdapter,帮助开发者更容易地为 RecyclerView 添加 Header 和 Footer。详情参见 【译】MergeAdapter 的使用 使用官方 API 为 Recyclerview 添加 Header 和 Footer
该版本中还有一个改动:「RecyclerView.Adapter
lazy state restoration」,帮助开发者恢复 RecyclerView 的状态
recyclerview update
我对这个功能并没有什么感觉。众所周知,Android 中的 View 内部是有着状态保存和恢复的方法的。RecyclerView 也是如此,它可以恢复自身已滚动的位置
我对这个功能并没有什么感觉。众所周知,Android 中的 View 内部是有着状态保存和恢复的方法的。RecyclerView 也是如此,它可以恢复自身已滚动的位置
View 内部恢复状态
有关状态保存的内容可以参见 【背上Jetpack】绝不丢失的状态 androidx SaveState ViewModel-SaveState 分析
真实情况也是如此
RecyclerView 内部可以恢复滚动位置
意外发现
最近看到 Florina Muntenescu 的 Restore RecyclerView scroll position ,其中介绍了 「RecyclerView.Adapter
lazy state restoration」,这勾起了我的兴趣
意外发现
如文中描述,RecyclerView 在 activity/fragment 重建时失去滚动位置是因为 Adapter 中的数据是 「异步」 加载的,当 RecyclerView layout 时数据并没有加载,因此也恢复不了之前的位置状态。一个比较简单的例子是使用 Navigation 组件进行导航,返回时 fragment 中的 RecyclerView 由于再次调用接口获取数据,导致其滑动位置失去
延迟加载数据,无法恢复滚动位置
解决方案
有几种方法可以保证 RecyclerView 恢复到正确的滚动位置,最好的办法是借助缓存,ViewModel 或 Repository 中缓存要显示的数据,确保始终在第一个布局传入前在 Adapter 上设置数据。也有一些其他的方案,这些方案要么太复杂,要么不够优雅
recyclerview:1.2.0-alpha02
中的解决方案是提供一个新的 Adapter 方法,该方法允许设置状态恢复策略,它有三个选项
ALLOW
这是 「默认」 的状态,它会立即恢复 RecyclerView 的状态,该种策略无法解决延迟加载的数据的问题,可以使用 PREVENT_WHEN_EMPTY
PREVENT_WHEN_EMPTY
仅当 Adapter 不为空(adapter.getItemCount() > 0)时,才恢复 RecyclerView 状态。 如果您的数据是异步加载的,那么 RecyclerView 会一直等到数据加载完毕,然后状态才能恢复。 如果您有默认 item(例如 Header 或 加载指示器)作为适配器的一部分,则应该使用PREVENT
选项,除非使用 MergeAdapter 添加了默认 item。 MergeAdapter 等待所有适配器准备就绪,然后才恢复状态
PREVENT
状态不会恢复,直到配置了 ALLOW
或者 PREVENT_WHEN_EMPTY
使用方式如下:
adapter.stateRestorationPolicy = PREVENT_WHEN_EMPTY
复制代码
「加入了上面的配置后即使是异步加载数据也能恢复 RecyclerView 的位置」
设置 PREVENT_WHEN_EMPTY
追踪引入过程
老规矩,我们沿着官方的 commit log 来看看其实现原理
首先我们看看 IssueTracker 上提的 Feature
IssueTracker
表达的意思也很简单,就是当加载异步数据时 RecyclerView 的位置状态无法恢复,Adapter 应该提供相关的解决方案
有意思的是,实现该功能时还重新实现了前一个版本的逻辑,我在 git commit log 中看到了 revert 操作
revert操作
为了防止 LayoutManager#onRestore
执行多次,没有采用最开始的实现方式。
但 Yigit Boyar (这个提交的开发者) 仍然希望使用最开始的实现方式,但是 LayoutManager#onRestoreInstance
的状态时 public ,因此只能选取一个折中的方案
新的实现方案
无奈之举
过去,开发者会无意间调用 onRestoreInstanceState(State)
方法。例如,一些开发者已使用它来手动设置自己更新的状态,这样即使在此状态之前已恢复,在此处传递状态也将导致 LayoutManager 接收它并相应地更新其内部状态。因此,即使看起来好像很奇怪,也必须始终调用 requestLayout
来保留功能
源码分析
接下来我们来分析这部分源码,内容很少,所以我们详细看下
首先是引入 StateRestorationPolicy
的枚举
然后需要提供 setStateRestorationPolicy
和 getStateRestorationPolicy
方法,此时我们还需要一个方法来判断是否要将 SavedState 传递给 LayoutManager
前面的 setStateRestorationPolicy
方法中 调用了 notifyStateRestorationPolicyChanged
,而 notifyStateRestorationPolicyChanged
为静态类 AdapterDataObservable
中的方法,该类中的其他方法我们也很熟悉,均是刷新 Adapter 中数据的方法。
notifyStateRestorationPolicyChanged
而 notifyStateRestorationPolicyChanged
中调用了 mObservers list 中元素的 onStateRestorationPolicyChanged
方法,通过源码我们得知该 list 中的元素类型为 AdapterDataObserver
,因此还需要在 AdapterDataObserver
中加入 onStateRestorationPolicyChanged
方法
onStateRestorationPolicyChanged
该方法是个空实现,而 RecyclerViewDataObserver
重写了该方法
RecyclerViewDataObserver
配置恢复策略以及恢复策略变化时的监听都有了,接下来要做的就是如果之前有待恢复的装则恢复之前的状态
恢复状态
❝注意:发布之前
❞StateRestorationPolicy
叫做StateRestorationStrategy
,后来命名为StateRestorationPolicy
,alpha 版本的库可能随时更改 API 的命名和删除 API,因此查看这部分源码的同学请注意
至此,相关的源码都在这里了
总结
StateRestorationPolicy
提供了 RecyclerView 异步加载数据恢复滚动位置的解决方案。原理就是通过配置 StateRestorationPolicy
来改变恢复策略,同时在策略改变时调用 requestLayout
方法。在 dispatchLayoutStep2()
(该方法会在 onLayout 和 measure 方法中调用) 方法中恢复状态(如果 canRestoreState()
返回 true)
「一点思考:我们都知道 ViewPager2 是使用 RecyclerView 实现的,那么借助本文介绍的 API 可以做点什么吗?」
欢迎各位小伙伴在评论区留言,说说你的想法
作者:Flywith24
链接:https://juejin.im/post/5eba4d5f6fb9a0436545b6e5
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
来源:oschina
链接:https://my.oschina.net/JiangTun/blog/4285944