如何使用多种视图类型创建RecyclerView?

Pon*_*pat 795 user-interface android android-recyclerview

来自https://developer.android.com/preview/material/ui-widgets.html

当我们创建时,RecyclerView.Adapter我们必须指定ViewHolder将与适配器绑定.

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

    private String[] mDataset;

    public MyAdapter(String[] myDataset) {
        mDataset = myDataset;
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {
        public TextView mTextView;
        public ViewHolder(TextView v) {
            super(v);
            mTextView = v;
        }
    }

    @Override
    public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.some_layout, parent, false);

        //findViewById...

        ViewHolder vh = new ViewHolder(v);
        return vh;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.mTextView.setText(mDataset[position]);
    }

    @Override
    public int getItemCount() {
        return mDataset.length;
    }
}
Run Code Online (Sandbox Code Playgroud)

那么,是否可以RecyclerView使用多种视图类型进行创建?

Ant*_*vin 1197

是的,这是可能的.只需实现getItemViewType(),并处理中的viewType参数onCreateViewHolder().

所以你做的事情如下:

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    class ViewHolder0 extends RecyclerView.ViewHolder {
        ...
        public ViewHolder0(View itemView){
        ...
        }
    }

    class ViewHolder2 extends RecyclerView.ViewHolder {
        ...
        public ViewHolder2(View itemView){
        ...
    }

    @Override
    public int getItemViewType(int position) {
        // Just as an example, return 0 or 2 depending on position
        // Note that unlike in ListView adapters, types don't have to be contiguous
        return position % 2 * 2;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
         switch (viewType) {
             case 0: return new ViewHolder0(...);
             case 2: return new ViewHolder2(...);
             ...
         }
    }

    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
        switch (holder.getItemViewType()) {
            case 0:
                ViewHolder0 viewHolder0 = (ViewHolder0)holder;
                ...
                break;

            case 2:
                ViewHolder2 viewHolder2 = (ViewHolder2)holder;
                ...
                break;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 然后你必须在onBindViewHolder()方法中转换视图类型,我认为它违背了泛型类型的目的.顺便说一句,谢谢你的回答. (44认同)
  • 您可以创建BaseHolder并为所有必需类型扩展它.然后添加一个抽象的setData,它将在实现持有者中被覆盖(覆盖?).这样您就可以让语言处理类型差异.虽然它只有在您拥有一组所有列表项都可以解释的数据时才有效. (29认同)
  • 如果ViewHolders驻留在RecyclerView适配器中,则它们应该是静态的 (5认同)
  • @GZ95 通常是的,因为不同的视图类型可以对应完全不同的视图布局。ViewHolder 是 [here](http://developer.android.com/training/improving-layouts/smooth-scrolling.html) 中描述的 android 中常见的设计模式。在它是可选使用之前,现在 RecyclerView 强制使用它。 (3认同)
  • 这是我的观点,因为在一个RecyclerView.Adapter中只有一个ViewHolder可用于如何向它添加多个? (3认同)
  • 那不同的布局文件呢?我想更改`optionMenuItem`上的布局。怎么可能?@安东·萨文 (2认同)
  • 添加多个视图类型效果很好,但问题在于绑定数据。滚动数据时随机更改。 (2认同)
  • @delive 3个或更多有什么问题吗?创建适当数量的视图持有者,仅此而已。 (2认同)

yqr*_*itc 83

如果视图类型的布局只有几个,绑定逻辑很简单,请遵循Anton的解决方案.
但是,如果您需要管理复杂的布局和绑定逻辑,代码将会很混乱.

我相信以下解决方案对需要处理复杂视图类型的人有用.

基础DataBinder类

abstract public class DataBinder<T extends RecyclerView.ViewHolder> {

    private DataBindAdapter mDataBindAdapter;

    public DataBinder(DataBindAdapter dataBindAdapter) {
        mDataBindAdapter = dataBindAdapter;
    }

    abstract public T newViewHolder(ViewGroup parent);

    abstract public void bindViewHolder(T holder, int position);

    abstract public int getItemCount();

......

}
Run Code Online (Sandbox Code Playgroud)

在创建单一视图类型时,此类中定义的函数与适配器类几乎完全相同.
对于每种视图类型,通过扩展此DataBinder来创建类.

示例DataBinder类

public class Sample1Binder extends DataBinder<Sample1Binder.ViewHolder> {

    private List<String> mDataSet = new ArrayList();

    public Sample1Binder(DataBindAdapter dataBindAdapter) {
        super(dataBindAdapter);
    }

    @Override
    public ViewHolder newViewHolder(ViewGroup parent) {
        View view = LayoutInflater.from(parent.getContext()).inflate(
            R.layout.layout_sample1, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void bindViewHolder(ViewHolder holder, int position) {
        String title = mDataSet.get(position);
        holder.mTitleText.setText(title);
    }

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

    public void setDataSet(List<String> dataSet) {
        mDataSet.addAll(dataSet);
    }

    static class ViewHolder extends RecyclerView.ViewHolder {

        TextView mTitleText;

        public ViewHolder(View view) {
            super(view);
            mTitleText = (TextView) view.findViewById(R.id.title_type1);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

要管理DataBinder类,请创建适配器类.

Base DataBindAdapter类

abstract public class DataBindAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return getDataBinder(viewType).newViewHolder(parent);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
        int binderPosition = getBinderPosition(position);
        getDataBinder(viewHolder.getItemViewType()).bindViewHolder(viewHolder, binderPosition);
    }

    @Override
    public abstract int getItemCount();

    @Override
    public abstract int getItemViewType(int position);

    public abstract <T extends DataBinder> T getDataBinder(int viewType);

    public abstract int getPosition(DataBinder binder, int binderPosition);

    public abstract int getBinderPosition(int position);

......

}
Run Code Online (Sandbox Code Playgroud)

通过扩展此基类来创建类,然后实例化DataBinder类并覆盖抽象方法

  1. getItemCount
    返回DataBinders的项目总数

  2. getItemViewType
    定义适配器位置和视图类型之间的映射逻辑.

  3. getDataBinder
    根据视图类型返回DataBinder实例

  4. getPosition
    将转换逻辑定义为指定DataBinder中位置的适配器位置

  5. getBinderPosition
    从适配器位置将转换逻辑定义到DataBinder中的位置

希望这个解决方案会有所帮助.
我在GitHub中留下了更多细节解决方案和样本,如果需要,请参考以下链接.
https://github.com/yqritc/RecyclerView-MultipleViewTypesAdapter

  • 多态性FTW:D (5认同)
  • 我对你的代码感到有些困惑,也许你可以帮助我,我不希望我的观点由列表中的位置定义,而是通过它们的视图类型来定义.似乎您的代码中的视图是根据其位置确定的,即.因此,如果im在位置1上,则显示视图1,显示位置3,视图3,其他所有内容都显示位置2的视图.我不希望我的观点基于位置而是基于视图类型 - 所以如果我指定视图类型是图像,它应该显示图像.我怎样才能做到这一点? (3认同)
  • 这段代码没有混淆,这是一个 RecyclerView 适配器模式,这应该像问题的正确答案一样被排除在外。按照@yqritc的链接,花一点时间去发现,你将拥有具有不同类型布局的RecyclerView的完美模式。 (2认同)

Pha*_*inh 40

这是一个完整的示例,展示了具有两种类型的 RecyclerView,视图类型由对象决定。

类模型

open class RecyclerViewItem
class SectionItem(val title: String) : RecyclerViewItem()
class ContentItem(val name: String, val number: Int) : RecyclerViewItem()
Run Code Online (Sandbox Code Playgroud)

适配器代码

const val VIEW_TYPE_SECTION = 1
const val VIEW_TYPE_ITEM = 2

class UserAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    var data = listOf<RecyclerViewItem>()

    override fun getItemViewType(position: Int): Int {
        if (data[position] is SectionItem) {
            return VIEW_TYPE_SECTION
        }
        return VIEW_TYPE_ITEM
    }

    override fun getItemCount(): Int {
        return data.size
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        if (viewType == VIEW_TYPE_SECTION) {
            return SectionViewHolder(
                LayoutInflater.from(parent.context).inflate(R.layout.item_user_section, parent, false)
            )
        }
        return ContentViewHolder(
            LayoutInflater.from(parent.context).inflate(R.layout.item_user_content, parent, false)
        )
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val item = data[position]
        if (holder is SectionViewHolder && item is SectionItem) {
            holder.bind(item)
        }
        if (holder is ContentViewHolder && item is ContentItem) {
            holder.bind(item)
        }
    }

    internal inner class SectionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        fun bind(item: SectionItem) {
            itemView.text_section.text = item.title
        }
    }

    internal inner class ContentViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        fun bind(item: ContentItem) {
            itemView.text_name.text = item.name
            itemView.text_number.text = item.number.toString()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

item_user_section.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/text_section"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#eee"
    android:padding="16dp" />
Run Code Online (Sandbox Code Playgroud)

item_user_content.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="32dp">

    <TextView
        android:id="@+id/text_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        tools:text="Name" />

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

</LinearLayout>
Run Code Online (Sandbox Code Playgroud)

使用示例

val dataSet = arrayListOf<RecyclerViewItem>(
    SectionItem("A1"),
    ContentItem("11", 11),
    ContentItem("12", 12),
    ContentItem("13", 13),

    SectionItem("A2"),
    ContentItem("21", 21),
    ContentItem("22", 22),

    SectionItem("A3"),
    ContentItem("31", 31),
    ContentItem("32", 32),
    ContentItem("33", 33),
    ContentItem("33", 34),
)

recyclerAdapter.data = dataSet
recyclerAdapter.notifyDataSetChanged()
Run Code Online (Sandbox Code Playgroud)

  • 这实际上非常适合“密封类”,或者在这种特殊情况下 - “密封接口”。让父级“密封”有助于保证所有子级都在“if/when”条件下进行检查 (5认同)

Sim*_*mon 35

下面不是伪代码,我测试了它,它对我有用.

我想在我的recyclerview中创建一个headerview,然后在标题下方显示一个用户可以单击的图片列表.

我在代码中使用了一些开关,不知道这是否是最有效的方法,所以请随意发表您的意见:

   public class ViewHolder extends RecyclerView.ViewHolder{

        //These are the general elements in the RecyclerView
        public TextView place;
        public ImageView pics;

        //This is the Header on the Recycler (viewType = 0)
        public TextView name, description;

        //This constructor would switch what to findViewBy according to the type of viewType
        public ViewHolder(View v, int viewType) {
            super(v);
            if (viewType == 0) {
                name = (TextView) v.findViewById(R.id.name);
                decsription = (TextView) v.findViewById(R.id.description);
            } else if (viewType == 1) {
                place = (TextView) v.findViewById(R.id.place);
                pics = (ImageView) v.findViewById(R.id.pics);
            }
        }
    }


    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent,
                                         int viewType)
    {
        View v;
        ViewHolder vh;
        // create a new view
        switch (viewType) {
            case 0: //This would be the header view in my Recycler
                v = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.recyclerview_welcome, parent, false);
                vh = new ViewHolder(v,viewType);
                return  vh;
            default: //This would be the normal list with the pictures of the places in the world
                v = LayoutInflater.from(parent.getContext())
                        .inflate(R.layout.recyclerview_picture, parent, false);
                vh = new ViewHolder(v, viewType);
                v.setOnClickListener(new View.OnClickListener(){

                    @Override
                    public void onClick(View v) {
                        Intent intent = new Intent(mContext, nextActivity.class);
                        intent.putExtra("ListNo",mRecyclerView.getChildPosition(v));
                        mContext.startActivity(intent);
                    }
                });
                return vh;
        }
    }

    //Overriden so that I can display custom rows in the recyclerview
    @Override
    public int getItemViewType(int position) {
        int viewType = 1; //Default is 1
        if (position == 0) viewType = 0; //if zero, it will be a header view
        return viewType;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        //position == 0 means its the info header view on the Recycler
        if (position == 0) {
            holder.name.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(mContext,"name clicked", Toast.LENGTH_SHORT).show();
                }
            });
            holder.description.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(mContext,"description clicked", Toast.LENGTH_SHORT).show();
                }
            });
            //this means it is beyond the headerview now as it is no longer 0. For testing purposes, I'm alternating between two pics for now
        } else if (position > 0) {
           holder.place.setText(mDataset[position]);
            if (position % 2 == 0) {
               holder.pics.setImageDrawable(mContext.getResources().getDrawable(R.drawable.pic1));
            }
            if (position % 2 == 1) {
                holder.pics.setImageDrawable(mContext.getResources().getDrawable(R.drawable.pic2));
            }

        }
    }
Run Code Online (Sandbox Code Playgroud)


Isl*_*ssi 20

对的,这是可能的.

写一个通用视图持有者:

    public abstract class GenericViewHolder extends RecyclerView.ViewHolder
{
    public GenericViewHolder(View itemView) {
        super(itemView);
    }

    public abstract  void setDataOnView(int position);
}
Run Code Online (Sandbox Code Playgroud)

然后创建视图持有者并使它们扩展GenericViewHolder.例如,这一个:

     public class SectionViewHolder extends GenericViewHolder{
    public final View mView;
    public final TextView dividerTxtV;

    public SectionViewHolder(View itemView) {
        super(itemView);
        mView = itemView;
        dividerTxtV = (TextView) mView.findViewById(R.id.dividerTxtV);
    }

    @Override
    public void setDataOnView(int position) {
        try {
            String title= sections.get(position);
            if(title!= null)
                this.dividerTxtV.setText(title);
        }catch (Exception e){
            new CustomError("Error!"+e.getMessage(), null, false, null, e);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

那么RecyclerView.Adapter类将如下所示:

public class MyClassRecyclerViewAdapter extends RecyclerView.Adapter<MyClassRecyclerViewAdapter.GenericViewHolder> {

@Override
public int getItemViewType(int position) {
     // depends on your problem
     switch (position) {
         case : return VIEW_TYPE1;
         case : return VIEW_TYPE2;
         ...
     }
}

    @Override
   public GenericViewHolder onCreateViewHolder(ViewGroup parent, int viewType)  {
    View view;
    if(viewType == VIEW_TYPE1){
        view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout1, parent, false);
        return new SectionViewHolder(view);
    }else if( viewType == VIEW_TYPE2){
        view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout2, parent, false);
        return new OtherViewHolder(view);
    }
    // Cont. other view holders ...
    return null;
   }

@Override
public void onBindViewHolder(GenericViewHolder holder, int position) {
    holder.setDataOnView(position);
}
Run Code Online (Sandbox Code Playgroud)


Gas*_*lén 18

比以往任何时候都简单,忘记 ViewTypes。不建议在一个适配器内使用多种视图类型。它会弄乱代码并破坏单一责任原则,因为现在适配器需要处理逻辑来知道要膨胀哪个视图。

\n

现在想象一下在大型团队中工作,每个团队都必须使用这些视图类型功能之一。如果所有在不同视图类型中工作的团队都接触同一个适配器,将会很混乱。使用 ConcatAdapter 可以解决此问题,您可以在其中隔离适配器。对它们进行一一编码,然后将它们合并到一个视图中。

\n

recyclerview:1.2.0-alpha04现在起你就可以使用了ConcatAdapter

\n

如果您需要具有不同视图类型的视图,您可以为每个部分编写适配器,然后使用 ConcatAdapter 将所有它们合并到一个回收器视图中。

\n

连接适配器

\n

此图显示了一个回收视图具有的三种不同的视图类型:页眉、内容和页脚。

\n

在此输入图像描述

\n

您只需为每个部分创建一个适配器,然后使用 ConcatAdapter 将它们合并到一个 recyclerview 中:

\n
val firstAdapter: FirstAdapter = \xe2\x80\xa6\nval secondAdapter: SecondAdapter = \xe2\x80\xa6\nval thirdAdapter: ThirdAdapter = \xe2\x80\xa6\nval concatAdapter = ConcatAdapter(firstAdapter, secondAdapter,\n                                  thirdAdapter)\nrecyclerView.adapter = concatAdapter\n
Run Code Online (Sandbox Code Playgroud)\n

在此输入图像描述

\n

这就是您需要知道的全部。如果您想处理加载状态,例如在加载发生后删除最后一个适配器,您可以使用LoadState

\n

有关更多深入信息,请关注 Florina Muntenescu 帖子https://medium.com/androiddevelopers/merge-adapters-sequentially-with-mergeadapter-294d2942127a

\n

  • 很好的解决方案,但是当类型混合时如何解决问题?例如,列表如下所示:SecondAdapter、FirstAdapter、FirstAdapter、ThirdAdapter、FirstAdapter、SecondAdapter、ThirdAdapter、FirsrtAdapter、SecondAdapter、ThirdAdapter...? (2认同)

Roh*_*ngh 16

为不同的布局创建不同的ViewHolder

在此输入图像描述
RecyclerView可以拥有您想要的任意数量的视图,但为了更好的可读性,我们可以看看如何使用两个ViewHolders创建一个.

它可以通过三个简单的步骤完成

  1. 覆盖 public int getItemViewType(int position)
  2. 根据onCreateViewHolder()方法中的ViewType返回不同​​的ViewHolders
  3. 基于onBindViewHolder()方法中的itemViewType填充视图

这是一个小代码片段

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

   private static final int LAYOUT_ONE= 0;
   private static final int LAYOUT_TWO= 1;

   @Override
   public int getItemViewType(int position)
   {
      if(position==0)
        return LAYOUT_ONE;
      else
        return LAYOUT_TWO;
   }

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

      View view =null;
      RecyclerView.ViewHolder viewHolder = null;

      if(viewType==LAYOUT_ONE)
      {
          view = LayoutInflater.from(parent.getContext()).inflate(R.layout.one,parent,false);
          viewHolder = new ViewHolderOne(view);
      }
      else
      {
          view = LayoutInflater.from(parent.getContext()).inflate(R.layout.two,parent,false);
          viewHolder= new ViewHolderTwo(view);
      }

      return viewHolder;
   }

   @Override
   public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {

      if(holder.getItemViewType()== LAYOUT_ONE)
      {
            // Typecast Viewholder 
            // Set Viewholder properties 
            // Add any click listener if any 
      }
      else {

        ViewHolderOne vaultItemHolder = (ViewHolderOne) holder;
        vaultItemHolder.name.setText(displayText);
        vaultItemHolder.name.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {
            .......
           }
         });

       }

   }

  //****************  VIEW HOLDER 1 ******************//

   public class ViewHolderOne extends RecyclerView.ViewHolder {

      public TextView name;

      public ViewHolderOne(View itemView) {
         super(itemView);
         name = (TextView)itemView.findViewById(R.id.displayName);
     }
   }


   //****************  VIEW HOLDER 2 ******************//

   public class ViewHolderTwo extends RecyclerView.ViewHolder{

      public ViewHolderTwo(View itemView) {
         super(itemView);

        ..... Do something
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

getItemViewType(int position)是关键

在我看来,创建这种recyclerView的起点是这种方法的知识.因为这个方法是可选的覆盖因此它默认在RecylerView类中不可见,这反过来又使许多开发人员(包括我)想知道从哪里开始.一旦你知道这个方法存在,创建这样的RecyclerView将是一个很小的步骤.

让我们看一个例子来证明我的观点.如果要在备用位置显示两个布局,请执行此操作

@Override
public int getItemViewType(int position)
{
   if(position%2==0)       // Even position 
     return LAYOUT_ONE;
   else                   // Odd position 
     return LAYOUT_TWO;
}
Run Code Online (Sandbox Code Playgroud)

相关链接:

查看我已实现此项目的项目


Say*_*nna 12

对的,这是可能的.在您的适配器getItemViewType布局像这样....

  public class MultiViewTypeAdapter extends RecyclerView.Adapter {

        private ArrayList<Model>dataSet;
        Context mContext;
        int total_types;
        MediaPlayer mPlayer;
        private boolean fabStateVolume = false;

        public static class TextTypeViewHolder extends RecyclerView.ViewHolder {

            TextView txtType;
            CardView cardView;

            public TextTypeViewHolder(View itemView) {
                super(itemView);

                this.txtType = (TextView) itemView.findViewById(R.id.type);
                this.cardView = (CardView) itemView.findViewById(R.id.card_view);
            }
        }

        public static class ImageTypeViewHolder extends RecyclerView.ViewHolder {

            TextView txtType;
            ImageView image;

            public ImageTypeViewHolder(View itemView) {
                super(itemView);

                this.txtType = (TextView) itemView.findViewById(R.id.type);
                this.image = (ImageView) itemView.findViewById(R.id.background);
            }
        }

        public static class AudioTypeViewHolder extends RecyclerView.ViewHolder {

            TextView txtType;
            FloatingActionButton fab;

            public AudioTypeViewHolder(View itemView) {
                super(itemView);

                this.txtType = (TextView) itemView.findViewById(R.id.type);
                this.fab = (FloatingActionButton) itemView.findViewById(R.id.fab);
            }
        }

        public MultiViewTypeAdapter(ArrayList<Model>data, Context context) {
            this.dataSet = data;
            this.mContext = context;
            total_types = dataSet.size();
        }

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

            View view;
            switch (viewType) {
                case Model.TEXT_TYPE:
                    view = LayoutInflater.from(parent.getContext()).inflate(R.layout.text_type, parent, false);
                    return new TextTypeViewHolder(view);
                case Model.IMAGE_TYPE:
                    view = LayoutInflater.from(parent.getContext()).inflate(R.layout.image_type, parent, false);
                    return new ImageTypeViewHolder(view);
                case Model.AUDIO_TYPE:
                    view = LayoutInflater.from(parent.getContext()).inflate(R.layout.audio_type, parent, false);
                    return new AudioTypeViewHolder(view);
            }
            return null;
        }

        @Override
        public int getItemViewType(int position) {

            switch (dataSet.get(position).type) {
                case 0:
                    return Model.TEXT_TYPE;
                case 1:
                    return Model.IMAGE_TYPE;
                case 2:
                    return Model.AUDIO_TYPE;
                default:
                    return -1;
            }
        }

        @Override
        public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int listPosition) {

            Model object = dataSet.get(listPosition);
            if (object != null) {
                switch (object.type) {
                    case Model.TEXT_TYPE:
                        ((TextTypeViewHolder) holder).txtType.setText(object.text);

                        break;
                    case Model.IMAGE_TYPE:
                        ((ImageTypeViewHolder) holder).txtType.setText(object.text);
                        ((ImageTypeViewHolder) holder).image.setImageResource(object.data);
                        break;
                    case Model.AUDIO_TYPE:

                        ((AudioTypeViewHolder) holder).txtType.setText(object.text);

                }
            }
        }

        @Override
        public int getItemCount() {
            return dataSet.size();
        }
    }
