Как создать RecyclerView с несколькими типами представлений

Из Создайте динамические списки с помощью RecyclerView:

Когда мы создаем 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;
    }
}

Можно ли создать RecyclerView с несколькими типами просмотра?


person Pongpat    schedule 07.10.2014    source источник
comment
В дополнение к ответу Антона см. Мой ответ здесь: stackoverflow.com/questions/25914003/   -  person ticofab    schedule 31.03.2015
comment
Проверьте эту ссылку, которая также может быть вам полезна stackoverflow.com/a/39972276/3946958   -  person Ravindra Kushwaha    schedule 19.10.2016
comment
Хорошее руководство здесь: guides.codepath.com/android/   -  person Gene Bo    schedule 17.11.2016
comment
Проверьте эту ссылку, она работает stackoverflow.com/questions/39971350/ Если есть проблема, дайте мне знать   -  person Ravindra Kushwaha    schedule 23.01.2017
comment
Отличная библиотека для его реализации github.com/vivchar/RendererRecyclerViewAdapter   -  person Vitaly    schedule 28.01.2018
comment
Посмотрите этот видеоурок: youtu.be/UZwiKdrm768 Я считаю его полезным. Надеюсь, это тебе тоже поможет.   -  person Chandu    schedule 16.07.2020
comment
Что ж, это 2021 год, и я подумал, что этот ответ нуждается в обновлении. В AndroidX теперь есть нечто, называемое ConcatAdapter, которое новичкам может оказаться проще в использовании. проверьте это, чтобы получить простое руководство: blog.mindorks.com/   -  person Shikhar Deep    schedule 19.01.2021


Ответы (23)


