ConcatAdapter导致getItemViewType()返回错误的值?

4

我有一个RecyclerView适配器,可以处理多个视图类型,我已经使用了多年。最近,我发现了ConcatAdapter,但似乎遇到了一个问题:它返回了错误的getItemViewType()值。

我的用例需要显示一个标题和下面的项目列表。我为标题创建了一个新的适配器类,但是项目列表将继续使用我的旧适配器(它还处理其他几种视图类型)。

因此,我已将这两个适配器连接在一起:

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

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

这个工作正常,但当我打开这个活动时,它立即崩溃并提示以下错误:
java.lang.ClassCastException: com.myapp.android.adapter.PostAdapter$PostViewHolder不能强制转换为com.myapp.android.adapter.PostAdapter$FooterViewHolder 这毫无意义,因为我从未添加过脚注到RecyclerView。
我的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会导致处理多个视图类型的适配器出现如此奇怪的行为?
2个回答

5
似乎是有意为之。经过一番调试,我发现这行代码中的adapter.getItemViewType(localPosition)是正确的,但ConcatAdapter还会进行额外的“从本地到全局项目视图类型的转换”,而我从未配置过,但至少它覆盖了我最初想做的事情。

mViewTypeLookup.localToGlobal()

所以你完全看不到本地项目视图类型的值,你只会从ConcatAdapter中得到一些随机分配的索引。由于所有内容都是私有的,你唯一的选择就是使用反射。

祝你好运。

(好吧,对于一个真正的答案,你也可以启用ConcatAdapter.Config(isolateViewTypes = false),确保所有的项目视图类型在它们实际不同的地方都是不同的,而不仅仅是返回0return super.getItemViewType(position)等,然后getItemViewType将不会创建一个local -> global映射以确保跨适配器的一致性,而是会为您返回本地项目视图类型)

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

你好, 我遇到了“IndexOutOfBoundsException”错误,它来自你在这里展示的同一行代码,你知道如何解决吗? 添加setIsolateViewTypes(false)没有解决问题。 谢谢。 - Ofir A.
1
不要在不通知适配器的情况下就直接原地修改列表。 - EpicPandaForce
@EpicPandaForce 我也遇到了同样的错误,我不理解上面的评论,请您详细说明一下什么是“停止原地突变列表”。感谢! - Olkunmustafa
这意味着你不会编辑ArrayList,而是将ArrayList包装在Collections.unmodifiableList()中。当你需要一个新的ArrayList时,你创建一个新的ArrayList(oldList),进行修改并将其包装在unmodifiable list中,然后将其传递给适配器。 - EpicPandaForce

5

当你绑定视图持有者时,应使用 RecyclerView.Adapters.getItemViewType(position) 而不是 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;
    }
}

由于视图持有者已经被回收,你会遇到一个错误,可能是任何视图持有者,所以当你强制将其强制转换为错误的类时,它会抛出 ClassCastException


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接