Run Code Online (Sandbox Code Playgroud)

供参考链接:https://www.journaldev.com/12372/android-recyclerview-example


lan*_*nyf 6

按照Anton的解决方案,拿出这个ViewHolder来保存/处理/委托不同类型的布局.但是当回收视图ViewHolder不是数据卷的类型时,不确定更换新布局是否有效.

所以基本上 onCreateViewHolder(ViewGroup parent, int viewType)只在需要新视图布局时调用;

getItemViewType(int position)将被要求viewType;

onBindViewHolder(ViewHolder holder, int position)在回收视图时总是调用(引入新数据并尝试显示该数据ViewHolder).

因此,当onBindViewHolder调用它时,需要放入正确的视图布局并更新ViewHolder.

替换视图布局的方法是否正确ViewHolder,或者出现问题?感谢任何评论!

public int getItemViewType(int position) {
    TypedData data = mDataSource.get(position);
    return data.type;
}

public ViewHolder onCreateViewHolder(ViewGroup parent, 
    int viewType) {
    return ViewHolder.makeViewHolder(parent, viewType);
}

public void onBindViewHolder(ViewHolder holder, 
    int position) {
    TypedData data = mDataSource.get(position);
    holder.updateData(data);
}

///
public static class ViewHolder extends 
    RecyclerView.ViewHolder {

    ViewGroup mParentViewGroup;
    View mCurrentViewThisViewHolderIsFor;
    int mDataType;

    public TypeOneViewHolder mTypeOneViewHolder;
    public TypeTwoViewHolder mTypeTwoViewHolder;

    static ViewHolder makeViewHolder(ViewGroup vwGrp, 
        int dataType) {
        View v = getLayoutView(vwGrp, dataType);
        return new ViewHolder(vwGrp, v, viewType);
    }

    static View getLayoutView(ViewGroup vwGrp, 
        int dataType) {
        int layoutId = getLayoutId(dataType);
        return LayoutInflater.from(vwGrp.getContext())
                             .inflate(layoutId, null);
    }

    static int getLayoutId(int dataType) {
        if (dataType == TYPE_ONE) {
            return R.layout.type_one_layout;
        } else if (dataType == TYPE_TWO) {
            return R.layout.type_two_layout;
        }
    }

    public ViewHolder(ViewGroup vwGrp, View v, 
        int dataType) {
        super(v);
        mDataType = dataType;
        mParentViewGroup = vwGrp;
        mCurrentViewThisViewHolderIsFor = v;

        if (data.type == TYPE_ONE) {
            mTypeOneViewHolder = new TypeOneViewHolder(v);
        } else if (data.type == TYPE_TWO) {
            mTypeTwoViewHolder = new TypeTwoViewHolder(v);
        }
    }

    public void updateData(TypeData data) {
        mDataType = data.type;
        if (data.type == TYPE_ONE) {
            mTypeTwoViewHolder = null;
            if (mTypeOneViewHolder == null) {
                View newView = getLayoutView(mParentViewGroup,
                               data.type);

                /**
                 *  how to replace new view with 
                    the view in the parent 
                    view container ???
                 */
                replaceView(mCurrentViewThisViewHolderIsFor, 
                            newView);
                mCurrentViewThisViewHolderIsFor = newView;

                mTypeOneViewHolder = 
                    new TypeOneViewHolder(newView);
            }
            mTypeOneViewHolder.updateDataTypeOne(data);

        } else if (data.type == TYPE_TWO){
            mTypeOneViewHolder = null;
            if (mTypeTwoViewHolder == null) {
                View newView = getLayoutView(mParentViewGroup, 
                               data.type);

                /**
                 *  how to replace new view with 
                    the view in the parent view 
                    container ???
                 */
                replaceView(mCurrentViewThisViewHolderIsFor, 
                            newView);
                mCurrentViewThisViewHolderIsFor = newView;

                mTypeTwoViewHolder = 
                    new TypeTwoViewHolder(newView);
            }
            mTypeTwoViewHolder.updateDataTypeOne(data);
        }
    }
}