Да, это возможно. Просто реализуйте 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;
        }
    }
}
person Anton Savin    schedule 07.10.2014
comment
Как будет выглядеть код в viewholder? Предполагается ли, что один зритель будет иметь один тип просмотра? - person Pongpat; 08.10.2014
comment
@ GZ95 в целом да, потому что разные типы представлений могут соответствовать совершенно разным макетам представлений. ViewHolder - это распространенный шаблон проектирования в Android, описанный здесь. Раньше его использование было необязательным, теперь RecyclerView требует его использования. - person Anton Savin; 08.10.2014
comment
Это моя точка зрения, поскольку в одном RecyclerView.Adapter доступен только один ViewHolder, как вы собираетесь добавить к нему несколько? - person Pongpat; 08.10.2014
comment
@ GZ95 вы можете создать подкласс MyAdapter.ViewHolder для каждого типа представления. - person Anton Savin; 08.10.2014
comment
Затем вам нужно указать тип держателя просмотра в методе onBindViewHolder (), который, как мне кажется, противоречит цели универсального типа. Кстати, спасибо за ответ. - person Pongpat; 08.10.2014
comment
Что ж, используя ListView, вы также должны выполнить приведение (более того, вы делаете это, даже если у вас есть только один тип представления, так что здесь есть некоторые улучшения). Насколько мне известно, в Java нет вариативных дженериков, поэтому они должны использовать то, что у них есть. - person Anton Savin; 08.10.2014
comment
Вы можете создать BaseHolder и расширить его для всех необходимых типов. Затем добавьте абстрактный setData, который будет переопределен (переопределен?) В держателях реализации. Таким образом, вы позволяете языку обрабатывать различия типов. Хотя это работает только в том случае, если у вас есть единый набор данных, который могут интерпретировать все элементы списка. - person DariusL; 05.12.2014
comment
А как насчет другого файла макета? Хочу сменить раскладку на optionMenuItem. Насколько это возможно? @AntonSavin - person Pratik Butani; 23.02.2015
comment
@ PratikButani-AndroidButs, когда вы создаете конкретный ViewHolder, вы инициализируете его с помощью некоторого View, которое вы можете создать любым удобным для вас способом - в частности, вы можете раздуть его из любого ресурса макета XML, который вы хотите. - person Anton Savin; 28.02.2015
comment
stackoverflow.com/q/28581712/1318946 @AntonSavin Я просто застрял с этой проблемой - person Pratik Butani; 02.03.2015
comment
см. мой ответ здесь: stackoverflow.com/questions/25914003/ - person ticofab; 31.03.2015
comment
это отлично работает при добавлении нескольких типов представления, но проблема связана с данными привязки. при прокрутке данных данные изменяются случайным образом. - person Hemant Shori; 28.04.2015
comment
@Hemant, наверное, вы неправильно это реализовали. Если у вас есть конкретная проблема, лучше задать новый вопрос. - person Anton Savin; 28.04.2015
comment
Перечисления были хороши для моделирования оператора switch - person JakeWilson801; 22.12.2015
comment
@AntonSavin: Я столкнулся с связанной проблемой ... Не могли бы вы помочь мне с моим вопросом здесь? - person Y.S; 27.12.2015
comment
Ваши ViewHolders должны быть статичными, если они находятся в вашем адаптере RecyclerView. - person Samer; 01.02.2016
comment
у тебя есть пример о 3 и более? - person ; 15.04.2016
comment
@delive В чем проблема с 3 и более? Создайте соответствующее количество держателей просмотров и все. - person Anton Savin; 15.04.2016
comment
@Samer, можешь объяснить, почему ViewHolders должны быть статичными? - person AJW; 03.07.2016
comment
В чем разница между getItemViewType и getViewType? getItemViewType не является методом по умолчанию в RecyclerAdapter. - person Martin Erlic; 06.07.2016
comment
@bluemunch Я не понимаю вашего вопроса, AFAIK нет такой вещи, как getViewType() в Android. - person Anton Savin; 07.07.2016
comment
Проверьте другое решение по этой ссылке, которое также может быть полезно для вас stackoverflow.com/a/39972276/3946958 - person Ravindra Kushwaha; 19.10.2016
comment
Как насчет положения onBindViewHolder. Например, view1 содержит 8 элементов, а view2 - 23 элемента. - person drojokef; 13.02.2017
comment
Спасибо @AntonSavin! Я не понимаю, как я могу указать, какое представление должно использовать партикулярный ViewHolder. То есть я создаю адаптер, передающий макет для inflate(), я не понимаю, что getItemViewType() должен получить в качестве параметра. Как я могу сказать, что если я использую адаптер для recyclerView с id R.id.r1, используйте ViewHolder0, а если я использую recyclerView с id R.id.r2, используйте ViewHolder1 '? - person A. Wolf; 24.04.2017
comment
Параметр @ A.Wolf getItemViewType() - это индекс элемента в списке. Ваша внутренняя логика решает, какой тип должен иметь этот элемент. Если у вас есть два разных экземпляра RecyclerView, вы, вероятно, создадите для них два разных класса адаптеров, чтобы реализовать другую логику. - person Anton Savin; 24.04.2017
comment
Моя идея состоит в том, чтобы иметь только один адаптер для recyclerview, который имеет одно изображение и одно текстовое представление, и я внедряю другой макет из конструктора адаптера. Я хочу изменить поведение в зависимости от этого значения. Мне нужно переключить этот метод на основе идентификатора макета ресурса? - person A. Wolf; 24.04.2017
comment
Лучший способ - получить конкретный элемент из своего списка, а затем решить, что вы хотите с ним сделать. itemsList.get(position) - person Vahid Amiri; 02.09.2017
comment
0 или 2 действительно сбили меня с толку, когда я впервые прочитал этот ответ. Я предлагаю упростить его до возврата 0 или 1, использовать константы и удалить кучу кода, чтобы сделать этот ответ действительно простым для понимания с первого взгляда. - person Dave Thomas; 21.11.2017
comment
Это отлично работает при добавлении нескольких типов представления, но проблема связана с данными привязки. Как я могу исправить случайное изменение данных? У меня уже определены 3 типа представления. - person Gianmarco Ferruzzi; 01.04.2019
comment
@GianmarcoFerruzzi Лучше задайте это отдельным вопросом - person Anton Savin; 02.04.2019
comment
Также посмотрите мой вопрос stackoverflow.com/q/58693741/3496570 - person AndroidGeek; 04.11.2019
comment
Вы сильно полагаетесь на позицию. Что, если это не имеет отношения к позиции? - person RonTLV; 21.11.2019
comment
@RonTLV, как правило, у вас должен быть набор данных, который вы хотите визуализировать. Получите элемент на основе положения и определите, как его визуализировать на основе этого. - person Anton Savin; 22.11.2019

