【问题标题】:How to preserve scroll position of a RecyclerView after a configuration change when using MVVM viewmodel and livedata?使用 MVVM 视图模型和 livedata 时如何在配置更改后保留 RecyclerView 的滚动位置?
【发布时间】:2020-10-31 19:04:32
【问题描述】:

请先阅读: 抱歉,看来我自己玩了。我之前在我的 xml 中使用了 RecyclerView,但将其切换为 CardStackView(它仍然使用完全相同的 RecyclerView 适配器)。如果我切换回 RecyclerView,下面的原始代码可以工作 - 滚动位置会在配置更改时自动保存和恢复。

我正在使用一个 MVVM 视图模型类,它在配置更改后成功地保留了 RecyclerView 的列表数据。但是,之前的 RecyclerView 位置并没有恢复。这是预期的行为吗?解决这个问题的好方法是什么?

我看到blog post on medium 简要提到您可以通过在 RecyclerView 上设置所述适配器之前设置适配器数据来保留滚动位置。 据我了解,在配置更改后,之前观察到的 livedata 会收到回调。该回调是我设置适配器数据的地方。但似乎这个回调发生在 onCreate() 函数完成之后,此时我的 RecyclerView 适配器已经设置。

class MainActivity : AppCompatActivity() {
    private val adapter = MovieAdapter()
    private lateinit var viewModel: MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // Create or retrieve viewmodel and observe data needed for recyclerview
        viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
        viewModel.movies.observe(this, {
            adapter.items = it
        })

        binding.recyclerview.adapter = adapter

        // If viewmodel has no data for recyclerview, retrieve it
        if (viewModel.movies.value == null) viewModel.retrieveMovies()
    }
}
class MovieAdapter :
    RecyclerView.Adapter<MovieAdapter.MovieViewHolder>() {

    var items: List<Movie> by Delegates.observable(emptyList()) { _, _, _ ->
        notifyDataSetChanged()
    }

    class MovieViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val binding = ItemMovieCardBinding.bind(itemView)

        fun bind(item: Movie) {
            with(binding) {
                imagePoster.load(item.posterUrl)
                textRating.text = item.rating.toString()
                textDate.text = item.date
                textOverview.text = item.overview
            }
        }
    }


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_movie_card, parent, false)

        return MovieViewHolder(view)
    }

    override fun onBindViewHolder(holder: MovieViewHolder, position: Int) {
        holder.bind(items[position])
    }

    override fun getItemCount() = items.size
}
class MainViewModel : ViewModel() {
    private val _movies = MutableLiveData<List<Movie>>()
    val movies: LiveData<List<Movie>> get() = _movies

    fun retrieveMovies() {
        viewModelScope.launch {
            val client = ApiClient.create()
            val result: Movies = withContext(Dispatchers.IO) { client.getPopularMovies() }

            _movies.value = result.movies
        }
    }
}

【问题讨论】:

  • is this expected behaviour? 配置更改会重新创建整个活动,它将通过 onDestroy 和 onCreate,source,这意味着这是您拥有的新活动,所以是的,这是预期的或正常的。这是你真正必须“修复”的东西,这取决于你。你会怎么修?我不确定 :) 如果您找到了保留最后一个可见项目的可靠方法,您可能可以使用 smoothScrollToPosition 或类似的方法
  • 在您的observe() lambda 中,有adapter.items = it。当您设置items 时,MovieAdapter 会做什么?它是否调用notifyDataSetChanged()?您可以使用适配器中的相关代码更新问题吗?
  • @a_local_nobody 根据上述博客:“有几种方法可以确保正确的滚动位置...最好的方法是确保您始终在适配器上设置数据之前第一个布局通过缓存要在内存中显示的数据,在 ViewModel" 中。这让我相信在 ViewModel 方面有一种特殊的方法可以做到这一点。当然,我可以在 ViewModel 类中创建另一个变量并在 Activity 被销毁之前将滚动位置 (getBindingAdapterPosition()) 保存在那里,但这真的是我要问的最好的方法。
  • @BenP。一旦检测到新数据,它就会立即调用notifyDataSetChanged()。我已经用适配器和视图模型类更新了问题。

标签: android mvvm android-recyclerview viewmodel android-livedata


【解决方案1】:

仅在其项目可用后设置适配器。

    viewModel.movies.observe(this, {
        adapter.items = it
        binding.recyclerview.adapter = adapter
    })

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-01-23
    • 1970-01-01
    • 1970-01-01
    • 2017-07-19
    • 2011-10-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多