如果你的回收视图得到了新的项目,最好使用通知fyItemRangeInserted
,以及每个项目唯一、稳定的id,这样它就可以很好地动画,而不会改变你看到的太多:
如您所见,列表中的第一个项目“0”,当我在它之前添加更多项目时,它保持在同一个位置,好像什么都没有改变。
这是一个很好的解决方案,当您在其他任何地方插入项目时,它也适用于其他情况。
然而,它并不适合所有情况。有时,我从外面得到的只是:“这是一个新的项目列表,有些是新的,有些是相同的,有些已经更新/删除”。
正因为如此,我不能再使用通知ItemRangeInserted
了,因为我不知道添加了多少。
问题是,如果我使用通知数据设置更改
,滚动会发生变化,因为当前项目之前的项目数量发生了变化。
这意味着您当前查看的项目将在视觉上移至一边:
正如你现在看到的,当我在第一个项目之前添加更多项目时,他们会将其向下推。
我希望当前可查看的项目将尽可能多地保留,优先级为顶部的项目(在这种情况下为0)。
对用户来说,除了一些可能的最终情况(删除了当前项目和之后的项目,或者以某种方式更新了当前项目),他不会注意到当前项目以上的任何内容。看起来好像我使用了通知fyItemRangeInserted
。
我试图保存当前滚动状态或位置,然后恢复它,如图所示,但没有一个解决方案解决了这个问题。
这是我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中)并尝试滚动到它。不过,这存在多个问题:
这是新代码:
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
}
}
我将使用DiffUtil
api来解决这个问题。DiffUtil
旨在采用“之前”和“之后”列表(可以像您想要的那样相似或不同),并将为您计算需要通知适配器的各种插入、删除等。
使用DiffUtil
最大的也是几乎唯一的挑战是定义要使用的DiffUtil. Callback
。对于您的概念验证应用程序,我认为事情会很容易。请原谅Java的代码;我知道您最初是用静态编程语言发布的,但我对静态编程语言并不像对Java那样熟悉。
这是一个我认为适用于您的应用程序的回调:
private static class MyCallback extends DiffUtil.Callback {
private List<ListItemData> oldItems;
private List<ListItemData> newItems;
@Override
public int getOldListSize() {
return oldItems.size();
}
@Override
public int getNewListSize() {
return newItems.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return oldItems.get(oldItemPosition).id == newItems.get(newItemPosition).id;
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return oldItems.get(oldItemPosition).data == newItems.get(newItemPosition).data;
}
}
以下是你如何在应用程序中使用它(在java/kotlin伪代码中):
addItemsFromTopButton.setOnClickListener {
MyCallback callback = new MyCallback();
callback.oldItems = new ArrayList<>(listItems);
// modify listItems however you want... add, delete, shuffle, etc
callback.newItems = new ArrayList<>(listItems);
DiffUtil.calculateDiff(callback).dispatchUpdatesTo(adapter);
}
我制作了自己的小应用程序来测试这一点:每次按下按钮都会添加20个项目,打乱列表,然后删除10个项目。这是我观察到的: