studio recyclerview multiple lista ejemplo crear con cardview android user-interface android-recyclerview

android - multiple - ¿Cómo crear RecyclerView con múltiples tipos de vista?



recyclerview cardview (17)

Crear ViewHolder diferente para un diseño diferente


RecyclerView puede tener cualquier cantidad de visualizadores que desee, pero para una mejor legibilidad, veamos cómo crear uno con dos ViewHolders.

Se puede hacer en tres simples pasos.

  1. Anular public int getItemViewType(int position)
  2. Devuelve diferentes ViewHolders basados ​​en ViewType en el método onCreateViewHolder()
  3. Rellenar vista basada en el itemViewType en el método onBindViewHolder()

Aquí hay un pequeño fragmento de código

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) es la clave

En mi opinión, el punto de partida para crear este tipo de recyclerView es el conocimiento de este método. Dado que este método es opcional para anular, por lo tanto, no es visible en la clase RecylerView de forma predeterminada, lo que a su vez hace que muchos desarrolladores (incluido yo) se pregunten por dónde comenzar. Una vez que sepa que este método existe, crear tal RecyclerView sería un juego de niños.

Veamos un ejemplo para probar mi punto. Si desea mostrar dos diseños en posiciones alternativas, haga esto

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

Enlaces relevantes:

Echa un vistazo al project donde he implementado esto

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

Cuando creamos RecyclerView.Adapter tenemos que especificar ViewHolder que se unirá con el adaptador.

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

Entonces, ¿es posible crear RecyclerView con múltiples tipos de vista?


Aunque la respuesta seleccionada es correcta, solo quiero elaborarla más. Encontré aquí un adaptador personalizado útil para múltiples tipos de vista en RecyclerView . Su versión de Kotlin está aquí .

Adaptador personalizado está siguiendo

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


En realidad, me gustaría mejorar la respuesta de Anton .

Como getItemViewType(int position) devuelve un valor entero, puede devolver el ID de recurso de diseño que necesitaría para inflar. De esa manera guardarías algo de lógica en el onCreateViewHolder(ViewGroup parent, int viewType) método.

Además, no sugeriría hacer cálculos intensivos getItemCount() ya que esa función en particular se llama al menos 5 veces al representar la lista, así como al representar cada elemento más allá de los elementos visibles. Lamentablemente, dado que el notifyDatasetChanged() método es final, realmente no puede anularlo, pero puede llamarlo desde otra función dentro del adaptador.


La implementación de tipos de vista se vuelve más fácil con kotlin, aquí hay una muestra con esta biblioteca ligera 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 } } }


Puede manejar múltiples tipos RecyclerAdapter de vista haciendo que getItemViewType() devuelva el viewType valor esperado para esa posición

Preparé una MultipleViewTypeAdapter lista de MCQ para la construcción de exámenes que pueden arrojar una pregunta que puede tener 2 o más respuestas válidas (opciones de casilla de verificación) y una sola pregunta de respuesta (opciones de botón de radio).

Para esto obtengo el tipo de Pregunta de la respuesta API y lo usé para decidir qué vista tengo que mostrar para esa pregunta.

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

Puede evitar múltiples rellenos de datos basados ​​en viewHolder condicionalmente onBindViewHolder() asignando los mismos identificadores para las vistas similares en viewHolders que difieren en su posición.


Puede usar la biblioteca: 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 */

``

Para cada elemento, debe implementar un ViewRenderer, ViewHolder, SomeModel:

ViewHolder: es un titular de vista simple de vista de reciclador.

SomeModel: es su modelo con ItemModel interfaz

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

Para más detalles puedes mirar documentaciones.


Recomiendo esta biblioteca de Hannes Dorfmann. Encapsula toda la lógica relacionada con un tipo de vista particular en un objeto separado llamado "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); } }


Si desea usarlo junto con Android Data Binding, consulte https://github.com/evant/binding-collection-adapter : es, con mucho, la mejor solución para los tipos de vistas múltiples RecyclerView que he visto.

