【问题标题】:Dynamically add layout to another layout将布局动态添加到另一个布局
【发布时间】:2015-11-21 23:16:25
【问题描述】:

我有一个 RecyclerView 适配器。在onBindViewHolder 中,我检查传递给适配器的每个项目有多少图像。根据图像的数量,我想将子布局加载/膨胀到主布局中。

例如:

  • 如果项目有 1 张图片,我需要将 images_one.xml 填充到主布局中
  • 如果项目有 2 张图片,我需要将 images_two.xml 填充到主布局中
  • 等,最多 8 张图片 (images_eight.xml)

这里是主要布局,main_layout.xml:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />

    // INFLATE CHILD LAYOUT HERE (images_one.xml, images_two.xml, etc.)

    <TextView
        android:id="@+id/desc"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />

</LinearLayout>

这里是需要膨胀到主布局中的子布局之一,images_two.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/image_one"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        />

    <ImageView
        android:id="@+id/image_two"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        />

</LinearLayout>

最后,这是我的 RecyclerView 适配器:

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

    private static Context context;
    private List<Message> mDataset;

    public RecyclerAdapter(Context context, List<Message> myDataset) {
        this.context = context;
        this.mDataset = myDataset;
    }

    public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnCreateContextMenuListener, View.OnClickListener {
        public TextView title;
        public TextView desc;

        public ViewHolder(View view) {
            super(view);
            view.setOnCreateContextMenuListener(this);

            title = (TextView) view.findViewById(R.id.title);
            desc = (TextView) view.findViewById(R.id.desc);
        }
    }

    @Override
    public RecyclerAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.main_layout, parent, false);
        ViewHolder vh = new ViewHolder((LinearLayout) view);

        return vh;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        Message item = mDataset.get(position);

        holder.title.setText(item.getTitle());
        holder.desc.setText(item.getDesc());

        int numImages = item.getImages().size();

        if (numImages == 1) {
            // Inflate images_one.xml into main_layout.xml
        } else if (numImages == 2) {
            // Inflate images_two.xml into main_layout.xml
        } else if (numImages == 3) {
            // Inflate images_three.xml into main_layout.xml
        }
        // ETC...
    }

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

}

实现这一点的最佳方式是什么?

【问题讨论】:

  • 如何在 RecyclerView 适配器中实现?
  • 将它充气到您的 ViewHolder 中的 itemView 中。
  • 如果我在 ViewHolder 中给它充气,我无法检查该项目有多少张图片。
  • onBindViewHolder(ViewHolder holder, int position)中的持有者

标签: android android-layout android-activity android-xml android-recyclerview


【解决方案1】:

Android 打算使用ViewHolders 的方式是在onCreateViewHolder() 中创建/扩展视图,然后在onBindViewHolder() 中填充适配器数据。这很重要,因为 onCreateViewHolder() 仅在没有可回收的内容时调用。

ListView 不同,RecyclerView 实际上是回收ViewHolders 而不是Views。所以试图在onBindViewHolder() 中增加视图是行不通的。

注意到onCreateViewHolder() 中的viewType 参数了吗?这才是关键。

首先,您需要在适配器中覆盖 getItemViewType()。让我们在这里走捷径;由于视图类型是int,我们只使用项目中的图像数量作为视图类型:

    @Override
    public int getItemViewType(int position) {
        //  ... return the number of images for data at position
    }

(通常我会定义 final ints 之类的 VIEW_TYPE_ONE_IMAGE 等返回,这是正确的做法。)

适配器如何使用视图类型?如果位置 0 的数据的视图类型为 4,则 RecyclerView 只会将在 onCreateViewHolder()viewType == 4 中创建的 ViewHolder 传递给带有 position == 0onBindViewHolder()

现在您的onCreateViewHolder() 可能如下所示:

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.main_layout, parent, false);
        return new ViewHolder(view, viewType);
    }

(仅供参考:onCreateViewHolder() 的返回类型应该是 your ViewHolder 类型,而不是 RecyclerViews。)

请注意,我在您的 ViewHolder 构造函数中添加了一个 viewType 参数:

    public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnCreateContextMenuListener, View.OnClickListener {
        public TextView title;
        public TextView desc;
        public ImageView img1;
        public ImageView img2;

        public ViewHolder(View view, int viewType) {
            super(view);
            view.setOnCreateContextMenuListener(this);

            title = (TextView) view.findViewById(R.id.title);
            desc = (TextView) view.findViewById(R.id.desc);

            ViewGroup placeholder = (ViewGroup) view.findViewById(R.id.placeholder);

            // here you can use the viewType to help you figure 
            // out which child views you need references for
            switch (viewType) {
            case 1:
                // ...
                break;

            case 2:
                LayoutInflater.from(parent.getContext()).inflate(R.layout.images_two, placeholder);       // this combines inflate() and addView()
                img1 = placeholder.findViewById(R.id.image_one);
                img2 = placeholder.findViewById(R.id.image_two);
                break;

            // ...
            }
        }
    }

如果您在所有不同的视图中使用相同的 ID,则不一定需要在构造函数中使用 viewType;您可以在onBindViewHolder() 中检查它们是否为空。


如果您有许多不同的视图类型,组织创建/绑定代码的一种好方法是使您的 ViewHolder 子类抽象,进一步将其子类化为每种视图类型的不同类型,然后返回适当输入onCreateViewHolder()。在你的抽象类型中声明一个像public void bind(YourItemType item) 这样的抽象方法并在你的子类中实现,那么你的onBindViewHolder() 方法就是:

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.bind(data.get(position));  // ... or however you access an item at position
    }

...和创建看起来像:

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View view = null;

        switch (viewType) {
        case 1:
            view = LayoutInflater.from(parent.getContext()).inflate(R.layout.images_one, parent, false);
            return new OneImageViewHolder(view);

        case 2:
            view = LayoutInflater.from(parent.getContext()).inflate(R.layout.images_two, parent, false);
            return new TwoImageViewHolder(view);

        // ... etc. etc.
        }
    }

别忘了,你可能会在onBindViewHolder() 中传递一个回收的ViewHolder,所以你必须假设你的所有视图都是“脏的”,并将每个子视图设置为@987654361 处数据的已知状态@。

【讨论】:

  • 这是一个非常棒的答案,但我有一个担忧。 main_layout.xml 怎么了?看起来,使用您的方法,我需要创建 8 个不同的“主要布局”(每个图像数量一个),除了图像部分之外,每个布局都完全相同?对吗?
  • 没有。我只是为答案写了一些简化的代码。这个想法是,使用视图类型,您可以根据列表项的需要创建不同的视图。我将更新答案以包含 main_layout.xml。
  • 好的,我更新了代码,但不确定具体细节。无论如何,策略是扩充您的主视图,根据视图类型扩充您的图像视图,然后将图像视图添加到主视图。因此,无论您如何拆分视图,总有一种方法可以使用视图类型来实现您的目标。
  • 这看起来可以满足我的需求。当我回到我的电脑时,我会试一试,然后回复你。再次感谢。
  • 我遇到的唯一问题是它似乎将图像布局添加到主布局的末尾。如何将子布局膨胀到主布局中的 placeholder 视图中,而不是主布局的末尾?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多