保存嵌套RecyclerView的滚动位置

4

我在我的应用程序中使用导航组件,使用Retrofit在MVVM架构中获取api数据并在嵌套的RecyclerView中显示。这个过程可以很好地工作,并且没有在嵌套的RecyclerView中显示数据时出现问题,但是当进入详细信息片段并返回到先前的片段时,无法保存状态和位置项在水平列表中,如何可以在返回到先前的片段时显示当前位置的RecyclerView?

父适配器

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)
}


 }
2个回答

1
我将使用这个教程来使我的轮播回收站保持滚动状态: https://rubensousa.com/2019/08/27/saving_scroll_state_of_nested_recyclerviews/ 基本上,你需要创建一个新类:
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)
    }

}

然后您可以使用这个类来存储片段/活动在分离/销毁时的状态。
override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    scrollStateHolder = ScrollStateHolder(savedInstanceState)
    return inflater.inflate(R.layout.layout, container, false)
}

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    scrollStateHolder.onSaveInstanceState(outState)
}

您还需要在代码中的某处使用这两行代码:

scrollStateHolder.setupRecyclerView(itemView.child_recipes_rv, this)
    scrollStateHolder.restoreScrollState(itemView.child_recipes_rv, this)

我说“某个地方”,因为那取决于你的具体实现。我做了教程中的变化版本,所以由你决定。在我的情况下,当我构建每个子recycler视图时,这两行代码是依次调用的。基本上,你必须为每个子recyclerview设置一个标识符。你将其用作映射中的键(请参阅ScrollStateHolder.kt),然后在保存片段/活动的状态时,保存了状态,包括recyclerview的滚动状态。

感谢您回答问题,但是当用另一个片段替换片段并返回先前的片段时,此代码无法工作。 - h.joa
也许你漏掉了什么。当创建一个片段时,它会恢复每个子回收视图的状态。你可以查看教程并更加逐步地跟随它,也许这样你就能找到错误了。 - Lheonair

0

在使用Ruben Sousa的博客文章时,对我有用的是使用视图模型来存储bundle,并在onDestroyView中使用scrollStateHolder?.onSaveInstanceState(viewModel.bundle)和在onCreateView中使用scrollStateHolder = ScrollStateHolder(viewModel.bundle)。只需将outBundlesavedInstanceState替换为它们,即可在更改片段和/或旋转时正常工作。

我使用了他的ParentAdapterChildAdapter,并使用了他的ScrollStateHolder修改了我的模型和视图,效果很好。稍后我会尝试其他类型的适配器和多视图。

您还可以尝试一种更“丑陋”的方法,在片段中创建将在子适配器中使用的布局管理器,并在其各自的实例中传递它们。然后,使用上述方法保存和恢复它们的实例状态。[未经测试]


请测试此链接,它对我有效。https://dev59.com/1FQJ5IYBdhLWcg3wGB5N#58835886 - h.joa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接