Если макетов для типов представлений немного, а логика привязки проста, выполните Решение Антона. Но код будет беспорядочным, если вам нужно управлять сложными макетами и логикой привязки.

Я считаю, что следующее решение будет полезно для тех, кому нужно обрабатывать сложные типы представлений.

Базовый класс 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();

......

}

Функции, которые необходимо определить в этом классе, во многом аналогичны классу адаптера при создании единого типа представления.

Для каждого типа представления создайте класс, расширив этот 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);
        }
    }
}

Чтобы управлять классами DataBinder, создайте класс адаптера.

Базовый класс 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);

......

}

Создайте класс, расширив этот базовый класс, а затем создайте экземпляры классов DataBinder и переопределите абстрактные методы.

  1. getItemCount
    Возвращает общее количество элементов DataBinders

  2. getItemViewType
    Определите логику сопоставления между положением адаптера и типом представления.

  3. getDataBinder
    Возвращает экземпляр DataBinder в зависимости от типа представления

  4. getPosition
    Определить логику преобразования в позицию адаптера из позиции в указанном DataBinder

  5. getBinderPosition
    Определить логику преобразования в позицию в DataBinder из позиции адаптера

Я оставил более подробное решение и образцы на GitHub, поэтому обратитесь к RecyclerView-MultipleViewTypeAdapter если нужно.

person yqritc    schedule 01.04.2015
comment
Меня немного смущает ваш код, возможно, вы можете мне помочь, я бы хотел, чтобы мои представления определялись не позициями в списке, а их типами представления. Похоже, что представления в вашем коде определяются на основе их позиций, т.е. поэтому, если я на позиции 1, отображается вид 1, позиция 3, вид 3, а все остальное отображает вид позиции 2. Я не хочу основывать свои взгляды на позициях, а на типах представлений, поэтому, если я укажу, что тип представления - изображения, он должен отображать изображение. Как я могу это сделать? - person Simon; 25.04.2015
comment
Извините, я не смог полностью понять ваш вопрос ..., но вам нужно где-то написать логику, чтобы привязать позицию и тип просмотра. - person yqritc; 27.04.2015
comment
этот код не запутан, это шаблон адаптера RecyclerView, и это должно быть исключено как правильный ответ на вопрос. Перейдите по ссылке @yqritc, потратьте немного времени на открытие, и у вас будет идеальный шаблон для RecyclerView с разными типами макетов. - person Stoycho Andreev; 27.04.2015
comment
newbe здесь, public class DataBinder<T extends RecyclerView.ViewHolder> может кто-нибудь сказать мне, как <T someClass> называется, так что я могу погуглить, если я увижу термин. Также, когда я говорю abstract public class DataBinder<T extends RecyclerView.ViewHolder>, означает ли это, что этот класс имеет тип ViewHolder, поэтому в результате каждый класс, расширяющий этот класс, будет иметь типviewHolder, в этом ли идея? - person rgv; 16.07.2015
comment
@yqritc Я использовал упомянутую здесь библиотеку, у меня проблема с несколькими DataBinder, я разместил вопрос о Stackoverflow. Вот ссылка stackoverflow.com/questions/33894351/. Помогите плз. - person Soft Kaka; 24.11.2015
comment
Очень чистое решение. Работает плавно и элегантно. @Simon Я понял ваш вопрос. Вы должны создать какую-то логику в своем классе адаптера. Например; вы можете использовать position %3 == 0 вместо position == 0. Но этого может быть недостаточно в зависимости от размера вашего набора данных для этого типа. - person Erdi İzgi; 01.12.2016
comment
@cesards, ты заставил меня снова освежить мои знания о полиморфизме, лол .... Java неплохая - person Paul Okeke; 06.09.2018
comment
@rgv это общий тип .. поиск Generics в Java - person Deeptimay Routray; 28.06.2021

