插入项目时 RecyclerView 行 flashes/blinks
RecyclerView row flashes/blinks when item inserted
每当我将一个项目插入我的 Room 数据库时,我的回收器视图都会闪烁包含新插入的视图持有者的行(列表的其余部分不会闪烁)。我正在使用 LiveData 通过在观察到的 ViewModel 方法的 onChanged() 中调用 submitList(list) 来保持我的列表自动更新。我的适配器扩展了 ListAdapter,我正在使用 DiffUtil 来跟踪列表中的更改。也就是说,我不会直接调用 notifyItemInserted(position),因为 DiffUtil 应该为我做这件事。有 2 种情况,其中一个项目被插入(1)一个全新的项目被插入到列表的末尾(2)一个被删除的项目被重新插入到列表中。在这两种情况下,该项目都会自行插入然后闪烁。我读过许多 post,其中人们建议在回收站视图中禁用动画,但这对我来说不是一个选项,因为我依赖代码中其他地方的动画。任何其他建议将不胜感激。我尽量使 posted 代码简短,但如果有帮助,我可以 post 更多。
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initComponents();
initRecyclerView();
setListeners();
setListObserver();
createItemTouchHelper();
}
private void setListObserver() {
viewModel.getAllItems().observe(this, new Observer<List<ListItem>>() {
@Override
public void onChanged(List<ListItem> newList) {
adapterMain.submitList(newList);
}
});
}
...
// Inserts a new ListItem when MainActivity's EditText is used
public void onClick(View v) {
if (v.getId() == R.id.img_add_item_main) {
String itemName = String.valueOf(edtAddItem.getText());
if (!itemName.trim().equals("")) { // Insert new list item only if the EditText is not empty
ListItem item = new ListItem();
item.setItemName(itemName);
viewModel.insert(item);
}
...
// SnackBar to allow a user to undo a delete operation
public void showUndoSnackBar(ListItem deletedItem) {
Snackbar undoSnackBar = Snackbar.make(constraintLayout, "Undo deleted Item",
Snackbar.LENGTH_LONG).setAction("UNDO", new View.OnClickListener() {
@Override
public void onClick(View v) {
// Restore deleted item to its original position in the list if UNDO is clicked
viewModel.insert(deletedItem);
}
});
undoSnackBar.show();
}
RecyclerAdapterMain.java
public class RecyclerAdapterMain extends ListAdapter<ListItem, RecyclerAdapterMain.ListItemHolder> {
public RecyclerAdapterMain() {
super(DIFF_CALLBACK);
}
private static final DiffUtil.ItemCallback<ListItem> DIFF_CALLBACK = new DiffUtil.ItemCallback<ListItem>() {
@Override
public boolean areItemsTheSame(@NonNull ListItem oldItem, @NonNull ListItem newItem) {
return oldItem.getId() == newItem.getId();
}
@Override
public boolean areContentsTheSame(@NonNull ListItem oldItem, @NonNull ListItem newItem) {
return oldItem.equals(newItem);
}
@Override
public ListItemHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recycler_item_layout_main, parent, false);
return new ListItemHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull ListItemHolder holder, int position) {
ListItem item = getItem(position);
holder.txtItemName.setText(item.getItemName());
holder.checkBox.setChecked(item.getIsChecked());
if(item.getIsChecked()) {
holder.txtItemName.setTextColor(Color.LTGRAY);
} else {
holder.txtItemName.setTextColor(Color.BLACK);
}
}
...
ListItem.java (POJO)
@Entity(tableName = "list_item_table")
public class ListItem {
@PrimaryKey(autoGenerate = true)
private long id;
private String itemName;
private boolean isChecked;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
... other getters and setters
public boolean equals(@Nullable ListItem listItem) {
return this.itemName.equals(listItem.getItemName()) && this.isChecked == listItem.getIsChecked();
}
}
我确定 DefaultItemAnimator class(在撰写本文时它是 RecyclerView 的动画器)有一个名为 animateAdd(final RecyclerView.ViewHolder holder) 的方法,它将 holder 的 alpha 设置为 0然后随着时间的推移将其动画化为 1。我通过将默认设置更改为 1 来验证这是闪烁的原因。我通过结合使用来自此 Whosebug and the documentation 的 DefaultItemAnimator.
接受的答案解决了这个问题。
首先,我创建了一个新的动画制作器 class,它扩展了 DefaultItemAnimator 以覆盖 animateAdd() 方法。在 animateAdd() 部分下,文档指出,“在将项目添加到 RecyclerView 时调用。实施者可以选择 是否 以及如何为该更改设置动画,但必须始终调用 dispatchAddFinished( RecyclerView.ViewHolder) 完成后,立即(如果不会出现动画) 或在动画实际结束后。”我立即调用 dispatchAddFinished() 以避免添加动画。除了有问题的动画外,所有动画都存在,而不是完全禁用动画。
MyRecyclerViewAnimator.java
public class MyRecyclerViewAnimator extends DefaultItemAnimator {
@Override
public boolean animateAdd(RecyclerView.ViewHolder holder) {
dispatchAddFinished(holder); // this is what bypasses the animation
return true;
}
/* this is the default implementation of animateAdd() in DefaultItemAnimator
@Override
public boolean animateAdd(final RecyclerView.ViewHolder holder) {
resetAnimation(holder);
holder.itemView.setAlpha(0); // this is what caused the flashing/blinking
mPendingAdditions.add(holder);
return true;
}
*/
}
MainActivity.java
...
recyclerMain.setItemAnimator(new MyRecyclerViewAnimator()); // set the default animator to your extended class
...
每当我将一个项目插入我的 Room 数据库时,我的回收器视图都会闪烁包含新插入的视图持有者的行(列表的其余部分不会闪烁)。我正在使用 LiveData 通过在观察到的 ViewModel 方法的 onChanged() 中调用 submitList(list) 来保持我的列表自动更新。我的适配器扩展了 ListAdapter,我正在使用 DiffUtil 来跟踪列表中的更改。也就是说,我不会直接调用 notifyItemInserted(position),因为 DiffUtil 应该为我做这件事。有 2 种情况,其中一个项目被插入(1)一个全新的项目被插入到列表的末尾(2)一个被删除的项目被重新插入到列表中。在这两种情况下,该项目都会自行插入然后闪烁。我读过许多 post,其中人们建议在回收站视图中禁用动画,但这对我来说不是一个选项,因为我依赖代码中其他地方的动画。任何其他建议将不胜感激。我尽量使 posted 代码简短,但如果有帮助,我可以 post 更多。
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initComponents();
initRecyclerView();
setListeners();
setListObserver();
createItemTouchHelper();
}
private void setListObserver() {
viewModel.getAllItems().observe(this, new Observer<List<ListItem>>() {
@Override
public void onChanged(List<ListItem> newList) {
adapterMain.submitList(newList);
}
});
}
...
// Inserts a new ListItem when MainActivity's EditText is used
public void onClick(View v) {
if (v.getId() == R.id.img_add_item_main) {
String itemName = String.valueOf(edtAddItem.getText());
if (!itemName.trim().equals("")) { // Insert new list item only if the EditText is not empty
ListItem item = new ListItem();
item.setItemName(itemName);
viewModel.insert(item);
}
...
// SnackBar to allow a user to undo a delete operation
public void showUndoSnackBar(ListItem deletedItem) {
Snackbar undoSnackBar = Snackbar.make(constraintLayout, "Undo deleted Item",
Snackbar.LENGTH_LONG).setAction("UNDO", new View.OnClickListener() {
@Override
public void onClick(View v) {
// Restore deleted item to its original position in the list if UNDO is clicked
viewModel.insert(deletedItem);
}
});
undoSnackBar.show();
}
RecyclerAdapterMain.java
public class RecyclerAdapterMain extends ListAdapter<ListItem, RecyclerAdapterMain.ListItemHolder> {
public RecyclerAdapterMain() {
super(DIFF_CALLBACK);
}
private static final DiffUtil.ItemCallback<ListItem> DIFF_CALLBACK = new DiffUtil.ItemCallback<ListItem>() {
@Override
public boolean areItemsTheSame(@NonNull ListItem oldItem, @NonNull ListItem newItem) {
return oldItem.getId() == newItem.getId();
}
@Override
public boolean areContentsTheSame(@NonNull ListItem oldItem, @NonNull ListItem newItem) {
return oldItem.equals(newItem);
}
@Override
public ListItemHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recycler_item_layout_main, parent, false);
return new ListItemHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull ListItemHolder holder, int position) {
ListItem item = getItem(position);
holder.txtItemName.setText(item.getItemName());
holder.checkBox.setChecked(item.getIsChecked());
if(item.getIsChecked()) {
holder.txtItemName.setTextColor(Color.LTGRAY);
} else {
holder.txtItemName.setTextColor(Color.BLACK);
}
}
...
ListItem.java (POJO)
@Entity(tableName = "list_item_table")
public class ListItem {
@PrimaryKey(autoGenerate = true)
private long id;
private String itemName;
private boolean isChecked;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
... other getters and setters
public boolean equals(@Nullable ListItem listItem) {
return this.itemName.equals(listItem.getItemName()) && this.isChecked == listItem.getIsChecked();
}
}
我确定 DefaultItemAnimator class(在撰写本文时它是 RecyclerView 的动画器)有一个名为 animateAdd(final RecyclerView.ViewHolder holder) 的方法,它将 holder 的 alpha 设置为 0然后随着时间的推移将其动画化为 1。我通过将默认设置更改为 1 来验证这是闪烁的原因。我通过结合使用来自此 Whosebug
首先,我创建了一个新的动画制作器 class,它扩展了 DefaultItemAnimator 以覆盖 animateAdd() 方法。在 animateAdd() 部分下,文档指出,“在将项目添加到 RecyclerView 时调用。实施者可以选择 是否 以及如何为该更改设置动画,但必须始终调用 dispatchAddFinished( RecyclerView.ViewHolder) 完成后,立即(如果不会出现动画) 或在动画实际结束后。”我立即调用 dispatchAddFinished() 以避免添加动画。除了有问题的动画外,所有动画都存在,而不是完全禁用动画。
MyRecyclerViewAnimator.java
public class MyRecyclerViewAnimator extends DefaultItemAnimator {
@Override
public boolean animateAdd(RecyclerView.ViewHolder holder) {
dispatchAddFinished(holder); // this is what bypasses the animation
return true;
}
/* this is the default implementation of animateAdd() in DefaultItemAnimator
@Override
public boolean animateAdd(final RecyclerView.ViewHolder holder) {
resetAnimation(holder);
holder.itemView.setAlpha(0); // this is what caused the flashing/blinking
mPendingAdditions.add(holder);
return true;
}
*/
}
MainActivity.java
...
recyclerMain.setItemAnimator(new MyRecyclerViewAnimator()); // set the default animator to your extended class
...