public static void replaceView(View currentView, 
    View newView) {
    ViewGroup parent = (ViewGroup)currentView.getParent();
    if(parent == null) {
        return;
    }
    final int index = parent.indexOfChild(currentView);
    parent.removeView(currentView);
    parent.addView(newView, index);
}
Run Code Online (Sandbox Code Playgroud)

编辑: ViewHolder具有成员mItemViewType来保存视图

编辑:貌似在onBindViewHolder(ViewHolder holder,int position)中传入的ViewHolder已经通过查看getItemViewType(int position)来获取(或创建)以确保它是匹配的,所以可能不必担心ViewHolder的type与数据[position]的类型不匹配.有谁知道onBindViewHolder()中的ViewHolder是如何被拾取的?

编辑:看起来回收ViewHolder是按类型选择的,因此没有战士.

编辑:http://wiresareobsolete.com/2014/09/building-a-recyclerview-layoutmanager-part-1/回答这个问题.

它得到的回收ViewHolder如下:

holder = getRecycledViewPool().getRecycledView(mAdapter.getItemViewType(offsetPosition));
Run Code Online (Sandbox Code Playgroud)

如果没有找到ViewHolder正确类型的回收,则创建一个新的.

public ViewHolder getRecycledView(int viewType) {
        final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
        if (scrapHeap != null && !scrapHeap.isEmpty()) {
            final int index = scrapHeap.size() - 1;
            final ViewHolder scrap = scrapHeap.get(index);
            scrapHeap.remove(index);
            return scrap;
        }
        return null;
    }

