当我从适配器中删除给定视图然后调用ItemDecoration
时,如何让notifyItemRemoved()
“更新”其他视图的项目偏移量?
在我的特定情况下,我有一个ItemDecoration
,它为每个项目提供少量顶部偏移量,并且有大量底部偏移量仅限最后一项。当我删除最后一项时,成为新的最后一项的视图没有任何底部偏移。
之前和之后:
如果我然后滚动列表,当我返回到列表的底部时,我的“新”最后一项具有正确的偏移量。只要“新”最后一项在屏幕上保持可见,这只是一个问题。
如果我将notifyItemRemoved()
来电更改为notifyDataSetChanged()
,新的最后一项将会应用正确的项目抵消,但我会丢失从notifyItemRemoved()
获得的内置动画。< / p>
如果我继续使用notifyItemRemoved()
和另外在前一项上调用notifyItemChanged()
,那么我将获得正确的项目偏移,但动画有些笨拙;倒数第二个项目(删除前)似乎淡出然后再次出现(在此屏幕截图中,卡片上写着“第19项”刚刚删除):
我知道我可以通过在我的<RecyclerView>
标记中应用底部填充并指定android:clipToPadding="false"
来在列表末尾创建一个大空格,但由于各种原因,此解决方案无法接受。
我最初在一个我正在处理的更大的应用程序中遇到过这个问题,但这里有一个很小的应用程序来证明这个问题。
MainActivity.java:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final MyAdapter adapter = new MyAdapter(20);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
adapter.removeItemAt(adapter.getItemCount() - 1);
}
});
RecyclerView recycler = findViewById(R.id.recycler);
recycler.setLayoutManager(new LinearLayoutManager(this));
recycler.addItemDecoration(new MyItemDecoration());
recycler.setAdapter(adapter);
}
private static class MyItemDecoration extends RecyclerView.ItemDecoration {
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int viewPosition = parent.getChildViewHolder(view).getLayoutPosition();
int lastPosition = state.getItemCount() - 1;
outRect.top = 20;
outRect.bottom = (viewPosition == lastPosition) ? 200 : 0;
}
}
private static class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
private final List<String> list;
private MyAdapter(int count) {
this.list = new ArrayList<>();
for (int i = 0; i < count; ++i) {
list.add("" + i);
}
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View itemView = inflater.inflate(R.layout.item, parent, false);
return new MyViewHolder(itemView);
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.text.setText("item: " + list.get(position));
}
@Override
public int getItemCount() {
return list.size();
}
private void removeItemAt(int position) {
if (list.size() > 0) {
list.remove(position);
notifyItemRemoved(position);
}
}
}
private static class MyViewHolder extends RecyclerView.ViewHolder {
private final TextView text;
public MyViewHolder(View itemView) {
super(itemView);
this.text = itemView.findViewById(R.id.text);
}
}
}
activity_main.xml中:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#eee">
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="REMOVE LAST ITEM"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
</LinearLayout>
item.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_margin="8dp"
android:gravity="center"
android:textColor="#000"
android:textSize="16sp"/>
</android.support.v7.widget.CardView>
答案 0 :(得分:10)
我通过更改removeItemAt()
方法解决了这个问题,如下所示:
private void removeItemAt(int position) {
if (list.size() > 0) {
list.remove(position);
notifyItemRemoved(position);
if (position != 0) {
notifyItemChanged(position - 1, Boolean.FALSE);
}
}
}
notifyItemChanged()
的第二个参数是关键。它可以是字面上的任何对象,但我选择Boolean.FALSE
,因为它是一个着名的&#34;&#34;对象,因为它传达了一点意图:我不希望动画在我改变的项目上运行。
事实证明,onBindViewHolder()
中定义的第二个RecyclerView.Adapter
方法是我以前从未见过的。来自源代码:
public void onBindViewHolder(VH holder, int position, List<Object> payloads) {
onBindViewHolder(holder, position);
}
摘自该方法的文件:
payloads
参数是来自notifyItemChanged(int, Object)
或notifyItemRangeChanged(int, int, Object)
的合并列表。如果payloads
列表不为空,则ViewHolder
当前绑定到旧数据,Adapter
可以使用有效内容信息运行有效的部分更新。如果有效负载为空,Adapter
必须运行完全绑定。
换句话说,通过在notifyItemChanged()
中将某些内容(任何内容)作为有效负载传递,我们告诉系统我们只想执行部分更新&#34; (在可能的情况下)。
所以,当然,我们已经指示系统执行部分更新......但是如何阻止简单地调用notifyItemChanged(position - 1)
导致的闪烁?它与默认情况下附加到所有RecyclerView.ItemAnimator
的{{1}}有关:RecyclerView
。
DefaultItemAnimator
的源代码包含此方法:
DefaultItemAnimator
您会注意到此方法也采用@Override
public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder,
@NonNull List<Object> payloads) {
return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads);
}
参数。这与上面提到的其他List<Object> payloads
调用中使用的有效负载列表相同。
因为我们传递了一个有效负载参数,所以此方法将返回onBindViewHolder()
。而且现在动画师现在被告知它可以重用已经存在的true
用于我们的&#34;更改&#34; item,它不会拆掉它并创建一个新的(或重复使用一个回收的)...它会阻止更改的项目上的默认淡入淡出动画运行!
答案 1 :(得分:3)
这是一个太迟的答案,但是我会回答,因为我有同样的问题。在插入/更新/删除数据之前,请尝试调用RecyclerView.invalidateItemDecorations()。
答案 2 :(得分:1)
ListAdapter 解决方案:
listAdapter.submitList(newList) {
handler.post { // or just "post" if you're inside View
recyclerView.invalidateItemDecorations()
}
}
密钥是最新的RecyclerView版本中的新submitList(list, commitCallback)
方法。它允许您在{strong>之后之后调用invalidateItemDecorations()
。ListAdapter将所有更改应用于RecyclerView。
我还必须添加post {}
调用才能使其正常工作-避免装饰过早失效,并且什么也没有发生。