Ниже не является псевдокодом. Я протестировал его, и он у меня сработал.

Я хотел создать заголовок в моем recyclerview, а затем отобразить список изображений под заголовком, на который пользователь может щелкнуть.

Я использовал несколько переключателей в своем коде и не знаю, является ли это наиболее эффективным способом сделать это, поэтому не стесняйтесь оставлять свои комментарии:

   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;
        }
    }

    //Overridden 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 it's 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));
            }
        }
    }
person Simon    schedule 11.04.2015
comment
Это хорошо, а что, если мне нужно несколько заголовков в динамических позициях? Скажем, список элементов с заголовками, определяющими категории. Казалось бы, ваше решение требует, чтобы специальные заголовки находились в заранее определенных позициях int. - person Bassinator; 04.08.2017

Создайте другой ViewHolder для разных макетов

Введите здесь описание изображения

RecyclerView может иметь любое количество держателей просмотра, которое вы хотите, но для лучшей читаемости давайте посмотрим, как создать один с двумя ViewHolder.

Это можно сделать в три простых шага

  1. Переопределить public int getItemViewType(int position)
  2. Возвращать разные ViewHolders на основе ViewType в onCreateViewHolder() методе
  3. Заполнить представление на основе itemViewType в onBindViewHolder() методе

Вот небольшой фрагмент кода:

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 будет легкой прогулкой.

Давайте посмотрим на один пример, чтобы доказать мою точку зрения. Если вы хотите показать два макета в разных положениях, сделайте это

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

Соответствующие ссылки:

Ознакомьтесь с проект, в котором я это реализовал.

person Rohit Singh    schedule 03.07.2018

Да, это возможно.

Напишите общий держатель представления:

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

    public abstract  void setDataOnView(int position);
}

затем создайте своих держателей представлений и заставьте их расширять 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);
        }
    }
}

тогда класс 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);
}
person Islam Assi    schedule 25.12.2015
comment
Как тогда использовать в деятельности? Следует ли передавать тип через метод? - person skm; 12.08.2018
comment
Как использовать этот адаптер в действии? И как он распознает, какой тип находится в списке - person skm; 12.08.2018

Да, это возможно.

В вашем адаптере getItemViewType Layout вот так ....

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();
    }
}

Для справочной ссылки: Пример Android RecyclerView - несколько типов просмотра

person Sayan Manna    schedule 03.04.2018
comment
Я переформатировал свой код, чтобы имитировать этот фрагмент, и теперь он отлично работает. Проблема, с которой я столкнулся, заключалась в том, что при смахивании за пределы текущей страницы он вылетал. Больше никаких сбоев! Отличная модель. Спасибо . Отличная работа. - person user462990; 04.01.2019
comment
Не мог найти ничего полезного в течение нескольких дней, пока не увидел это, Спасибо! - person Itay Braha; 04.05.2019

Следуя решении Антона, я придумал это ViewHolder, который содержит / обрабатывает / делегирует различные типы макетов.

Но я не уверен, сработает ли заменяющий новый макет, если ViewHolder представления повторного использования не является типом свернутых данных.

Таким образом, onCreateViewHolder(ViewGroup parent, int viewType) вызывается только тогда, когда требуется новый макет представления;

getItemViewType(int position) будет вызываться для viewType;

onBindViewHolder(ViewHolder holder, int position) всегда вызывается при перезапуске представления (вводятся новые данные и пытаются отобразить с ними ViewHolder).

Поэтому, когда вызывается onBindViewHolder, его необходимо поместить в правильный макет представления и обновить 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 can I replace a 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 can I replace a 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, позиция int) переданный ViewHolder был взят (или создан) путем просмотра getItemViewType (позиция int), чтобы убедиться, что это совпадение, поэтому, возможно, не нужно беспокоиться о том, что ViewHolder type не соответствует типу data [position].