View getViewForPosition(int position, boolean dryRun) {
    ......

    if (holder == null) {
            final int offsetPosition = mAdapterHelper.findPositionOffset(position);
            if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
                throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
                        + "position " + position + "(offset:" + offsetPosition + ")."
                        + "state:" + mState.getItemCount());
            }

            final int type = mAdapter.getItemViewType(offsetPosition);
            // 2) Find from scrap via stable ids, if exists
            if (mAdapter.hasStableIds()) {
                holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
                if (holder != null) {
                    // update position
                    holder.mPosition = offsetPosition;
                    fromScrap = true;
                }
            }
            if (holder == null && mViewCacheExtension != null) {
                // We are NOT sending the offsetPosition because LayoutManager does not
                // know it.
                final View view = mViewCacheExtension
                        .getViewForPositionAndType(this, position, type);
                if (view != null) {
                    holder = getChildViewHolder(view);
                    if (holder == null) {
                        throw new IllegalArgumentException("getViewForPositionAndType returned"
                                + " a view which does not have a ViewHolder");
                    } else if (holder.shouldIgnore()) {
                        throw new IllegalArgumentException("getViewForPositionAndType returned"
                                + " a view that is ignored. You must call stopIgnoring before"
                                + " returning this view.");
                    }
                }
            }
            if (holder == null) { // fallback to recycler
                // try recycler.
                // Head to the shared pool.
                if (DEBUG) {
                    Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared "
                            + "pool");
                }
                holder = getRecycledViewPool()
                        .getRecycledView(mAdapter.getItemViewType(offsetPosition));
                if (holder != null) {
                    holder.resetInternal();
                    if (FORCE_INVALIDATE_DISPLAY_LIST) {
                        invalidateDisplayListInt(holder);
                    }
                }
            }
            if (holder == null) {
                holder = mAdapter.createViewHolder(RecyclerView.this,
                        mAdapter.getItemViewType(offsetPosition));
                if (DEBUG) {
                    Log.d(TAG, "getViewForPosition created new ViewHolder");
                }
            }
        }
        boolean bound = false;
        if (mState.isPreLayout() && holder.isBound()) {
            // do not update unless we absolutely have to.
            holder.mPreLayoutPosition = position;
        } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
            if (DEBUG && holder.isRemoved()) {
                throw new IllegalStateException("Removed holder should be bound and it should"
                        + " come here only in pre-layout. Holder: " + holder);
            }
            final int offsetPosition = mAdapterHelper.findPositionOffset(position);
            mAdapter.bindViewHolder(holder, offsetPosition);
            attachAccessibilityDelegate(holder.itemView);
            bound = true;
            if (mState.isPreLayout()) {
                holder.mPreLayoutPosition = position;
            }
        }

        final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
        final LayoutParams rvLayoutParams;
        if (lp == null) {
            rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
            holder.itemView.setLayoutParams(rvLayoutParams);
        } else if (!checkLayoutParams(lp)) {
            rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
            holder.itemView.setLayoutParams(rvLayoutParams);
        } else {
            rvLayoutParams = (LayoutParams) lp;
        }
        rvLayoutParams.mViewHolder = holder;
        rvLayoutParams.mPendingInvalidate = fromScrap && bound;
        return holder.itemView;
}
Run Code Online (Sandbox Code Playgroud)