puedes usarlo como

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

y luego en el diseño donde está la lista

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

los elementos de su lista deben implementar una BaseListItem interfaz similar a esta

interface BaseListItem { val layoutRes: Int }

y la vista del elemento debería verse así

<layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="item" type="...presentation.somescreen.list.YourListItem"/> </data> ... </layout>

Donde YourListItem implementa BaseListItem

Espero que ayude a alguien.


primero debes crear 2 xml de diseño. después de eso, dentro del adaptador de vista de reciclaje TYPE_CALL y TYPE_EMAIL hay dos valores estáticos con 1 y 2 respectivamente en la clase de adaptador.

ahora Defina dos valores estáticos en el nivel de clase Adaptador de vista de Reciclador, por ejemplo: private static int TYPE_CALL = 1; private static int TYPE_EMAIL = 2;

Ahora cree un titular de vista con múltiples vistas como esta:

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

Ahora codifique como se muestra a continuación en el método onCreateViewHolder y onBindViewHolder en el adaptador de la vista de reciclaje:

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


El siguiente no es un pseudocódigo y lo he probado y me ha funcionado.

Quería crear una vista de encabezado en mi vista de reciclador y luego mostrar una lista de imágenes debajo del encabezado en el que el usuario puede hacer clic.

Utilicé algunos modificadores en mi código, no sé si esa es la forma más eficiente de hacerlo, así que no dude en dar sus comentarios:

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


Es muy simple y directo.

Simplemente anule el método getItemViewType() en su adaptador. Sobre la base de los datos, devuelva diferentes valores de itemViewType. Por ejemplo, considere un objeto de tipo Persona con un miembro isMale, si isMale es verdadero, devuelva 1 e isMale es falso, devuelva 2 en el método getItemViewType() .

Ahora llega a createViewHolder (ViewGroup parent, int viewType) , sobre la base de diferentes viewType, puede inflar los diferentes archivos de diseño. como el siguiente

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

en onBindViewHolder (titular de VH, posición int) verifique dónde está la instancia de AdapterFemaleViewHolder o AdapterMaleViewHolder por instanceof y asigne los valores correspondientes.

ViewHolder puede ser así

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


Sí, es posible. En su adaptador getItemViewType Layout así ...

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

para enlace de referencia: https://www.journaldev.com/12372/android-recyclerview-example


Sí, es posible.

Escriba un titular de vista genérico:

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

luego cree sus titulares de vista y haga que extiendan el GenericViewHolder. Por ejemplo, este:

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

entonces la clase RecyclerView.Adapter se verá así:

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


Si es posible. Simplemente implemente getItemViewType() y cuide el parámetro onCreateViewHolder() en onCreateViewHolder() .

Entonces haces algo como:

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


Si los diseños para los tipos de vista son pocos y las lógicas de enlace son simples, siga la solución de Anton.
Pero el código será complicado si necesita administrar los diseños complejos y las lógicas de enlace.

Creo que la siguiente solución será útil para alguien que necesite manejar tipos de vista complejos.

Clase base de 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(); ...... }

Las funciones necesarias para definir en esta clase son más o menos las mismas que la clase de adaptador al crear el tipo de vista única.
Para cada tipo de vista, cree la clase extendiendo este DataBinder.

Clase DataBinder de muestra

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

Para administrar las clases de DataBinder, cree una clase de adaptador.

Clase Base DataBindAdapter

abstract public class DataBindAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return getDataBinder(viewType).newViewHolder(parent); } @Override public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { int binderPosition = getBinderPosition(position); getDataBinder(viewHolder.getItemViewType()).bindViewHolder(viewHolder, binderPosition); } @Override public abstract int getItemCount(); @Override public abstract int getItemViewType(int position); public abstract <T extends DataBinder> T getDataBinder(int viewType); public abstract int getPosition(DataBinder binder, int binderPosition); public abstract int getBinderPosition(int position); ...... }

