1

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

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

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

列表意外重新排序

二次拖动操作

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();
  }
}
4

0 回答 0