Asa*_*hry 6

虽然选择的答案是正确的,但我只想进一步阐述它。我在 RecyclerView 中为多种视图类型找到了一个有用的自定义适配器。它的Kotlin 版本在这里

自定义适配器如下:

public class CustomAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private final Context context;
    ArrayList<String> list; // ArrayList of your Data Model
    final int VIEW_TYPE_ONE = 1;
    final int VIEW_TYPE_TWO = 2;

    public CustomAdapter(Context context, ArrayList<String> list) { // you can pass other parameters in constructor
        this.context = context;
        this.list = list;
    }

    private class ViewHolder1 extends RecyclerView.ViewHolder {

        TextView yourView;
        ViewHolder1(final View itemView) {
            super(itemView);
            yourView = itemView.findViewById(R.id.yourView); // Initialize your All views prensent in list items
        }
        void bind(int position) {
            // This method will be called anytime a list item is created or update its data
            // Do your stuff here
            yourView.setText(list.get(position));
        }
    }

    private class ViewHolder2 extends RecyclerView.ViewHolder {

        TextView yourView;
        ViewHolder2(final View itemView) {
            super(itemView);
            yourView = itemView.findViewById(R.id.yourView); // Initialize your All views prensent in list items
        }
        void bind(int position) {
            // This method will be called anytime a list item is created or update its data
            //Do your stuff here
            yourView.setText(list.get(position));
        }
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
       if (viewType == VIEW_TYPE_ONE) {
           return new ViewHolder1(LayoutInflater.from(context).inflate(R.layout.your_list_item_1, parent, false));
       }
       //if its not VIEW_TYPE_ONE then its VIEW_TYPE_TWO
       return new ViewHolder2(LayoutInflater.from(context).inflate(R.layout.your_list_item_2, parent, false));

    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (list.get(position).type == Something) { // Put your condition, according to your requirements
            ((ViewHolder1) holder).bind(position);
        } else {
            ((ViewHolder2) holder).bind(position);
        }
    }

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

    @Override
    public int getItemViewType(int position) {
        // Here you can get decide from your model's ArrayList, which type of view you need to load. Like
        if (list.get(position).type == Something) { // Put your condition, according to your requirements
            return VIEW_TYPE_ONE;
        }
        return VIEW_TYPE_TWO;
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 5

我有一个更好的解决方案,允许以声明和类型安全的方式创建多个视图类型.它是用Kotlin写的,顺便说一句,这真的很棒.

所有必需视图类型的简单视图持有者

class ViewHolderMedium(itemView: View) : RecyclerView.ViewHolder(itemView) {
    val icon: ImageView = itemView.findViewById(R.id.icon) as ImageView
    val label: TextView = itemView.findViewById(R.id.label) as TextView
}
Run Code Online (Sandbox Code Playgroud)

有一个适配器数据项的抽象.请注意,视图类型由特定视图持有者类的hashCode表示(Kotlin中的KClass)

trait AdapterItem {
   val viewType: Int
   fun bindViewHolder(viewHolder: RecyclerView.ViewHolder)
}

abstract class AdapterItemBase<T>(val viewHolderClass: KClass<T>) : AdapterItem {
   override val viewType: Int = viewHolderClass.hashCode()  
   abstract fun bindViewHolder(viewHolder: T)
   override fun bindViewHolder(viewHolder: RecyclerView.ViewHolder) {
       bindViewHolder(viewHolder as T)
   }
}
Run Code Online (Sandbox Code Playgroud)

bindViewHolder需要在具体的适配器项类中重写(类型安全方式)

class AdapterItemMedium(val icon: Drawable, val label: String, val onClick: () -> Unit) : AdapterItemBase<ViewHolderMedium>(ViewHolderMedium::class) {
    override fun bindViewHolder(viewHolder: ViewHolderMedium) {
        viewHolder.icon.setImageDrawable(icon)
        viewHolder.label.setText(label)
        viewHolder.itemView.setOnClickListener { onClick() }
    }
}
Run Code Online (Sandbox Code Playgroud)

这些AdapterItemMedium对象的列表是实际接受的适配器的数据源,List<AdapterItem>见下文.

此解决方案的重要部分是视图持有者工厂,它将提供特定ViewHolder的新实例

class ViewHolderProvider {
    private val viewHolderFactories = hashMapOf<Int, Pair<Int, Any>>()

    fun provideViewHolder(viewGroup: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val (layoutId: Int, f: Any) = viewHolderFactories.get(viewType)
        val viewHolderFactory = f as (View) -> RecyclerView.ViewHolder
        val view = LayoutInflater.from(viewGroup.getContext()).inflate(layoutId, viewGroup, false)
        return viewHolderFactory(view)
    }

    fun registerViewHolderFactory<T>(key: KClass<T>, layoutId: Int, viewHolderFactory: (View) -> T) {
        viewHolderFactories.put(key.hashCode(), Pair(layoutId, viewHolderFactory))
    }
}
Run Code Online (Sandbox Code Playgroud)

简单的适配器类看起来像这样

public class MultitypeAdapter(val items: List<AdapterItem>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

   val viewHolderProvider = ViewHolderProvider() // inject ex Dagger2

   init {
        viewHolderProvider!!.registerViewHolderFactory(ViewHolderMedium::class, R.layout.item_medium, { itemView ->
            ViewHolderMedium(itemView)
        })
   }

   override fun getItemViewType(position: Int): Int {
        return items[position].viewType
    }

    override fun getItemCount(): Int {
        return items.size()
    }

    override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): RecyclerView.ViewHolder? {
        return viewHolderProvider!!.provideViewHolder(viewGroup, viewType)
    }

    override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
        items[position].bindViewHolder(viewHolder)     
    }
}
Run Code Online (Sandbox Code Playgroud)