Похоже, Recycle ViewHolder выбирается по типу, так что там нет воина.

Создание LayoutManager RecyclerView - Часть 1 отвечает на этот вопрос.

Он получает рециркуляцию ViewHolder как:

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

Или создайте новую, если не нашли 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;
}
person lannyf    schedule 11.01.2015

У меня есть лучшее решение, которое позволяет создавать несколько типов представлений декларативным и типобезопасным способом. Он написан на Котлине, что, кстати, очень приятно.

Простые держатели просмотра для всех необходимых типов представлений

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
}

Есть абстракция элемента данных адаптера. Обратите внимание, что тип представления представлен хэш-кодом определенного класса держателя представления (KClass в Kotlin).

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 необходимо переопределить в конкретных классах элементов адаптера (типобезопасный способ).

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<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))
    }
}

А простой класс адаптера выглядит так:

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)
    }
}

Есть всего три шага для создания нового типа представления:

  1. создать класс держателя представления
  2. создать класс элемента адаптера (расширяющийся от AdapterItemBase)
  3. зарегистрируйте класс держателя представления в ViewHolderProvider

Вот пример этой концепции: android-drawer-template .

Он идет еще дальше - тип представления, который действует как компонент прядильщика с выбираемыми элементами адаптера.

person Michal Faber    schedule 17.04.2015

Это очень просто и понятно.

Просто переопределите getItemViewType () в вашем адаптере. На основе данных возвращаются разные значения itemViewType. Например, рассмотрим объект типа Person с членом isMale, если isMale имеет значение true, вернуть 1, а isMale - false, вернуть 2 в getItemViewType ().

Теперь перейдем к createViewHolder (ViewGroup parent, int viewType), на основе другого viewType вы можете раздувать другой файл макета. Как следующее:

 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 или AdapterMaleViewHolder на instanceof, и соответственно присвойте значения.

ViewHolder может быть таким

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

    class AdapterFemaleViewHolder extends RecyclerView.ViewHolder {
         ...
         public AdapterFemaleViewHolder(View itemView){
            ...
         }
    }
person Tulsi    schedule 02.01.2017

Хотя выбранный ответ верен, я просто хочу его более детально проработать. Я нашел полезный Пользовательский адаптер для множественного просмотра Типы в 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;
    }
}
person Asad Ali Choudhry    schedule 07.11.2019

Проще, чем когда-либо, забыть о ViewTypes. Не рекомендуется использовать несколько типов просмотра внутри одного адаптера. Это испортит код и нарушит принцип единой ответственности, так как теперь адаптеру необходимо обрабатывать логику, чтобы знать, какое представление нужно раздувать.

А теперь представьте, что вы работаете в больших группах, где каждая команда должна работать с одним из этих типов представления. Будет беспорядком касаться одного и того же адаптера всеми командами, работающими с разными типами просмотра. Это решается с помощью ConcatAdapter, где вы изолируете адаптеры. Кодируйте их один за другим, а затем просто объединяйте в одном представлении.

С recyclerview:1.2.0-alpha04 теперь вы можете использовать ConcatAdapter.

Если вам нужно представление с разными типами представления, вы можете просто написать адаптеры для каждого раздела и просто использовать ConcatAdapter, чтобы объединить их все внутри одного recyclerview.

ConcatAdapter

На этом изображении показаны три разных типа просмотра, которые есть в одном recyclerview: верхний колонтитул, содержимое и нижний колонтитул.

Введите здесь описание изображения

Вы создаете только один адаптер для каждого раздела, а затем просто используете ConcatAdapter, чтобы объединить их внутри одного recyclerview:

val firstAdapter: FirstAdapter = …
val secondAdapter: SecondAdapter = …
val thirdAdapter: ThirdAdapter = …
val concatAdapter = ConcatAdapter(firstAdapter, secondAdapter,
                                  thirdAdapter)
recyclerView.adapter = concatAdapter

Введите здесь описание изображения

