Recyclerview + Listadapter 中的项目不会在更新时重绘

Items in Recyclerview + Listadapter won't redraw on update

我正在设置一个 RecyclerView,它使用 ListAdapter 来计算变化时的动画。 RecyclerView 通过 ViewModel 接收数据,ViewModel 通过 Firebase 获取列表。显示的项目属于 Mensa 类。 Mensa 项目可以更改其可见性、占用率或显示的距离。

我想实现 favorite/hide 个项目的两个按钮,从而改变它们在列表中的位置。每个项目中的两个按钮允许用户收藏或隐藏项目。这将根据排序策略将项目移动到列表的顶部/底部,将收藏夹放在第一位,默认第二位,最后隐藏。

但是,当我点击一个按钮时,列表会重新排列,但点击的项目不会重新绑定。按钮保留它们的旧状态(和 OnClickListeners),只有滚动列表才会调用 onBind 方法。 DiffUtil.Callback 是我的问题吗?我真的不知道我的代码有什么问题。

我已经在适配器的 submitList 方法中提供了一个新列表(这个来自另一个 Whosebug 问题的建议在我的例子中启用了动画),但是点击的项目仍然不会重绘。

在MensaListActivity.java

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mensa_list);

        viewModel = ViewModelProviders.of(this).get(MensaListModel.class);

        final RecyclerView recyclerView =findViewById(R.id.mensa_list_recyclerview);
        final MensaListAdapter adapter = new MensaListAdapter(this, new MensaListAdapter.ItemButtonsListener() {
            @Override
            public void visibilityButtonClicked(Mensa mensa, VisibilityPreference newVisibility) {
                viewModel.visibilityChanged(mensa, newVisibility);
            }
        });
        recyclerView.setAdapter(adapter);
        recyclerView.setHasFixedSize(false);
        recyclerView.setLayoutManager(new GridLayoutManager(this, 1, RecyclerView.VERTICAL, false));

        viewModel.getMensaData().observe(this, new Observer<LinkedList<Mensa>>() {
            @Override
            public void onChanged(LinkedList<Mensa> mensas) {
                adapter.submitList(new LinkedList<>(mensas));
            }
        });

在MensaListModel.java

    public LiveData<LinkedList<Mensa>> getMensaData() {
        return mensaData;
    }

    // ...

    public void visibilityChanged(Mensa changedItem, VisibilityPreference newVisibility) {
        LinkedList<Mensa> newData = getMensaData().getValue();
        int index = newData.indexOf(changedItem);

        newData.remove(index);
        newData.add(changedItem);
        sortMensaData(newData);
        // sortMensaData calls postValue method

MensaListAdapter.java

public class MensaListAdapter extends ListAdapter<Mensa, MensaListAdapter.MensaViewHolder> {

    private final ItemButtonsListener listener;
    private final Context context;

    class MensaViewHolder extends RecyclerView.ViewHolder {

        TextView nameLabel;
        TextView addressLabel;
        TextView restaurantTypeLabel;
        TextView occupancyLabel;
        TextView distanceLabel;
        ImageButton favoriteButton;
        ImageButton hideButton;

        public MensaViewHolder(@NonNull View itemView) {
            super(itemView);

            // a bunch of assignments
        }

        public void bindData(final Mensa newMensa) {

            nameLabel.setText(newMensa.getName());
            addressLabel.setText(newMensa.getAddress());
            restaurantTypeLabel.setText(newMensa.getType().toString());
            String occText = "Occupancy: " + newMensa.getOccupancy().toInt();
            occupancyLabel.setText(occText);
            if (newMensa.getDistance() != -1) {
                distanceLabel.setVisibility(View.VISIBLE);
                distanceLabel.setText(Double.toString(newMensa.getDistance()));
            } else {
                distanceLabel.setVisibility(View.INVISIBLE);
            }

            switch(newMensa.getVisibility()){
                case FAVORITE:
                    favoriteButton.setImageDrawable(ResourcesCompat.getDrawable(context.getResources(), R.drawable.favorite_active, null));
                    favoriteButton.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            listener.visibilityButtonClicked(newMensa, VisibilityPreference.DEFAULT);
                        }
                    }); break;
                case DEFAULT:
                    favoriteButton.setImageDrawable(ResourcesCompat.getDrawable(context.getResources(), R.drawable.favorite_inactive, null));
                    favoriteButton.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            listener.visibilityButtonClicked(newMensa, VisibilityPreference.FAVORITE);
                        }
                    }); break;
                case HIDDEN:
                    favoriteButton.setImageDrawable(ResourcesCompat.getDrawable(context.getResources(), R.drawable.favorite_inactive, null));
                    favoriteButton.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            listener.visibilityButtonClicked(newMensa, VisibilityPreference.FAVORITE);
                        }
                    }); break;

