如何在设备旋转时保存RecyclerView.Adapter的数据?
解决RecyclerView拖拽排序后配置变化丢失排序的问题
看起来你遇到的核心痛点是:onSaveInstanceState 的存储容量有限,没法存大量的 AppData 对象,导致旋转屏幕或Activity销毁重建后,辛苦拖拽好的排序直接丢失了。下面给你几个实用的解决方案,按场景优先级排序:
方案1:用ViewModel保留数据(最适合配置变化场景)
ViewModel的生命周期完全独立于Activity的配置变化(比如旋转屏幕),只要把你的数据List放在ViewModel里,旋转时数据不会被销毁重建,自然就能保留排序状态。
具体步骤:
- 先创建一个继承自
ViewModel的类,专门用来持有你的数据:
class AppDataViewModel : ViewModel() { // 用MutableLiveData是为了数据变化时能自动通知UI更新,也可以直接用MutableList val appDataList = MutableLiveData<MutableList<AppData>>() // 初始化数据的方法,避免重复加载 fun initData(initialList: MutableList<AppData>) { if (appDataList.value.isNullOrEmpty()) { appDataList.value = initialList } } }
- 在你的Activity/Fragment中获取ViewModel实例,用它来管理所有数据操作:
class YourActivity : AppCompatActivity() { private lateinit var viewModel: AppDataViewModel private lateinit var adapter: YourAdapter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 获取ViewModel实例 viewModel = ViewModelProvider(this)[AppDataViewModel::class.java] // 初始化数据:如果ViewModel里没有数据,就从原始来源加载(比如本地资源、网络) val initialData = loadInitialAppData() viewModel.initData(initialData) // 观察数据变化,自动更新Adapter viewModel.appDataList.observe(this) { sortedList -> adapter.submitList(sortedList) } // 初始化RecyclerView和拖拽功能 adapter = YourAdapter(viewModel.appDataList.value ?: mutableListOf()) recyclerView.adapter = adapter setupDragAndDrop() } // 拖拽交换位置时,直接操作ViewModel里的List private fun onItemDragged(fromPosition: Int, toPosition: Int) { val currentList = viewModel.appDataList.value ?: return Collections.swap(currentList, fromPosition, toPosition) viewModel.appDataList.postValue(currentList) // 通知UI刷新 } }
这样旋转屏幕时,ViewModel会稳稳保留住排序后的List,Activity重建后直接复用ViewModel里的数据,排序自然不会丢。
方案2:本地持久化(适合需要长期保存排序的场景)
如果不仅是配置变化,连APP重启后都想保留排序,那就要把排序状态持久化到本地。推荐用Room数据库,也可以用SharedPreferences(适合数据量不大的情况)。
用Room的示例:
- 修改
AppData为Room实体类,增加一个sortOrder字段记录排序位置:
@Entity(tableName = "app_data") data class AppData( @PrimaryKey val id: String, // 每个元素要有唯一ID,比如UUID生成 val title: String, val desc: String, val drawableResId: Int, // 注意:Drawable不能直接存数据库,建议存资源ID var sortOrder: Int // 专门用来记录排序位置的字段 )
- 拖拽交换位置后,更新所有受影响元素的
sortOrder,并同步到数据库:
private fun onItemDragged(fromPosition: Int, toPosition: Int) { val currentList = adapter.currentList.toMutableList() Collections.swap(currentList, fromPosition, toPosition) // 更新每个元素的排序字段 currentList.forEachIndexed { index, appData -> appData.sortOrder = index } // 批量更新到数据库(用Room的批量更新方法) viewModel.updateAppDataList(currentList) adapter.submitList(currentList) }
- 每次启动APP或Activity重建时,从数据库按
sortOrder升序加载数据:
viewModel.getSortedAppData().observe(this) { sortedList -> adapter.submitList(sortedList) }
这样不管是旋转屏幕还是重启APP,排序都会从数据库加载,完全不会丢失。
方案3:只保存排序索引(适合数据量较大但不需要长期保存的场景)
如果不想用ViewModel,也不想引入数据库,可以只保存每个元素的唯一标识的排序顺序,而不是整个AppData对象。这样存储的数据量极小,甚至可以存在onSaveInstanceState里。
具体步骤:
- 给
AppData增加一个唯一标识字段:
data class AppData( val id: String, // 唯一ID,比如用UUID.randomUUID().toString()生成 val field1: String, val field2: String, val icon: Drawable )
- 在
onSaveInstanceState中保存排序后的ID列表:
override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) val sortedIds = adapter.currentList.map { it.id } outState.putStringArrayList("sorted_ids", ArrayList(sortedIds)) }
- 在
onCreate中恢复排序:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val initialData = loadInitialAppData() var sortedData = initialData // 如果有保存的排序ID,重新对原始数据排序 savedInstanceState?.getStringArrayList("sorted_ids")?.let { sortedIds -> sortedData = sortedIds.mapNotNull { id -> initialData.find { it.id == id } }.toMutableList() } // 初始化Adapter和RecyclerView adapter = YourAdapter(sortedData) recyclerView.adapter = adapter setupDragAndDrop() }
这个方法的好处是存储的数据量极小,完全符合onSaveInstanceState的限制,但缺点是如果原始数据发生变化(比如新增/删除元素),排序可能会出错,适合数据固定的场景。
总结一下:
- 只是应对旋转屏幕等配置变化:优先用ViewModel,最简单高效。
- 需要长期保存排序(重启APP也不丢):用Room数据库,持久化最可靠。
- 数据量大但只需要临时保存排序:用保存排序索引的方法。
内容的提问来源于stack exchange,提问作者Ening
相关产品推荐
相关产品推荐