创建新视图类型只需3个步骤:

  1. 创建一个视图持有者类
  2. 创建一个适配器项类(从AdapterItemBase扩展)
  3. 注册视图持有者类 ViewHolderProvider

下面是这个概念的一个例子:android-drawer-template 它更进一步 - 视图类型充当微调器组件,可选适配器项.


Tul*_*lsi 5

这非常简单直接。

只需在适配器中重写getItemViewType()方法即可。根据数据返回不同的itemViewType值。例如,考虑具有成员isMale的Person类型的对象,如果isMale为true,则返回1,isMale为false,在getItemViewType()方法中返回2 。

现在谈到createViewHolder(ViewGroup父类,int viewType),基于不同的viewType yon可以膨胀不同的布局文件。像下面

 if (viewType ==1){
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.male,parent,false);
    return new AdapterMaleViewHolder(view);
}
else{
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.female,parent,false);
    return new AdapterFemaleViewHolder(view);
}
Run Code Online (Sandbox Code Playgroud)

onBindViewHolder(VH保持器,INT位置)检查其中支架是实例AdapterFemaleViewHolderAdapterMaleViewHolder通过instanceof,并相应地分配的值。

ViewHolder可能会像这样

    class AdapterMaleViewHolder extends RecyclerView.ViewHolder {
            ...
            public AdapterMaleViewHolder(View itemView){
            ...
            }
        }

    class AdapterFemaleViewHolder extends RecyclerView.ViewHolder {
         ...
         public AdapterFemaleViewHolder(View itemView){
            ...
         }
    }
