ConcatAdapter 导致 getItemViewType() 到 return 错误值?

ConcatAdapter causing getItemViewType() to return the wrong value?

我有一个 RecyclerView 适配器,可以处理我使用多年的几种视图类型。最近,我发现了 ConcatAdapter,但我似乎 运行 遇到了一个问题,它似乎为 getItemViewType().

返回了错误的值

我有一个用例,我想在其中显示 header 并在其下方显示项目列表。我为 header 创建了一个新的适配器 class,但列表项将继续使用我的旧适配器(它还处理其他几种视图类型)。

所以我连接了两个适配器:

HeaderAdapter headerAdapter = new HeaderAdapter(context);
PostAdapter postAdapter = new PostAdapter(context, postData);

ConcatAdapter concatAdapter = new ConcatAdapter(headerAdapter, postAdapter);
recyclerView.setAdapter(concatAdapter);

这工作正常,但是当我打开 activity 时,它立即崩溃并给出错误:

java.lang.ClassCastException: com.myapp.android.adapter.PostAdapter$PostViewHolder cannot be cast to com.myapp.android.adapter.PostAdapter$FooterViewHolder

这毫无意义,因为我什至从未在回收站视图中添加页脚。

我在 PostAdapter 中的 getItemViewType() 方法看起来像这样:

@Override
public int getItemViewType(int position) {
    if (data.get(position) instanceof Footer) {
        return TYPE_FOOTER;
    } else if (data.get(position) instanceof Post) {
        return TYPE_POST;
    }

    return -1;
}

我的 onBindViewHolder() 看起来像这样:

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    switch (holder.getItemViewType()) {
        case TYPE_FOOTER:
            FooterViewHolder vh1 = (FooterViewHolder) holder;
            configureFooterViewHolder(vh1, position);
            break;
        case TYPE_POST:
            PostViewHolder vh2 = (PostViewHolder) holder;
            configurePostViewHolder(vh2, position);
            break;
        default:
            break;
    }
}

但无论出于何种原因,当我 运行 调试器时,它位于 FooterViewHolder vh1 = (FooterViewHolder) holder; 行,即使我从未将页脚作为 PostAdapter 数据的一部分。

那么它为什么登陆这个?为什么 ConcatAdapter 会在处理多种视图类型的适配器中导致这种奇怪的行为?

当你绑定view holders时,你应该使用RecyclerView.Adapters.getItemViewType(position), not RecyclerView.ViewHolder.getItemViewType():

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    switch (getItemViewType(position)) { // not holder.getItemViewType()
        case TYPE_FOOTER:
            FooterViewHolder vh1 = (FooterViewHolder) holder;
            configureFooterViewHolder(vh1, position);
            break;
        case TYPE_POST:
            PostViewHolder vh2 = (PostViewHolder) holder;
            configurePostViewHolder(vh2, position);
            break;
        default:
            break;
    }
}

你得到了一个错误,因为 view holder 被回收了,它可以是任何 view holder,因此当你强制转换它到一个错误的 class 时它抛出 ClassCastException

好像是故意的。经过一些调试,我发现这一行的 adapter.getItemViewType(localPosition) 是正确的,但 ConcatAdapter 还做了一个额外的“从本地到全局项目视图类型的转换”,我从未配置过,但至少它覆盖了我本来想做的。

所以您根本看不到本地项目视图类型值,您只是从 ConcatAdapter 中获得一些随机分配的索引。由于所有这些都是私有的,您唯一的选择就是反射。

祝你好运。

(好的,对于真正的答案,您还可以启用 ConcatAdapter.Config(isolateViewTypes = false),确保所有项目视图类型彼此不同,它们实际上是不同的,而不仅仅是 returning 0return super.getItemViewType(position) 左右,然后 getItemViewType 将不会创建 local -> global 映射以确保适配器之间的一致性,而是 return 本地项目视图为你打字)

    val concatAdapter = ConcatAdapter(
        ConcatAdapter.Config.Builder()
            .setIsolateViewTypes(false)
            .build(),