Это все, что вам нужно знать. Если вы хотите обработать состояние загрузки, например удалить последний адаптер после некоторой загрузки, вы можете использовать LoadState.

person Gastón Saillén    schedule 25.06.2020
comment
Большое спасибо. Этот ответ очень полезен. Необходимо добавить implementation "androidx.recyclerview:recyclerview:1.2.0-alpha04" в градиент. - person jujuf1; 02.01.2021
comment
Доступна стабильная версия 1.2.1: implementation "androidx.recyclerview:recyclerview:1.2.1". У меня это сработало отлично. - person Atakan Yildirim; 18.06.2021

Вот полный пример, показывающий RecyclerView с двумя типами, тип представления определяется объектом.

Модель класса

open class RecyclerViewItem
class SectionItem(val title: String) : RecyclerViewItem()
class ContentItem(val name: String, val number: Int) : RecyclerViewItem()

Код адаптера

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()
        }
    }
}

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" />

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>

Пример использования

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()
person Linh    schedule 06.01.2021

Я рекомендую эту библиотеку от Ханнеса Дорфманна. Он инкапсулирует всю логику, относящуюся к определенному типу представления, в отдельном объекте с именем 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);
  }
}
person Dmitrii Bychkov    schedule 05.04.2019

Если кому-то интересно увидеть супер-простое решение, написанное на Kotlin, проверьте только что созданный мной пост в блоге. Пример в блоге основан на создании Sectioned RecyclerView:

https://brona.blog/2020/06/sectioned-recyclerview-in-three-steps/

person Mariusz Brona    schedule 24.06.2020
comment
Ссылка не работает - Хм. Нам не удается найти этот сайт. Мы не можем подключиться к серверу на brona.blog. - person Peter Mortensen; 07.07.2021

Прежде всего я рекомендую вам прочитать отличную статью Ханнеса Дорфманна по этой теме.

Когда приходит новый тип представления, вам нужно отредактировать свой адаптер, и вам придется справиться с множеством беспорядочных вещей. Ваш адаптер должен быть открытым для расширения, но закрытым для модификации.

Вы можете проверить эти два проекта, они могут дать представление о том, как обрабатывать разные типы представления в адаптере:

person ibrahimyilmaz    schedule 02.08.2020

На самом деле, я хотел бы улучшить ответ Антона.

Так как getItemViewType(int position) возвращает целочисленное значение, вы можете вернуть идентификатор ресурса макета, который нужно увеличить. Таким образом вы сэкономите логику в onCreateViewHolder(ViewGroup parent, int viewType) методе.

Кроме того, я бы не предлагал выполнять интенсивные вычисления в getItemCount(), поскольку эта конкретная функция вызывается не менее 5 раз при рендеринге списка, а также при рендеринге каждого элемента за пределами видимых элементов. К сожалению, поскольку метод notifyDatasetChanged() является окончательным, вы не можете его переопределить, но вы можете вызвать его из другой функции внутри адаптера.

person Dragas    schedule 05.08.2016
comment
Да, это может сработать, но запутает других разработчиков. Также из документации Note: Integers must be in the range 0 to getViewTypeCount() - 1. IGNORE_ITEM_VIEW_TYPE can also be returned. Так что лучше написать немного больше кода и не использовать хаки. - person Ioane Sharvadze; 09.10.2017
comment
Я согласен. Тогда я пропустил этот конкретный пункт. - person Dragas; 10.10.2017
comment
Забавно, потому что RecyclerView.Adapter: getItemViewType () здесь developer.android.com/reference/android/support/v7/widget/ предложите, что опубликовал Драгас, и вам следует рассмотреть возможность использования ресурсов идентификатора для однозначной идентификации типов представления элементов. очевидно, не обращая внимания на требование для getViewTypeCount () - person Deemoe; 11.01.2018

Вы можете использовать библиотеку: https://github.com/vivchar/RendererRecyclerViewAdapter

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 интерфейсом

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));
    }
}

Более подробно вы можете посмотреть в документации.

person Vitaly    schedule 17.01.2017

