【问题标题】:Different item view for each item in a Recycler View回收站视图中每个项目的不同项目视图
【发布时间】:2015-09-30 13:49:04
【问题描述】:

我正在尝试构建一个水平回收器视图,每个项目都是相同的父类型,但最多可以包含十个不同的子视图。我看过this answer,它解释了如何覆盖

getItemViewType(int position)

createViewHolder(ViewGroup parent, int viewType)

但这仅适用于您对每个项目的查看次数有限的情况。我必须显示的项目列表的大小可以是 40,每个项目最多可以包含 8 个图像,每个图像都位于父视图的不同位置。

我最初的计划是在 Recycler View Adapter 中动态创建每个项目视图

onBindViewHolder(ViewHolder viewHolder, int i)

这会导致问题,因为我必须在每次看到项目时创建所有单独的视图并将它们附加到父视图。因此,我在包含回收器视图的活动中创建了一个视图列表,而不是传递对象列表来创建视图,我只是传递了视图本身。由于我不必每次都阅读每个项目并在每次加载时为每个项目创建自定义视图,因此效果要​​好得多。

我现在的问题是

  1. 它仍然没有我想要的那么顺利。如果您对如何更快或更正确地做到这一点有任何解决方案或想法。告诉我。

以下是回收站视图的三种可能不同视图的示例图像。所以这些视图只包括照片和形状。但其他视图包括文本和颜色。

【问题讨论】:

  • 你的意思是“但这只有在每个项目的查看次数有限时才有效。” ?您可以创建的不同类型视图的数量没有上限。当你这样尝试时有什么问题?
  • 在我发布的链接中,他们检查每个 itemViewType 以及它是否与特定类型匹配。然后他们只使用那个 xml 文件。因此,如果我有超过 2000 种不同观点的可能性,那么这种方式可能行不通。我正在从我以编程方式获得的维度构建这些视图。
  • 2000?天哪!我认为一个好主意可能是使用自定义视图组,该视图组对给予它的视图做出适当反应
  • 我试过了,但是有很多不同的变体,这个视图数据来自服务器并且可以随时更改,所以我需要能够以编程方式从服务器详细信息构建它。有些有不同的颜色背景、不同的字体和不同位置的文本以及许多不同大小和形状的图像。
  • 如果服务器控制了它的外观,你还不如把列表换成webview,让服务器显示。不过,我不建议将其作为解决方案——如果您能够写下您的需求、查看重要内容并将您的不同视图类型缩小到更易于管理的范围内,那就更好了!

标签: android android-recyclerview


【解决方案1】:

每个项目视图都可以选择最多 8 个图像,这些图像周围有边框或无边框,最多 4 个带有条纹、实线、无边框、剪贴画的文本框 >每个都可以选择不同的颜色查看项目。每个项目也有不同的 > 尺寸和不同的位置。物品外观的可能性几乎是无穷无尽的

我们实际上可以将其简化为

我有最多 8 个图像和 4 个文本的视图,可以有不同的 风格和位置。

那么只有 45 种可能性来选择将有多少图像和文本(计算没有图像或文本的可能性)。

所以如果我用足够多的元素制作简单的RelativeLayout,然后根据需要移动它们......

Hovewer 说 45 种可能的类型都很好... 太多了。你看到让任何适配器携带太多的项目类型不是很聪明,简单地说,当适配器设置为回收器视图(或 ListView)时,它所做的第一件事就是创建“小”视图池(RecyclerViewPool),它基本上是simple sparse array,这将保留任何已删除的 ViewHolder,直到它被重用的时间到来,如果堆太大,它将被删除。也就是说理论上,在实践中,在指定时间可以有多少 scap 子项是有限制的(对于回收器,我认为它在 5 左右),所以如果我们要创建具有多种类型的视图持有者,我们希望大部分时间都在访问该缓存,并且由于我们的类型 createViewHolder 没有废料视图持有者,因此会被调用,实际上这感觉更像是我们根本没有视图持有者。

还有一件事。

然后我在包含回收器视图的活动中创建了一个视图列表>而不是传递对象列表来创建视图,我只是>传递了视图本身。由于我从来没有>每次都必须阅读每个项目并为每个项目创建一个自定义视图,因此效果要​​好得多。

Recycler 视图用于卸载不再需要的项目(视图),或者我应该说对用户不可见的项目(视图),并尽可能地重用旧视图以减少布局膨胀和搜索的数量为身份证。 并且通过一直加载它们,这个功能就被省略了。

如果我没记错的话,这可能比使用 LinearLayout(垂直)创建 ScrollView 并将所有视图放入其中要慢一点。 但是这里最大的缺点是你所有的视图总是被加载,如果它们很简单,我真的不明白为什么不使用 ScrollView + LinearLayout,但如果不是很好,这会变得复杂,在新手机上,内存不是什么大问题,有 500+ mb 的手机,但即便如此,并非每部手机都会为您的应用程序提供 300 mb。

我的答案

我猜想它会是你想听到的:)

减慢布局膨胀的东西,实际上并不是设置文本、背景或位置,而是加载资源来执行此操作,并搜索视图。

让我们从以下开始: 为您加载的资源使用缓存以尽可能多地重用它们,(我在最后添加了关于图像加载的一小部分)。 FindViewById 真的很慢,我的意思是,只需使用 viewholder 模式并删除对视图的搜索,您就可以获得很好的加速。

您不想为每个元素设置布局。

想法非常简单,根据您的主要层次结构视图(这次)图像和文本的数量创建布局组。在创建 ViewHolder 创建 RelativeLayout 时,推送足够的图像/文本以满足您对位置元素的需求。在绑定数据时移动元素并根据需要设置样式。

