在未知数量的RecyclerView数据更改(插入,删除,更新)时,如何在视觉上保持相同的滚动位置?

时间:2017-11-23 15:02:56

标签: android android-recyclerview

背景

如果您的RecyclerView获取新项目,最好使用notifyItemRangeInserted,以及每个项目的唯一,稳定的ID,以便它可以很好地制作动画,而不会更改您看到的内容:

enter image description here

正如你所看到的,当我在它之前添加更多项目时,列表中第一个项目“0”会保持在同一位置,就好像什么都没有改变一样。

问题

这是一个很好的解决方案,当你在其他任何地方插入项目时,它也适合其他情况。

然而,它并不适合所有情况。有时,我从外面得到的是:“这里有一个新的项目清单,有些是新的,有些是相同的,有些是更新/删除的”。

因此,我不能再使用notifyItemRangeInserted了,因为我不知道添加了多少。{/ p>

问题是,如果我使用notifyDataSetChanged,则滚动会更改,因为当前项目之前的项目数量已更改。

这意味着您当前查看的项目将被视觉移到一边:

enter image description here

正如您现在所看到的,当我在第一个项目之前添加更多项目时,他们会将其推下来。

我希望当前可查看的项目尽可能保留,优先级最高(在这种情况下为“0”)。

对于用户,他不会注意到当前项目之外的任何内容,除了一些可能的最终案例(删除当前项目以及之后的项目或以某种方式更新当前项目)。看起来好像我使用了notifyItemRangeInserted

我尝试了什么

我尝试保存当前的滚动状态或位置,然后将其恢复,如图here所示,但没有一个解决方案已修复此问题。

这是我尝试过的POC项目:

class MainActivity : AppCompatActivity() {
    val listItems = ArrayList<ListItemData>()
    var idGenerator = 0L
    var dataGenerator = 0

    class ListItemData(val data: Int, val id: Long)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
            val inflater = LayoutInflater.from(this@MainActivity)
            override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
                return object : RecyclerView.ViewHolder(inflater.inflate(android.R.layout.simple_list_item_1, parent, false)) {}
            }

            override fun onBindViewHolder(holder: RecyclerView.ViewHolder?, position: Int) {
                val textView = holder!!.itemView as TextView
                val item = listItems[position]
                textView.text = "item: ${item.data}"
            }

            override fun getItemId(position: Int): Long = listItems[position].id

            override fun getItemCount(): Int = listItems.size
        }
        adapter.setHasStableIds(true)
        recyclerView.adapter = adapter
        for (i in 1..30)
            listItems.add(ListItemData(dataGenerator++, idGenerator++))
        addItemsFromTopButton.setOnClickListener {
            for (i in 1..5) {
                listItems.add(0, ListItemData(dataGenerator++, idGenerator++))
            }
            //this is a good insertion, when we know how many items were added
            adapter.notifyItemRangeInserted(0, 5)
            //this is a bad insertion, when we don't know how many items were added
//            adapter.notifyDataSetChanged()
        }


    }
}


<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.user.recyclerviewadditionwithoutscrollingtest.MainActivity">


    <Button
        android:id="@+id/addItemsFromTopButton" android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" android:layout_marginRight="8dp"
        android:text="add items to top" app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="@+id/recyclerView"/>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView" android:layout_width="0dp" android:layout_height="0dp"
        android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" android:layout_marginStart="8dp"
        android:layout_marginTop="8dp" android:orientation="vertical"
        app:layoutManager="android.support.v7.widget.LinearLayoutManager"
        app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"/>
</android.support.constraint.ConstraintLayout>

问题

是否可以通知适配器各种更改,但是让它保持在完全相同的位置?

目前查看的项目如果可以,则会保留,或根据需要删除/更新。

当然,物品'ID将保持独特和稳定,但遗憾的是,细胞大小可能彼此不同。

编辑:我找到了部分解决方案。它的工作原理是获取哪个视图位于顶部,获取其项目(将其保存在viewHolder中)并尝试滚动到它。但是有很多问题:

  1. 如果该项目被删除,我将不得不以某种方式滚动到下一个项目,依此类推。我认为在真正的应用程序中,我可以设法做到这一点。不知道有没有更好的方法。
  2. 目前它已经通过列表来获取项目,但也许在真实应用程序中我可以优化它。
  3. 由于它只是滚动到项目,如果将它放在RecyclerView的顶部边缘,所以如果你滚动一点以显示部分,它会移动一点:
  4. enter image description here

    这是新代码:

    class MainActivity : AppCompatActivity() {
        val listItems = ArrayList<ListItemData>()
        var idGenerator = 0L
        var dataGenerator = 0
    
        class ListItemData(val data: Int, val id: Long)
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            val adapter = object : RecyclerView.Adapter<ViewHolder>() {
                val inflater = LayoutInflater.from(this@MainActivity)
                override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder {
                    return ViewHolder(inflater.inflate(android.R.layout.simple_list_item_1, parent, false))
                }
    
                override fun onBindViewHolder(holder: ViewHolder, position: Int) {
                    val textView = holder.itemView as TextView
                    val item = listItems[position]
                    textView.text = "item: ${item.data}"
                    holder.listItem = item
                }
    
                override fun getItemId(position: Int): Long = listItems[position].id
    
                override fun getItemCount(): Int = listItems.size
            }
            adapter.setHasStableIds(true)
            recyclerView.adapter = adapter
            for (i in 1..30)
                listItems.add(ListItemData(dataGenerator++, idGenerator++))
            val layoutManager = recyclerView.layoutManager as LinearLayoutManager
            addItemsFromTopButton.setOnClickListener {
                for (i in 1..5) {
                    listItems.add(0, ListItemData(dataGenerator++, idGenerator++))
                }
                val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
                val holder = recyclerView.findViewHolderForAdapterPosition(firstVisibleItemPosition) as ViewHolder
                adapter.notifyDataSetChanged()
                val listItemToGoTo = holder.listItem
                for (i in 0..listItems.size) {
                    val cur = listItems[i]
                    if (listItemToGoTo === cur) {
                        layoutManager.scrollToPositionWithOffset(i, 0)
                        break
                    }
                }
                //TODO think what to do if the item wasn't found
            }
    
    
        }
    
        class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
            var listItem: ListItemData? = null
        }
    }
    

0 个答案:

没有答案