// removed hidebutton assignments, as they're identical to the favoritebutton assignment
            }

        }
    }

    public MensaListAdapter(Context context, ItemButtonsListener listener) {
        super(DIFF_CALLBACK);
        this.context = context;
        this.listener = listener;
    }

    private static final DiffUtil.ItemCallback<Mensa> DIFF_CALLBACK =
            new DiffUtil.ItemCallback<Mensa>() {
                @Override
                public boolean areItemsTheSame(@NonNull Mensa oldItem, @NonNull Mensa newItem) {
                    return oldItem.equals(newItem);
                }

                @Override
                public boolean areContentsTheSame(@NonNull Mensa oldItem, @NonNull Mensa newItem) {
                    return oldItem.getDistance() == newItem.getDistance()
                            && oldItem.getOccupancy().equals(newItem.getOccupancy())
                            && oldItem.getVisibility().equals(newItem.getVisibility());
                }
            };

    @Override
    public int getItemViewType(int position) {
        return R.layout.mensa_list_item;
    }

    @NonNull
    @Override
    public MensaViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        final View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
        return new MensaViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MensaViewHolder holder, int position) {
        holder.bindData(getItem(position));
    }

    public interface ItemButtonsListener{
        void visibilityButtonClicked(Mensa mensa, VisibilityPreference newVisibility);
    }

}

Mensa.java

public class Mensa {

    private String uID;
    private String name;
    private String address;
    private Occupancy occupancy;
    private RestaurantType type;
    private VisibilityPreference visibility;
    private double latitude;
    private double longitude;
    // distance is calculated lazily as soon as location coordinates are available, -1 means not calculated.
    private double distance = -1;

    public Mensa() {

    }

    // generated by android studio
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Mensa mensa = (Mensa) o;
        return uID.equals(mensa.uID);
    }

    @Override
    public int hashCode() {
        return Objects.hash(uID);
    }

    // a bunch of getters and setters

}

点击收藏按钮(心)前的列表。相关的是心脏按钮和眼睛按钮。

收藏 "Akademiestraße" 项目后的列表。位置变了,但是心形图标没变,OnClickListeners还是一样。

滚动回到列表顶部后的列表。现在心填满了,OnClickListeners也改了。

在我看来,您的数据正在更新,但 RecyclerView 仅更新订单,而不更新项目的视图。更新视图中的项目后,尝试调用适配器的 notifyDataSetChanged()

请记住,您的视图正在被回收,这意味着如果您有一个在位置 0 中选中的复选框。滚动将使该复选框在某些项目中重复使用,因此您可能会看到其他也被选中的项目即使你从不检查它。始终保存您的视图状态,因为它将被回收。您可以使用 POJO/model 通过布尔字段保存状态。

此外,在使用 DiffUtil 时,请确保它是一个不同的列表实例,不要重用旧列表,因为它可能不会更新您的数据。

您可能还想将此 adapter.submitList(new LinkedList<>(mensas)); 更改为仅此 adapter.submitList(mensas);