Реализация типов представлений становится проще с Kotlin. Вот пример этой легкой библиотеки https://github.com/Link184/KidAdapter

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
        }
    }
}
person Link182    schedule 22.12.2018

Вы можете иметь дело с множественными типами просмотра RecyclerAdapter, заставляя getItemViewType() возвращать ожидаемое viewType значение для этой позиции.

Я подготовил MultipleViewTypeAdapter для составления списка MCQ для экзаменов, которые могут вызывать вопрос, который может иметь два или более действительных ответа (варианты флажков) и вопросы с одним ответом (параметры радиообмена).

Для этого я получаю тип вопроса из ответа API, и я использовал его, чтобы решить, какое представление я должен показать для этого вопроса.

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();
    }
}

Вы можете избежать множественных заполнений данных viewHolder на основе условных выражений в onBindViewHolder(), назначив одинаковые идентификаторы для аналогичных представлений в viewHolder, которые различаются своим расположением.

person Avinash Ch    schedule 06.05.2019

Если вы хотите использовать его вместе с Android Data Binding, просмотрите https://github.com/evant/binding-collection-adapter - это, безусловно, лучшее решение для нескольких RecyclerView типов представлений, которые я когда-либо видел.

Вы можете использовать это как

var items: AsyncDiffPagedObservableList<BaseListItem> =
        AsyncDiffPagedObservableList(GenericDiff)

    val onItemBind: OnItemBind<BaseListItem> =
        OnItemBind { itemBinding, _, item -> itemBinding.set(BR.item, item.layoutRes) }

И затем в макете, где есть список:

 <androidx.recyclerview.widget.RecyclerView
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1"
                app:enableAnimations="@{false}"
                app:scrollToPosition="@{viewModel.scrollPosition}"

                app:itemBinding="@{viewModel.onItemBind}"
                app:items="@{viewModel.items}"

                app:reverseLayoutManager="@{true}"/>

Элементы вашего списка должны реализовывать BaseListItem интерфейс, который выглядит следующим образом:

interface BaseListItem {
    val layoutRes: Int
}

И представление элемента должно выглядеть примерно так:

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
                name="item"
                type="...presentation.somescreen.list.YourListItem"/>
    </data>

   ...

</layout>

Где YourListItem реализует BaseListItem.

person Pavlo Ostasha    schedule 04.10.2019

Сначала вы должны создать два XML-файла макета. После этого внутри адаптера recyclerview TYPE_CALL и TYPE_EMAIL представляют собой два статических значения с 1 и 2 соответственно в классе адаптера.

Теперь определите два статических значения на уровне класса адаптера представления Recycler, например: private static int TYPE_CALL = 1; частный статический int TYPE_EMAIL = 2;

Теперь создайте держатель представления с несколькими представлениями, подобными этому:

class CallViewHolder extends RecyclerView.ViewHolder {

    private TextView txtName;
    private TextView txtAddress;

    CallViewHolder(@NonNull View itemView) {
        super(itemView);
        txtName = itemView.findViewById(R.id.txtName);
        txtAddress = itemView.findViewById(R.id.txtAddress);
    }
}

class EmailViewHolder extends RecyclerView.ViewHolder {

    private TextView txtName;
    private TextView txtAddress;

    EmailViewHolder(@NonNull View itemView) {
        super(itemView);
        txtName = itemView.findViewById(R.id.txtName);
        txtAddress = itemView.findViewById(R.id.txtAddress);
    }
}

Теперь выполните код, как показано ниже, в методах onCreateViewHolder и onBindViewHolder в адаптере recyclerview:

@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
    View view;
    if (viewType == TYPE_CALL) { // for call layout
        view = LayoutInflater.from(context).inflate(R.layout.item_call, viewGroup, false);
        return new CallViewHolder(view);

    } else { // for email layout
        view = LayoutInflater.from(context).inflate(R.layout.item_email, viewGroup, false);
        return new EmailViewHolder(view);
    }
}

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
    if (getItemViewType(position) == TYPE_CALL) {
        ((CallViewHolder) viewHolder).setCallDetails(employees.get(position));
    } else {
        ((EmailViewHolder) viewHolder).setEmailDetails(employees.get(position));
    }
}
person Genius8020    schedule 05.10.2019

