Android 保存嵌套RecyclerViews的滚动位置
我在应用程序中使用导航组件,对于从api获取数据,我在MVVM架构中使用改装,我希望从api获取数据并在嵌套的RecyclerView中显示,这对于将数据显示到嵌套的Recylerview中是可行的,也不是问题,但当转到片段详细信息并返回到上一个片段未保存状态和水平列表中的位置项时,如何在返回到上一个片段时显示当前位置RecyclerView 父适配器Android 保存嵌套RecyclerViews的滚动位置,android,kotlin,android-recyclerview,nestedrecyclerview,Android,Kotlin,Android Recyclerview,Nestedrecyclerview,我在应用程序中使用导航组件,对于从api获取数据,我在MVVM架构中使用改装,我希望从api获取数据并在嵌套的RecyclerView中显示,这对于将数据显示到嵌套的Recylerview中是可行的,也不是问题,但当转到片段详细信息并返回到上一个片段未保存状态和水平列表中的位置项时,如何在返回到上一个片段时显示当前位置RecyclerView 父适配器 import kotlinx.android.synthetic.main.item_main_shaping_group.view.* c
import kotlinx.android.synthetic.main.item_main_shaping_group.view.*
class MainShapingAdapter(
private val listGroup: MutableList<MainModel>,
private val listener: ListItemClick
) : RecyclerView.Adapter<MainShapingAdapter.MyViewHolder>(),
MainShapingChildAdapter.ListItemClickChild {
private val viewPool = RecyclerView.RecycledViewPool()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val layout = LayoutInflater.from(parent.context)
.inflate(R.layout.item_main_shaping_group, parent, false)
return MyViewHolder(layout)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.itemView.apply {
tv_titleGroup_itemGroup.text = listGroup[position].category.categoryTitle
rv_itemGroup.layoutManager =
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, true)
rv_itemGroup.adapter = MainShapingChildAdapter(
listGroup[position].listProduct.toMutableList(),
this@MainShapingAdapter
)
rv_itemGroup.isNestedScrollingEnabled = false
rv_itemGroup.setRecycledViewPool(viewPool)
btn_more_itemGroup.setOnClickListener {
listener.itemOnclickCategory(listGroup[position].category)
}
}
}
override fun getItemCount(): Int = listGroup.size
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
}
interface ListItemClick {
fun itemOnclickCategory(category: CategoryModel)
fun itemOnclickChild(product: Product)
}
override fun childOnclick(product: Product) {
listener.itemOnclickChild(product)
}
override fun onViewRecycled(holder: MyViewHolder) {
super.onViewRecycled(holder)
Log.d(ConstantApp.TAG, "onViewRecycled 1")
}
}
import kotlinx.android.synthetic.main.item_main_shaping_child.view.*
class MainShapingChildAdapter(
private val listProduct: MutableList<Product>,
private val listener: ListItemClickChild
) : RecyclerView.Adapter<MainShapingChildAdapter.MyViewHolder>() {
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val layout = LayoutInflater.from(parent.context)
.inflate(R.layout.item_main_shaping_child, parent, false)
return MyViewHolder(layout)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.itemView.apply {
Glide.with(context).load(listProduct[position].productCover)
.into(iv_coverProduct_shapingChild)
tv_titleProduct_shapingChild.text = listProduct[position].productTitle
tv_priceProduct_shapingChild.text = listProduct[position].productPrice.toString()
setOnClickListener {
listener.childOnclick(listProduct[position])
}
}
}
override fun getItemCount(): Int = listProduct.size
interface ListItemClickChild {
fun childOnclick(product: Product)
}
}
导入kotlinx.android.synthetic.main.item\u main\u shaping\u group.view*
类主形状适配器(
私有val列表组:可变列表,
私有val侦听器:ListItemClick
):RecyclerView.Adapter(),
MainShapingChildAdapter.ListItemClickChild{
private val viewPool=RecyclerView.RecycledViewPool()
重写CreateViewHolder(父级:ViewGroup,viewType:Int):MyViewHolder{
val layout=LayoutInflater.from(parent.context)
.充气(R.layout.item\u main\u shaping\u group,父级,false)
返回MyViewHolder(布局)
}
覆盖onBindViewHolder(holder:MyViewHolder,位置:Int){
holder.itemView.apply{
tv\U titleGroup\U itemGroup.text=listGroup[position].category.categoryTitle
rv_itemGroup.layoutManager=
LinearLayoutManager(上下文,LinearLayoutManager.HORIZONTAL,true)
rv_itemGroup.adapter=MainShapingChildAdapter(
listGroup[position].listProduct.toMutableList(),
this@MainShapingAdapter
)
rv_itemGroup.isNestedScrollingEnabled=false
rv_itemGroup.setRecycledViewPool(viewPool)
btn\u more\u itemGroup.setOnClickListener{
listener.itemOnclickCategory(列表组[position].category)
}
}
}
重写getItemCount():Int=listGroup.size
类MyViewHolder(itemView:View):RecyclerView.ViewHolder(itemView){
}
界面列表项单击{
趣味项目ClickCategory(类别:CategoryModel)
趣味项目ClickChild(产品:产品)
}
覆盖趣味儿童点击(产品:产品){
listener.itemOnclickChild(产品)
}
覆盖视图回收(持有者:MyViewHolder){
super.onViewRecycled(支架)
Log.d(ConstantApp.TAG,“onview1”)
}
}
儿童适配器
import kotlinx.android.synthetic.main.item_main_shaping_group.view.*
class MainShapingAdapter(
private val listGroup: MutableList<MainModel>,
private val listener: ListItemClick
) : RecyclerView.Adapter<MainShapingAdapter.MyViewHolder>(),
MainShapingChildAdapter.ListItemClickChild {
private val viewPool = RecyclerView.RecycledViewPool()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val layout = LayoutInflater.from(parent.context)
.inflate(R.layout.item_main_shaping_group, parent, false)
return MyViewHolder(layout)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.itemView.apply {
tv_titleGroup_itemGroup.text = listGroup[position].category.categoryTitle
rv_itemGroup.layoutManager =
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, true)
rv_itemGroup.adapter = MainShapingChildAdapter(
listGroup[position].listProduct.toMutableList(),
this@MainShapingAdapter
)
rv_itemGroup.isNestedScrollingEnabled = false
rv_itemGroup.setRecycledViewPool(viewPool)
btn_more_itemGroup.setOnClickListener {
listener.itemOnclickCategory(listGroup[position].category)
}
}
}
override fun getItemCount(): Int = listGroup.size
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
}
interface ListItemClick {
fun itemOnclickCategory(category: CategoryModel)
fun itemOnclickChild(product: Product)
}
override fun childOnclick(product: Product) {
listener.itemOnclickChild(product)
}
override fun onViewRecycled(holder: MyViewHolder) {
super.onViewRecycled(holder)
Log.d(ConstantApp.TAG, "onViewRecycled 1")
}
}
import kotlinx.android.synthetic.main.item_main_shaping_child.view.*
class MainShapingChildAdapter(
private val listProduct: MutableList<Product>,
private val listener: ListItemClickChild
) : RecyclerView.Adapter<MainShapingChildAdapter.MyViewHolder>() {
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val layout = LayoutInflater.from(parent.context)
.inflate(R.layout.item_main_shaping_child, parent, false)
return MyViewHolder(layout)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.itemView.apply {
Glide.with(context).load(listProduct[position].productCover)
.into(iv_coverProduct_shapingChild)
tv_titleProduct_shapingChild.text = listProduct[position].productTitle
tv_priceProduct_shapingChild.text = listProduct[position].productPrice.toString()
setOnClickListener {
listener.childOnclick(listProduct[position])
}
}
}
override fun getItemCount(): Int = listProduct.size
interface ListItemClickChild {
fun childOnclick(product: Product)
}
}
导入kotlinx.android.synthetic.main.item\u main\u shaping\u child.view*
类MainShapingChildAdapter(
私有val列表产品:可变列表,
私有val侦听器:ListItemClickChild
):RecyclerView.Adapter(){
类MyViewHolder(itemView:View):RecyclerView.ViewHolder(itemView){
}
重写CreateViewHolder(父级:ViewGroup,viewType:Int):MyViewHolder{
val layout=LayoutInflater.from(parent.context)
.充气(右布局项目\主项目\子项目、父项目、假项目)
返回MyViewHolder(布局)
}
覆盖onBindViewHolder(holder:MyViewHolder,位置:Int){
holder.itemView.apply{
Glide.with(context).load(listProduct[position].productCover)
.进入(iv_coverProduct_shapingChild)
tv\u titleProduct\u shapingChild.text=listProduct[position].productTitle
tv\u priceProduct\u shapingChild.text=listProduct[position].productPrice.toString()
setOnClickListener{
listener.childOnclick(listProduct[position])
}
}
}
重写getItemCount():Int=listProduct.size
接口列表项ClickChild{
趣味儿童点击(产品:产品)
}
}
我使用本教程使旋转木马回收器视图保持滚动状态:
基本上,您必须创建一个新类:
import android.os.Bundle
import android.os.Parcelable
import androidx.recyclerview.widget.RecyclerView
/**
* Persists scroll state for nested RecyclerViews.
*
* 1. Call [saveScrollState] in [RecyclerView.Adapter.onViewRecycled]
* to save the scroll position.
*
* 2. Call [restoreScrollState] in [RecyclerView.Adapter.onBindViewHolder]
* after changing the adapter's contents to restore the scroll position
*/
class ScrollStateHolder(savedInstanceState: Bundle? = null) {
companion object {
const val STATE_BUNDLE = "scroll_state_bundle"
}
/**
* Provides a key that uniquely identifies a RecyclerView
*/
interface ScrollStateKeyProvider {
fun getScrollStateKey(): String?
}
/**
* Persists the [RecyclerView.LayoutManager] states
*/
private val scrollStates = hashMapOf<String, Parcelable>()
/**
* Keeps track of the keys that point to RecyclerViews
* that have new scroll states that should be saved
*/
private val scrolledKeys = mutableSetOf<String>()
init {
savedInstanceState?.getBundle(STATE_BUNDLE)?.let { bundle ->
bundle.keySet().forEach { key ->
bundle.getParcelable<Parcelable>(key)?.let {
scrollStates[key] = it
}
}
}
}
fun setupRecyclerView(recyclerView: RecyclerView, scrollKeyProvider: ScrollStateKeyProvider) {
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
saveScrollState(recyclerView, scrollKeyProvider)
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val key = scrollKeyProvider.getScrollStateKey()
if (key != null && dx != 0) {
scrolledKeys.add(key)
}
}
})
}
fun onSaveInstanceState(outState: Bundle) {
val stateBundle = Bundle()
scrollStates.entries.forEach {
stateBundle.putParcelable(it.key, it.value)
}
outState.putBundle(STATE_BUNDLE, stateBundle)
}
fun clearScrollState() {
scrollStates.clear()
scrolledKeys.clear()
}
/**
* Saves this RecyclerView layout state for a given key
*/
fun saveScrollState(
recyclerView: RecyclerView,
scrollKeyProvider: ScrollStateKeyProvider
) {
val key = scrollKeyProvider.getScrollStateKey() ?: return
// Check if we scrolled the RecyclerView for this key
if (scrolledKeys.contains(key)) {
val layoutManager = recyclerView.layoutManager ?: return
layoutManager.onSaveInstanceState()?.let { scrollStates[key] = it }
scrolledKeys.remove(key)
}
}
/**
* Restores this RecyclerView layout state for a given key
*/
fun restoreScrollState(
recyclerView: RecyclerView,
scrollKeyProvider: ScrollStateKeyProvider
) {
val key = scrollKeyProvider.getScrollStateKey() ?: return
val layoutManager = recyclerView.layoutManager ?: return
val savedState = scrollStates[key]
if (savedState != null) {
layoutManager.onRestoreInstanceState(savedState)
} else {
// If we don't have any state for this RecyclerView,
// make sure we reset the scroll position
layoutManager.scrollToPosition(0)
}
// Mark this key as not scrolled since we just restored the state
scrolledKeys.remove(key)
}
}
您还必须在代码中的某个地方使用这两行:
scrollStateHolder.setupRecyclerView(itemView.child_recipes_rv, this)
scrollStateHolder.restoreScrollState(itemView.child_recipes_rv, this)
我之所以说“某处”,是因为这取决于您的具体实现。我做了一个变化的家伙在教程中所做的,所以这取决于你。在我的例子中,当我构建每个child recycler视图时,这两行会依次调用
基本上,您必须为每个子recyclerview都有一个标识符。您可以将其用作地图中的一个键(请参见ScrollStateHolder.kt),然后在保存片段/活动的状态时,您正在保存该状态,其中包括recyclerview的滚动状态。在使用,正在使用视图模型存储捆绑包,并在带有
scrollStateHolder?.onSaveInstanceState(viewModel.bundle)
的OnTestRoyView上使用它,在带有scrollStateHolder=scrollStateHolder(viewModel.bundle)
的onCreateView上使用它。刚刚用这些替换了extandle
和savedInstanceState
,它在更改片段和/或旋转时工作
我用他的ParentAdapter
和ChildAdapter
和他的ScrollStateHolder
修改了我的模型和视图,效果很好。稍后我将尝试使用其他类型的适配器和多视图
您还可以尝试,用一种更“丑陋”的方式,创建布局管理器,这些布局管理器将在片段中的子适配器中使用,并在调用它们各自的实例时通过它们。然后,使用前面描述的方法,保存并恢复实例状态[未测试]感谢您回答这个问题,但当用另一个片段替换片段并返回以前的片段时,此cod不起作用。可能您遗漏了什么。创建片段时,将恢复每个子回收器视图的状态。你可以查看教程并一步一步地学习,也许那样你可以找到你的错误请测试这个链接,它对我有用。