具有多种 ViewHolder 类型的 RecyclerView

RecyclerView with multiple ViewHolder types

GalleryFragment 中,我显示了用户图库中的所有图像,我在位置 0 处有一个按钮,该按钮可以通过相机拍摄新图像。

我正在像这样传递列表中的第一个数据galleryModelList.add(new GalleryModel(null, null, null));

我这样做是因为我想先显示按钮然后显示其他图像。

public class GalleryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private final List<GalleryModel> galleryModelList;

    public GalleryAdapter(List<GalleryModel> galleryModelList) {
        this.galleryModelList = galleryModelList;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (viewType == 0)
            return new NewImageViewHolder(RowNewImageBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
        else
            return new GalleryViewHolder(RowGalleryBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        if (holder.getItemViewType() == 0) {
            //...
        } else {
            //...
        }
    }

    @Override
    public int getItemCount() {
        return galleryModelList.size();
    }

    @Override
    public int getItemViewType(int position) {
        return position;
    }

    public static class NewImageViewHolder extends RecyclerView.ViewHolder {

        RowNewImageBinding rowNewImageBinding;

        public NewImageViewHolder(@NonNull RowNewImageBinding rowNewImageBinding) {
            super(rowNewImageBinding.getRoot());
            this.rowNewImageBinding = rowNewImageBinding;
        }

    }

    public static class GalleryViewHolder extends RecyclerView.ViewHolder {

        RowGalleryBinding rowGalleryBinding;

        public GalleryViewHolder(@NonNull RowGalleryBinding rowGalleryBinding) {
            super(rowGalleryBinding.getRoot());
            this.rowGalleryBinding = rowGalleryBinding;
        }

    }

}

一切正常,但我想问一下,像我上面做的那样将空值传递到第一个位置是不是一种好方法,或者有更好的方法来显示按钮而不传递假数据?


编辑 2022 年 2 月 16 日

snachmsm的答案是正确的,但我做了一些修改,这是结果

当我点击按钮旁边的项目时,出现此错误 java.lang.ClassCastException: com.test.app.adapters.GalleryAdapter$NewImageViewHolder cannot be cast to com.test.app.adapters.GalleryAdapter$GalleryViewHolder

错误来自这里

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads) {
    if (payloads.isEmpty())
        super.onBindViewHolder(holder, position, payloads);
    else
        ((GalleryViewHolder) holder).rowGalleryBinding.rowGalleryCounter.setText(String.valueOf(payloads.get(0)));
}

这是完整的 GalleryAdapter 文件

public class GalleryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private final List<GalleryModel> galleryModelList;
    private final List<Integer> selectedItems = new ArrayList<>();

    public GalleryAdapter(List<GalleryModel> galleryModelList) {
        this.galleryModelList = galleryModelList;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (viewType == 0)
            return new NewImageViewHolder(RowNewImageBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
        else
            return new GalleryViewHolder(RowGalleryBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        if (holder.getItemViewType() == 0) {
            //soon
        } else {
            int realPosition = position - 1;
            GalleryViewHolder galleryViewHolder = (GalleryViewHolder) holder;
            galleryViewHolder.rowGalleryBinding.setGalleryModel(galleryModelList.get(realPosition));
            if (selectedItems.contains(realPosition)) {
                galleryViewHolder.rowGalleryBinding.rowGalleryCounter.setText(String.valueOf(selectedItems.indexOf(realPosition) + 1));
                galleryViewHolder.rowGalleryBinding.rowGalleryCounter.setVisibility(View.VISIBLE);
            } else
                galleryViewHolder.rowGalleryBinding.rowGalleryCounter.setVisibility(View.GONE);
            galleryViewHolder.rowGalleryBinding.rowGalleryContainer.setOnClickListener(view -> {
                if (selectedItems.contains(realPosition)) {
                    selectedItems.remove((Integer) realPosition);
                    galleryViewHolder.rowGalleryBinding.rowGalleryCounter.setVisibility(View.GONE);
                } else {
                    selectedItems.add(realPosition);
                    galleryViewHolder.rowGalleryBinding.rowGalleryCounter.setText(String.valueOf(selectedItems.indexOf(realPosition) + 1));
                    galleryViewHolder.rowGalleryBinding.rowGalleryCounter.setVisibility(View.VISIBLE);
                }
                for (int i = 0; i < selectedItems.size(); i++)
                    notifyItemChanged(selectedItems.get(i), selectedItems.indexOf(selectedItems.get(i)) + 1);
            });
        }
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads) {
        if (payloads.isEmpty())
            super.onBindViewHolder(holder, position, payloads);
        else
            ((GalleryViewHolder) holder).rowGalleryBinding.rowGalleryCounter.setText(String.valueOf(payloads.get(0)));
    }

    @Override
    public int getItemCount() {
        return galleryModelList.size() + 1;
    }

    @Override
    public int getItemViewType(int position) {
        return position;
    }

    public static class NewImageViewHolder extends RecyclerView.ViewHolder {

        RowNewImageBinding rowNewImageBinding;

        public NewImageViewHolder(@NonNull RowNewImageBinding rowNewImageBinding) {
            super(rowNewImageBinding.getRoot());
            this.rowNewImageBinding = rowNewImageBinding;
        }

    }

    public static class GalleryViewHolder extends RecyclerView.ViewHolder {

        RowGalleryBinding rowGalleryBinding;

        public GalleryViewHolder(@NonNull RowGalleryBinding rowGalleryBinding) {
            super(rowGalleryBinding.getRoot());
            this.rowGalleryBinding = rowGalleryBinding;
        }

    }

}

下面的答案和我之前说的一样完美,但在我做了一些更改后,我遇到了上面的错误,我不知道如何解决它。

你不应该那样做,这个 single/first 项目不是 collection 成员。不要添加这个虚拟视图,而是通知适配器有一个额外的项目

private static final int EXTRA_ITEMS_ON_TOP = 1; // num of buttons

private static final int TYPE_BUTTON = 0;
private static final int TYPE_LIST_ITEM =1;

@Override
public int getItemCount() {
    return galleryModelList.size() + EXTRA_ITEMS_ON_TOP;
}

@Override
public int getItemViewType(int position) {
    // return TYPE_BUTTON type first position(s)
    return position < EXTRA_ITEMS_ON_TOP ? TYPE_BUTTON  : TYPE_LIST_ITEM;
}

onBindViewHolder里面只是重新计算位置

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
    if (holder.getItemViewType() == TYPE_BUTTON) {
        //... position < EXTRA_ITEMS_ON_TOP, so 0 only in current config
    } else {
        int realPosition = position - EXTRA_ITEMS_ON_TOP;
        GalleryModel model = galleryModelList.get(realPosition);
        ...
    }
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    if (viewType == TYPE_BUTTON)
        return new NewImageViewHolder(RowNewImageBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
    else
        return new GalleryViewHolder(RowGalleryBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
}

您当前的方法是一种解决方法。它可能有效,但您可能还想从适配器获取完整列表,您将获得一个包含一个额外项目(虚拟项目)的列表,并且当它用 nulls 完成时,您可能会得到一些 NullPointerException。为了解决这个问题,你可能会做一些 if,这会使下一个地方的代码复杂化