Run Code Online (Sandbox Code Playgroud)


小智 5

我推荐 Hannes Dorfmann 的这个库。它将与特定视图类型相关的所有逻辑封装在一个名为“AdapterDelegate”的单独对象中。

https://github.com/sockeqwe/AdapterDelegates

public class CatAdapterDelegate extends AdapterDelegate<List<Animal>> {

  private LayoutInflater inflater;

  public CatAdapterDelegate(Activity activity) {
    inflater = activity.getLayoutInflater();
  }

  @Override public boolean isForViewType(@NonNull List<Animal> items, int position) {
    return items.get(position) instanceof Cat;
  }

  @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
    return new CatViewHolder(inflater.inflate(R.layout.item_cat, parent, false));
  }

  @Override public void onBindViewHolder(@NonNull List<Animal> items, int position,
      @NonNull RecyclerView.ViewHolder holder, @Nullable List<Object> payloads) {

    CatViewHolder vh = (CatViewHolder) holder;
    Cat cat = (Cat) items.get(position);

    vh.name.setText(cat.getName());
  }

  static class CatViewHolder extends RecyclerView.ViewHolder {

    public TextView name;

    public CatViewHolder(View itemView) {
      super(itemView);
      name = (TextView) itemView.findViewById(R.id.name);
    }
  }
}

public class AnimalAdapter extends ListDelegationAdapter<List<Animal>> {

  public AnimalAdapter(Activity activity, List<Animal> items) {

    // DelegatesManager is a protected Field in ListDelegationAdapter
    delegatesManager.addDelegate(new CatAdapterDelegate(activity))
                    .addDelegate(new DogAdapterDelegate(activity))
                    .addDelegate(new GeckoAdapterDelegate(activity))
                    .addDelegate(23, new SnakeAdapterDelegate(activity));

    // Set the items from super class.
    setItems(items);
  }
}
Run Code Online (Sandbox Code Playgroud)