关于android:如何创建具有多种视图类型的RecyclerView?

How to create RecyclerView with multiple view type?

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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;
    }
}

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


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

所以你做的事情如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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;
        }
    }
}


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

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

基础DataBinder类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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();

......

}

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

示例DataBinder类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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);
        }
    }
}

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

Base DataBindAdapter类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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);

......

}

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

  • getItemCount
    返回DataBinders的总项数

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

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

  • 为getPosition
    从指定DataBinder中的位置将转换逻辑定义到适配器位置

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

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


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

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
       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));
                }

            }
        }


    对的,这是可能的。

    写一个通用视图持有者:

    1
    2
    3
    4
    5
    6
    7
    8
        public abstract class GenericViewHolder extends RecyclerView.ViewHolder
    {
        public GenericViewHolder(View itemView) {
            super(itemView);
        }

        public abstract  void setDataOnView(int position);
    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
         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);
            }
        }
    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    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);
    }


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

    enter image description here
    RecyclerView可以拥有您想要的任意数量的视图,但为了更好的可读性,我们可以看看如何使用两个ViewHolders创建一个。

    It can be done in three simple steps

  • 覆盖public int getItemViewType(int position)
  • 根据onCreateViewHolder()方法中的ViewType返回不同??的ViewHolders
  • 基于onBindViewHolder()方法中的itemViewType填充视图
  • 这是一个小代码片段

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    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
          }
       }
    }

    getItemViewType(int position)是关键

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

    Lets see one example to prove my point. If you want to show two layout
    at alternate positions do this

    1
    2
    3
    4
    5
    6
    7
    8
    @Override
    public int getItemViewType(int position)
    {
       if(position%2==0)       // Even position
         return LAYOUT_ONE;
       else                   // Odd position
         return LAYOUT_TWO;
    }

    相关链接:

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


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
      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();
            }
        }

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


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

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

    getItemViewType(int position)将被调用viewType;

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

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    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);
    }

    编辑: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

    1
    holder = getRecycledViewPool().getRecycledView(mAdapter.getItemViewType(offsetPosition));

    如果没有找到正确类型的recycle ViewHolder,请创建一个新的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    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;
    }

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

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

    1
    2
    3
    4
    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
    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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)
       }
    }

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

    1
    2
    3
    4
    5
    6
    7
    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() }
        }
    }

    此类AdapterItemMedium对象的列表是实际接受List的适配器的数据源,请参见下文。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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))
        }
    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    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)    
        }
    }

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

  • 创建一个视图持有者类
  • 创建一个适配器项类(从AdapterItemBase扩展)
  • ViewHolderProvider中注册视图持有者类
  • 以下是此概念的示例:android-drawer-template
    它更进一步 - 视图类型充当微调器组件,可选适配器项。


    它非常简单直接。

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

    现在来到createViewHolder(ViewGroup parent,int viewType),在不同的viewType的基础上,yon可以膨胀不同的布局文件。如下

    1
    2
    3
    4
    5
    6
    7
    8
     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);
    }

    在onBindViewHolder(VH holder,int position)中,检查holder是AdapterFemaleViewHolder的实例或AdapterMaleViewHolderinstanceof,并相应地赋值。

    ViewHolder可能是这样的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
        class AdapterMaleViewHolder extends RecyclerView.ViewHolder {
                ...
                public AdapterMaleViewHolder(View itemView){
                ...
                }
            }

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

    我推荐这个来自Hannes Dorfmann的图书馆。它将与特定视图类型相关的所有逻辑封装在名为"AdapterDelegate"的单独对象中。
    https://github.com/sockeqwe/AdapterDelegates

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    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);
      }
    }

    实际上,我想改进安东的答案。

    由于getItemViewType(int position)返回一个整数值,因此您可以返回需要膨胀的布局资源ID。这样你就可以在onCreateViewHolder(ViewGroup parent, int viewType)方法中保存一些逻辑。

    此外,我不建议在getItemCount()中进行密集计算,因为在渲染列表时,以及在将每个项目渲染到可见项目之外时,该特定函数至少被调用5次。遗憾的是,因为notifyDatasetChanged()方法是最终的,你不能真正覆盖它,但你可以从适配器中的另一个函数调用它。


    您可以通过使getItemViewType()返回该位置的预期viewType值来处理multipleViewTypes RecyclerAdapter

    我准备了一个MultipleViewTypeAdapter来构建用于考试的MCQ列表,这可能会产生一个可能有2个或更多有效答案(复选框选项)和单个答案问题(radiobutton选项)的问题。

    为此,我从API响应中获取问题的类型,并使用它来决定我必须为该问题显示哪个视图。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    public class MultiViewTypeAdapter extends RecyclerView.Adapter {

        Context mContext;
        ArrayList<Question> dataSet;
        ArrayList<String> questions;
        private Object radiobuttontype1;


        //Viewholder to display Questions with checkboxes
        public static class Checkboxtype2 extends RecyclerView.ViewHolder {
            ImageView imgclockcheck;
            CheckBox checkbox;

            public Checkboxtype2(@NonNull View itemView) {
                super(itemView);
                imgclockcheck = (ImageView) itemView.findViewById(R.id.clockout_cbox_image);
                checkbox = (CheckBox) itemView.findViewById(R.id.clockout_cbox);


            }
        }

            //Viewholder to display Questions with radiobuttons

        public static class Radiobuttontype1 extends RecyclerView.ViewHolder {
            ImageView clockout_imageradiobutton;
            RadioButton clockout_radiobutton;
            TextView sample;

            public radiobuttontype1(View itemView) {
                super(itemView);
                clockout_imageradiobutton = (ImageView) itemView.findViewById(R.id.clockout_imageradiobutton);
                clockout_radiobutton = (RadioButton) itemView.findViewById(R.id.clockout_radiobutton);
                sample = (TextView) itemView.findViewById(R.id.sample);
            }
        }

        public MultiViewTypeAdapter(ArrayList<QueDatum> data, Context context) {
            this.dataSet = data;
            this.mContext = context;

        }

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

            if (viewType.equalsIgnoreCase("1")) {
                View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.clockout_radio_list_row, viewGroup, false);
                return new radiobuttontype1(view);

            } else if (viewType.equalsIgnoreCase("2")) {
                View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.clockout_cbox_list_row, viewGroup, false);
                view.setHorizontalFadingEdgeEnabled(true);
                return new Checkboxtype2(view);

            } else if (viewType.equalsIgnoreCase("3")) {
                View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.clockout_radio_list_row, viewGroup, false);
                return new Radiobuttontype1(view);

            } else if (viewType.equalsIgnoreCase("4")) {
                View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.clockout_radio_list_row, viewGroup, false);
                return new Radiobuttontype1(view);

            } else if (viewType.equalsIgnoreCase("5")) {
                View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.clockout_radio_list_row, viewGroup, false);
                return new Radiobuttontype1(view);
            }


            return null;
        }

        @Override
        public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int viewType) {
            if (viewType.equalsIgnoreCase("1")) {
                options =  dataSet.get(i).getOptions();
                question = dataSet.get(i).getQuestion();
                image = options.get(i).getValue();
                ((radiobuttontype1) viewHolder).clockout_radiobutton.setChecked(false);
                ((radiobuttontype1) viewHolder).sample.setText(question);
                //loading image bitmap in the ViewHolder's View
                Picasso.with(mContext)
                        .load(image)
                        .into(((radiobuttontype1) viewHolder).clockout_imageradiobutton);

            } else if (viewType.equalsIgnoreCase("2")) {
                options = (ArrayList<Clockout_questions_Option>) dataSet.get(i).getOptions();
                question = dataSet.get(i).getQuestion();
                image = options.get(i).getValue();
                //loading image bitmap in the ViewHolder's View
                Picasso.with(mContext)
                        .load(image)
                        .into(((Checkboxtype2) viewHolder).imgclockcheck);

            } else if (viewType.equalsIgnoreCase("3")) {
                    //fit data to viewHolder for ViewType 3
            } else if (viewType.equalsIgnoreCase("4")) {
    //fit data to viewHolder for ViewType 4  
            } else if (viewType.equalsIgnoreCase("5")) {
    //fit data to viewHolder for ViewType 5    
            }
        }

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

        /**
         * returns viewType for that position by picking the viewType value from the
         *     dataset
         */
        @Override
        public int getItemViewType(int position) {
            return dataSet.get(position).getViewType();

        }


    }

    您可以在onBindViewHolder()中避免多个基于条件的viewHolder数据填充,方法是在viewHolders中为相似视图指定相同的ID,这些视图的位置不同。


    使用kotlin可以更轻松地查看类型实现,以下是使用此轻型库的示例https://github.com/Link184/KidAdapter

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    recyclerView.setUp {
        withViewType {
            withLayoutResId(R.layout.item_int)
            withItems(mutableListOf(1, 2, 3, 4, 5, 6))
            bind<Int> { // this - is adapter view hoder itemView, it - current item
                intName.text = it.toString()
            }
        }


        withViewType("SECOND_STRING_TAG") {
            withLayoutResId(R.layout.item_text)
            withItems(mutableListOf("eight","nine","ten","eleven","twelve"))
            bind<String> {
                stringName.text = it
            }
        }
    }

    您可以使用该库:https://github.com/vivchar/RendererRecyclerViewAdapter

    1
    2
    3
    mRecyclerViewAdapter = new RendererRecyclerViewAdapter(); /* included from library */
    mRecyclerViewAdapter.registerRenderer(new SomeViewRenderer(SomeModel.TYPE, this));
    mRecyclerViewAdapter.registerRenderer(...); /* you can use several types of cells */

    `

    对于每个项目,您应该实现ViewRenderer,ViewHolder,SomeModel:

    ViewHolder - 它是回收者视图的简单视图持有者。

    SomeModel - 它是ItemModel接口的模型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class SomeViewRenderer extends ViewRenderer<SomeModel, SomeViewHolder> {

      public SomeViewRenderer(final int type, final Context context) {
        super(type, context);
      }
      @Override
     public void bindView(@NonNull final SomeModel model, @NonNull final SomeViewHolder holder) {
        holder.mTitle.setText(model.getTitle());
     }
     @NonNull
     @Override
     public SomeViewHolder createViewHolder(@Nullable final ViewGroup parent) {
        return new SomeViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.some_item, parent, false));
     }
    }

    有关详细信息,您可以查看文档。