Cree la clase extendiendo esta clase base, y luego instancia las clases de DataBinder y anule los métodos abstractos

  1. getItemCount
    Devuelve el recuento total de elementos de DataBinders

  2. getItemViewType
    Defina la lógica de mapeo entre la posición del adaptador y el tipo de vista.

  3. getDataBinder
    Devuelve la instancia de DataBinder en función del tipo de vista

  4. getPosition
    Defina la lógica de conversión a la posición del adaptador desde la posición en el DataBinder especificado

  5. getBinderPosition
    Defina la lógica de conversión a la posición en el DataBinder desde la posición del adaptador

Espero que esta solución sea útil.
Dejé una solución más detallada y muestras en GitHub, así que consulte el siguiente enlace si lo necesita.
https://github.com/yqritc/RecyclerView-MultipleViewTypesAdapter


Tengo una mejor solución que permite crear múltiples tipos de vistas de forma declarativa y segura. Está escrito en Kotlin que por cierto es realmente agradable.

Soportes de vista simple para todos los tipos de vista requeridos

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 }

Hay una abstracción del elemento de datos del adaptador. Tenga en cuenta que un tipo de vista está representado por un hashCode de clase de titular de vista particular (KClass en 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) } }

Solo se debe anular bindViewHolder en las clases de elementos de adaptador de hormigón (tipo seguro)

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

La lista de dichos objetos AdapterItemMedium es una fuente de datos para el adaptador que realmente acepta List<AdapterItem> ver más abajo.

Parte importante de esta solución es una fábrica de soporte de vistas que proporcionará nuevas instancias de un ViewHolder específico

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

Y la clase de adaptador simple se ve así

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

Solo 3 pasos para crear un nuevo tipo de vista:

  1. crear una clase de titular de vista
  2. crear una clase de elemento de adaptador (que se extiende desde AdapterItemBase)
  3. registrar clase de titular de vista en ViewHolderProvider

Aquí hay un ejemplo de este concepto: android-drawer-template Va aún más lejos: tipo de vista que actúa como componente giratorio, elementos de adaptador seleccionables.


siguiendo la solución de Anton, ViewHolder este ViewHolder que contiene / maneja / delega diferentes tipos de diseños. Pero no estoy seguro de si el nuevo diseño de reemplazo funcionaría cuando el ViewHolder la vista de ViewHolder no sea el tipo de rollo de datos.

Entonces, básicamente, onCreateViewHolder(ViewGroup parent, int viewType) solo se llama cuando se necesita un nuevo diseño de vista;

getItemViewType(int position) para viewType ;

onBindViewHolder(ViewHolder holder, int position) siempre se llama al reciclar la vista (se introducen nuevos datos y se intenta mostrar con ese ViewHolder ).

Entonces, cuando se llama a onBindViewHolder , debe colocar el diseño de vista correcto y actualizar ViewHolder .

¿Es la forma correcta de reemplazar el diseño de vista para que se ViewHolder ese ViewHolder , o algún problema? Agradezco cualquier comentario!

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

Editar: ViewHolder tiene miembro mItemViewType para mantener la vista

Editar: se ve como en onBindViewHolder (titular de ViewHolder, posición int), el ViewHolder pasado ha sido recogido (o creado) por getItemViewType (posición int) para asegurarse de que coincida, por lo que es posible que no tenga que preocuparse de que ViewHolder''s tipo no coincide con el tipo de datos [posición]. ¿Alguien sabe más cómo se recoge el ViewHolder en onBindViewHolder ()?

Editar: Parece que el ViewHolder reciclaje se selecciona por tipo, por lo que no hay guerrero allí.

Editar: http://wiresareobsolete.com/2014/09/building-a-recyclerview-layoutmanager-part-1/ responde a esta pregunta.

Obtiene el ViewHolder reciclaje como:

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

o cree uno nuevo si no encuentra el ViewHolder de reciclaje del tipo correcto.

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