为什么在 RecyclerView 中重复拖动动画?

问题描述

我正在使用 ItemTouchHelper 类来支持在我的 RecyclerView 中拖放。当我在它周围拖动一个项目时,它会按预期在视觉上更新(交换行)。一旦我放下该项目,就会发生另一个 ** 视觉** 拖动。例如(见下图),如果我将项目“a”从索引 0 拖动到索引 3,正确的列表显示项目“b”现在位于索引 0。他们回收视图重复操作并在索引 0 处获取新项目("b") 并将其拖动到索引 3!无论我从哪个索引拖动到哪个索引,都会发生这种重复拖动。

我将其称为 **visual** 拖动,因为我提交给 RecyclerView 的 ListAdapter 的列表已正确排序(通过日志验证)。如果我重新启动我的应用程序,列表的顺序是正确的。或者,如果我调用 notifyDataSetChanged(),在不需要的动画之后,它会正确排序。是什么导致了第二个动画?

编辑:根据文档,如果您在 areContentsTheSame() 方法 (DiffUtil) 中使用 equals() 方法,“此处错误地返回 false 将导致动画过多。”据我所知,我在下面的 POJO 文件中正确覆盖了此方法。我被难住了...

Unexpected reordering of list

Second drag operation

MainActivity.java

private void setListObserver() {
    viewModel.getAllItems().observe(this,new Observer<List<ListItem>>() {
      @Override
      // I have verified newList has the correct order through log statements
      public void onChanged(List<ListItem> newList) { 
        adapterMain.submitList(newList);
      }
    });
  }
...

// This method is called when item starts dragging
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder,int actionState) {
  ...

  if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) { 
    currentList = new ArrayList<>(adapterMain.getCurrentList()); // get current list from adapter
  }
  ...
}

// This method is called when item is dropped
public void clearView(@NonNull RecyclerView recyclerView,@NonNull RecyclerView.ViewHolder viewHolder) {
...

  // I have verified that all code in this method is returning correct values through log statements.
  // If I restart the app,everything is in the correct order

  // this is position of the where the item was dragged to,gets its value from the onMoved method. 
  // it's the last "toPos" value in onMoved() after the item is dropped
  int position = toPosition; 

    // Skip this code if item was deleted (indicated by -1). Otherwise,update the moved item
    if(position != -1) {
      ListItem movedItem = currentList.get(position);

      // If dragged to the beginning of the list,subtract 1 from the previously lowest
      // positionInList value (the item below it) and assign it the moved item. This will ensure
      // that it now has the lowest positionInList value and will be ordered first.
      if(position == 0) {
        itemAfterPos = currentList.get(position + 1).getPositionInList();
        movedItemNewPos = itemAfterPos - 1;

        // If dragged to the end of list,add 1 to the positionInList value of the previously
        // largest value and assign to the moved item so it will be ordered last.
      } else if (position == (currentList.size() - 1)) {

        itemBeforePos = currentList.get(position - 1).getPositionInList();
        movedItemNewPos = itemBeforePos + 1;

        // If dragged somewhere in the middle of list,get the positionInList variable value of
        // the items before and after it. They are used to compute the moved item's new
        // positionInList value.
      } else {

        itemBeforePos = currentList.get(position - 1).getPositionInList(); 
        itemAfterPos = currentList.get(position + 1).getPositionInList();

        // Calculates the moved item's positionInList variable to be half way between the
        // item above it and item below it
        movedItemNewPos = itemBeforePos + ((itemAfterPos - itemBeforePos) / 2.0);
      }
      updateItemPosInDb(movedItem,movedItemNewPos);
    }

  private void updateItemPosInDb(ListItem movedItem,double movedItemNewPos) {
    movedItem.setPositionInList(movedItemNewPos);
    viewModel.update(movedItem); // this updates the database which triggers the onChanged method 
  }

  public void onMoved(@NonNull RecyclerView recyclerView,@NonNull RecyclerView.ViewHolder source,int fromPos,@NonNull RecyclerView.ViewHolder target,int toPos,int x,int y) {
    Collections.swap(currentList,toPos,fromPos);
    toPosition = toPos; // used in clearView()
    adapterMain.notifyItemMoved(fromPos,toPos);
  }
}).attachToRecyclerView(recyclerMain);

