【问题标题】:Save Scroll Positions of Nested RecyclerViews保存嵌套 RecyclerViews 的滚动位置
【发布时间】:2020-11-11 21:59:43
【问题描述】:

我在我的应用程序中使用导航组件,为了从 api 获取数据,我在 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)
}


 }

【问题讨论】:

    标签: android kotlin android-recyclerview nestedrecyclerview


    【解决方案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)
    

    我说的是“某处”,因为这取决于您的具体实施。我做了一个人在教程中所做的变化,所以这取决于你。在我的情况下,当我构建每个子回收器视图时,这两行会被依次调用。

    基本上,您必须为每个子回收站视图设置一个标识符。然后您将其用作地图中的键(请参阅 ScrollStateHolder.kt),然后在保存片段/活动的状态时,您正在保存状态,其中包括 recyclerview 的滚动状态。

    【讨论】:

    • 感谢您回答这个问题,但是当用另一个片段替换片段并返回上一个片段时,此 cod 不起作用
    • 也许你错过了什么。创建片段时,这将恢复每个子回收器视图的状态。您可以查看教程并逐步进行更多操作,也许这样您就可以找到错误
    【解决方案2】:

    在使用 this (Ruben Sousa's blog entry) 时,对我有用的是使用视图模型来存储包,并在 onDestroyViewscrollStateHolder?.onSaveInstanceState(viewModel.bundle)onCreateViewscrollStateHolder = ScrollStateHolder(viewModel.bundle) 上使用它。只需将 outBundlesavedInstanceState 替换为它们,它就可以在更改片段和/或旋转时工作。

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

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

    【讨论】:

    猜你喜欢
    • 2019-05-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-11
    • 2018-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多