为了减少类型的数量,让我们使用四舍五入到 2 的图像/文本数量。

更新 LayoutParams 并不像大多数人认为的那样花费我们太多,它花费了一些,不要误会我的意思,这基本上会迫使父级进行额外的子级测量和布局更新,仅此而已,当视图将被显示时,它必须无论如何都要进行此测量和布局更新:)

假设我们有这两组

  1. 图片(最多 2 张)id:0
  2. 图片(最多 4 张)id:1
  3. 图片(最多 6 张)ID:2
  4. 图片(最多 8 张)ID:3

还有

  1. 文本(最多 2 个)id:0
  2. 文本(最多 4 个)ID:1

这就是我将如何“命名”类型

int type = (img_id)<<1 | (txt_id) 

而总类型数实际上是

int type_count = (4 * 2) = 8

结束我们的适配器“基础”是这样的

public class MyAdapter<MyType extends MyAdapter.MyTypeBase> extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
Context context;
ArrayList<MyType> elements;

public MyAdapter(Context context, ArrayList<MyType> elements) {
    this.context = context;
    this.elements = elements;
}

public int getItemCount() { return elements.size(); }
public MyType get(int position) { return elements.get(position); }

public int getItemViewType(int position) {
    int imgs = get(position).getImageCount();
    int txts = get(position).getTextCount();
    imgs = (imgs/2) * 2 + (imgs % 2 == 0 ? 0 : 1);
    txts = (txts/2) * 2 + (txts % 2 == 0 ? 0 : 1);
    return imgs << 1 | txts;
}

@Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int position) {
    return new ViewHolder(new RelativeLayout(context), position); // or use inflater to inflate some base layout
}


class ViewHolder extends RecyclerView.ViewHolder {
    int textCount, imageCount;
    RelativeLayout root;
    ImageView [] imageViews;
    TextView [] textViews;
    public ViewHolder(RelativeLayout view, int position) {
        super(view);
        root = view;
        textCount = (position & 1) * 2;
        imageCount = (position >> 1) * 2;
        textViews = new TextView[textCount];
        imageViews = new ImageView[imageCount];
        for (int i = 0; i < imageCount; i++) {
            root.addView(imageViews[i] = new ImageView(context)); // or use inflater
        }
        for (int i = 0; i < textCount; i++) {
            root.addView(textViews[i] = new TextView(context)); // or use inflater
        }
    }
}

interface MyTypeBase {
    int getImageCount();
    int getTextCount();
}

是的,有些东西不见了:)

public void onBindViewHolder(ViewHolder viewHolder, int position) {
    MyTypeBase element = get(position);
    vh.textViews[vh.textCount-1].setVisibility(element.getTextCount()%2 == 0? View.VISIBLE : View.GONE);
    vh.imageViews[vh.imageCount-1].setVisibility(element.getImageCount()%2 == 0? View.VISIBLE : View.GONE);

    // now this is your place to shine
}

因为我不知道你能得到什么作为元素的“数据”,所以在这里对你没有多大帮助。因此,在 bindViewHolder 中,您的 viewHolder 将有足够的图像和文本来满足您的元素需要,重新加载任何图像并移动它们。

最后的笔记

我还应该在这里和那里添加 fev 注释:)

懒惰。是的,我的意思是,当您想偷懒时,您会尝试使解决方案尽可能简单,使代码更易于阅读,并且不太可能出现重大错误:)

另外请使用库来显示和缓存图像,而不是每次都加载它们。很少有像 UniwersalImageLoader、Picasso 或 Volley 这样的好作品。我个人使用 UIL,所以这里举个例子。

// This might be done in helper class ^^
DisplayImageOptions OPTIONS_DISC_AND_CACHE = new DisplayImageOptions.Builder()
                .cacheOnDisk(true)
                .cacheInMemory(true)
                .build();

ImageLoaderConfiguration configuration = new ImageLoaderConfiguration.Builder(context)
//              .memoryCacheSize((int) (Runtime.getRuntime().maxMemory() / 1024) / 16) // if you want to reduce it some more ^^
                .threadPoolSize(4)
                .tasksProcessingOrder(QueueProcessingType.LIFO)
                .build();

        imageLoader.init(configuration);

// And thats how to use it
ImageLoader.getInstance().displayImage("MY URL", (ImageView) myImageView, Tools.OPTIONS_DISC_AND_CACHE);

以及为什么你应该使用这样的库,不知道...用于磁盘的内存和缓存允许更快的加载时间。

最后,英语不是我的主要语言,所以如果我拼错了什么,忘记了逗号,或者写错了句子,请务必纠正。

干杯。

【讨论】:

  • 我喜欢这个答案。感谢您抽出宝贵时间将其全部输入。我目前正在使用 UIL 加载图像/位图。我正在考虑使用滚动视图和线性布局,但您对内存的看法是正确的,当我有很多包含大量图像的视图时,我的手机无法处理。我确实喜欢在需要时隐藏和显示内容的想法,但由于每个视图的数量和差异,我需要很多默认视图才能开始。这个周末我会试试,让你知道它是如何工作的。需要大改写。干杯
【解决方案2】:

1 创建一个列表列表,每个项目应包含 1 个或多个图像

    List<List<image>> list 

2 将其传递给您的适配器

3在getItemView方法中使用if来设置类型

    list.get(position).size();

4 再次使用 if 或 isinstanceof 或者我认为 viewHolder 有一种方法可以获取其类并将您的 viewHolder 转换为正确的类。

【讨论】:

  • 但这只有在我在这些视图中只有图像时才有效,在项目视图中的不同位置还有不同大小和颜色的文本和形状。