RecyclerAdapterMain.java

public class RecyclerAdapterMain extends ListAdapter<ListItem,RecyclerAdapterMain.ListItemHolder> {

  // Registers MainActivity as a listener to checkbox clicks. Main will update database accordingly.
  private CheckBoxListener checkBoxListener;

  public interface CheckBoxListener {
    void onCheckBoxClicked(ListItem item); // Method implemented in MainActivity
  }

  public void setCheckBoxListener(CheckBoxListener checkBoxListener) {
    this.checkBoxListener = checkBoxListener;
  }

  public RecyclerAdapterMain() {
    super(DIFF_CALLBACK);
  }

    // Static keyword makes DIFF_CALLBACK variable available to the constructor when it is called
    // DiffUtil will compare two objects to determine if updates are needed
    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();
    }

    // Documentation - NOTE: if you use equals,your object must properly override Object#equals().
    // Incorrectly returning false here will result in too many animations. 
    // As far as I can tell I am overriding the equals() properly in my POJO below
    @Override
    public boolean areContentsTheSame(@NonNull ListItem oldItem,@NonNull ListItem newItem) {
      return oldItem.equals(newItem);
    }
  };

  @NonNull
  @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);
    Resources resources = holder.itemView.getContext().getResources();
    holder.txtItemName.setText(item.getItemName());
    holder.checkBox.setChecked(item.getIsChecked());

    // Set the item to "greyed out" if checkbox is checked,normal color otherwise
    if(item.getIsChecked()) {
      holder.txtItemName.setTextColor(Color.LTGRAY);
      holder.checkBox.setButtonTintList(ColorStateList
          .valueOf(resources.getColor(R.color.checkedColor,null)));
    } else {
      holder.txtItemName.setTextColor(Color.BLACK);
      holder.checkBox.setButtonTintList(ColorStateList
          .valueOf(resources.getColor(R.color.uncheckedColor,null)));
    }
  }

  public class ListItemHolder extends RecyclerView.ViewHolder {
    private TextView txtItemName;
    private CheckBox checkBox;

    public ListItemHolder(@NonNull View itemView) {
      super(itemView);
      txtItemName = itemView.findViewById(R.id.txt_item_name);

      // Toggle checkbox state
      checkBox = itemView.findViewById(R.id.checkBox);
      checkBox.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
          checkBoxListener.onCheckBoxClicked(getItem(getAdapterPosition()));
        }
      });
    }
  }

  public ListItem getItemAt(int position) {
    return getItem(position);
  }
}

ListItem.java (POJO)

@Entity(tableName = "list_item_table")
public class ListItem {

  @PrimaryKey(autoGenerate = true)
  private long id;

  private String itemName;
  private boolean isChecked;
  private double positionInList;

  public long getId() {
    return id;
  }

  public void setId(long id) {
    this.id = id;
  }

  public String getItemName() {
    return itemName;
  }

  public void setItemName(String itemName) {
    this.itemName = itemName;
  }

  public void setChecked(boolean isChecked) {
    this.isChecked = isChecked;
  }

  public boolean getIsChecked() {
    return isChecked;
  }

  public void setPositionInList(double positionInList) {
    this.positionInList = positionInList;
  }

  public double getPositionInList() {
    return positionInList;
  }

  @Override
  public boolean equals(@Nullable Object obj) {
    ListItem item = new ListItem();
    if(obj instanceof ListItem) {
       item = (ListItem) obj;
    }

    return this.getItemName().equals(item.getItemName()) &&
        this.getIsChecked() == item.getIsChecked() &&
        this.getPositionInList() == item.getPositionInList();
  }
}

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...