Я сделал что-то подобное. Я передал fragmentType и создал два ViewHolders и на основе этого классифицировал свои макеты соответственно в одном адаптере, который может иметь разные Layouts и LayoutManagers

private Context mContext;
protected IOnLoyaltyCardCategoriesItemClicked mListener;
private String fragmentType;
private View view;

public LoyaltyCardsCategoriesRecyclerViewAdapter(Context context, IOnLoyaltyCardCategoriesItemClicked itemListener, String fragmentType) {
    this.mContext = context;
    this.mListener = itemListener;
    this.fragmentType = fragmentType;
}

public class LoyaltyCardCategoriesFragmentViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

    private ImageView lc_categories_iv;
    private TextView lc_categories_name_tv;
    private int pos;

    public LoyaltyCardCategoriesFragmentViewHolder(View v) {
        super(v);
        view.setOnClickListener(this);
        lc_categories_iv = (ImageView) v.findViewById(R.id.lc_categories_iv);
        lc_categories_name_tv = (TextView) v.findViewById(R.id.lc_categories_name_tv);
    }

    public void setData(int pos) {
        this.pos = pos;
        lc_categories_iv.setImageResource(R.mipmap.ic_launcher);
        lc_categories_name_tv.setText("Loyalty Card Categories");
    }

    @Override
    public void onClick(View view) {
        if (mListener != null) {
            mListener.onLoyaltyCardCategoriesItemClicked(pos);
        }
    }
}

public class MyLoyaltyCardsFragmentTagViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

    public ImageButton lc_categories_btn;
    private int pos;

    public MyLoyaltyCardsFragmentTagViewHolder(View v) {
        super(v);
        lc_categories_btn = (ImageButton) v.findViewById(R.id.lc_categories_btn);
        lc_categories_btn.setOnClickListener(this);
    }

    public void setData(int pos) {
        this.pos = pos;
        lc_categories_btn.setImageResource(R.mipmap.ic_launcher);
    }

    @Override
    public void onClick(View view) {
        if (mListener != null) {
            mListener.onLoyaltyCardCategoriesItemClicked(pos);
        }
    }
}

@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (fragmentType.equalsIgnoreCase(Constants.LoyaltyCardCategoriesFragmentTag)) {
        view = LayoutInflater.from(mContext).inflate(R.layout.loyalty_cards_categories_frag_item, parent, false);
        return new LoyaltyCardCategoriesFragmentViewHolder(view);
    } else if (fragmentType.equalsIgnoreCase(Constants.MyLoyaltyCardsFragmentTag)) {
        view = LayoutInflater.from(mContext).inflate(R.layout.my_loyalty_cards_categories_frag_item, parent, false);
        return new MyLoyaltyCardsFragmentTagViewHolder(view);
    } else {
        return null;
    }
}

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
    if (fragmentType.equalsIgnoreCase(Constants.LoyaltyCardCategoriesFragmentTag)) {
        ((LoyaltyCardCategoriesFragmentViewHolder) holder).setData(position);
    } else if (fragmentType.equalsIgnoreCase(Constants.MyLoyaltyCardsFragmentTag)) {
        ((MyLoyaltyCardsFragmentTagViewHolder) holder).setData(position);
    }
}

@Override
public int getItemCount() {
    return 7;
}
person Syed Arsalan Kazmi    schedule 29.12.2020

Я вижу, что есть много отличных ответов, невероятно подробных и обширных. В моем случае я всегда лучше понимаю вещи, если буду следовать рассуждениям практически с нуля, шаг за шагом. Я бы порекомендовал вам проверить эту ссылку и, если у вас возникнут похожие вопросы, поищите любые кодовые таблицы, которые решают эту проблему.

Основы Android Kotlin: заголовки в RecyclerView

person Miguel Lasa    schedule 22.01.2021