Reciclador de vista y manejo de diferentes tipos de inflación de fila

Estoy tratando de trabajar con el nuevo RecyclerView , pero no pude encontrar un ejemplo de RecyclerView con diferentes tipos de filas / cardviews inflados.

Con ListView , getViewTypeCount y getItemViewType , para manejar diferentes tipos de filas.

¿Se supone que debo hacerlo como “viejo” o debería hacer algo con LayoutManager ? Me preguntaba si alguien podría indicarme la dirección correcta. Porque solo puedo encontrar ejemplos con un tipo.

Quiero tener una lista de tarjetas ligeramente diferentes. ¿O debería simplemente usar un scrollView con cardViews dentro de él … hacerlo sin el adaptador y recyclerView ?

Manejar la lógica de filas / secciones similar a la de UITableView de iOS no es tan simple en Android como en iOS, sin embargo, cuando utilizas RecyclerView, la flexibilidad de lo que puedes hacer es mucho mayor.

Al final, se trata de cómo averiguar qué tipo de vista está mostrando en el Adaptador. Una vez que lo hayas descifrado, será fácil navegar (no realmente, pero al menos tendrás eso resuelto).

El adaptador expone dos métodos que debe anular:

 getItemViewType(int position) 

La implementación predeterminada de este método siempre devolverá 0, lo que indica que solo hay 1 tipo de vista. En su caso, no es así, por lo que deberá encontrar una forma de afirmar qué fila corresponde a qué tipo de vista. A diferencia de iOS, que gestiona esto para ti con filas y secciones, aquí solo tendrás un índice en el que confiar, y necesitarás usar tus habilidades de desarrollador para saber cuándo una posición se correlaciona con un encabezado de sección, y cuándo se correlaciona con una fila normal.

 createViewHolder(ViewGroup parent, int viewType) 

Debe sobrescribir este método de todos modos, pero generalmente la gente simplemente ignora el parámetro viewType. De acuerdo con el tipo de vista, tendrá que inflar el recurso de diseño correcto y crear su titular de vista en consecuencia. RecyclerView se encargará de reciclar diferentes tipos de vistas de una manera que evite el choque de diferentes tipos de vistas.

Si planea usar un LayoutManager predeterminado, como LinearLayoutManager , debería estar listo para usar. Si planea hacer su propia implementación de LayoutManager, necesitará trabajar un poco más. La única API con la que realmente tienes que trabajar es findViewByPosition(int position) que da una vista determinada en una posición determinada. Dado que probablemente quiera diseñarlo de forma diferente según el tipo de vista, tiene algunas opciones:

  1. Por lo general, cuando usa el patrón ViewHolder, establece la etiqueta de la vista con el titular de la vista. Puede usar esto durante el tiempo de ejecución en el administrador de diseño para averiguar qué tipo es la vista al agregar un campo en el titular de la vista que expresa esto.

  2. Como necesitará una función que determine qué posición se correlaciona con qué tipo de vista, también podría hacer que este método sea accesible globalmente de alguna manera (tal vez una clase singleton que administre los datos?), Y luego simplemente puede consultar el mismo método según la posición.

Aquí hay una muestra de código:

 // in this sample, I use an object array to simulate the data of the list. // I assume that if the object is a String, it means I should display a header with a basic title. // If not, I assume it's a custom model object I created which I will use to bind my normal rows. private Object[] myData; public static final int ITEM_TYPE_NORMAL = 0; public static final int ITEM_TYPE_HEADER = 1; public class MyAdapter extends Adapter { @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == ITEM_TYPE_NORMAL) { View normalView = LayoutInflater.from(getContext()).inflate(R.layout.my_normal_row, null); return new MyNormalViewHolder(normalView); // view holder for normal items } else if (viewType == ITEM_TYPE_HEADER) { View headerRow = LayoutInflater.from(getContext()).inflate(R.layout.my_header_row, null); return new MyHeaderViewHolder(headerRow); // view holder for header items } } @Override public void onBindViewHolder(ViewHolder holder, int position) { final int itemType = getItemViewType(position); if (itemType == ITEM_TYPE_NORMAL) { ((MyNormalViewHolder)holder).bindData((MyModel)myData[position]); } else if (itemType == ITEM_TYPE_HEADER) { ((MyHeaderViewHolder)holder).setHeaderText((String)myData[position]); } } @Override public int getItemViewType(int position) { if (myData[position] instanceof String) { return ITEM_TYPE_HEADER; } else { return ITEM_TYPE_NORMAL; } } @Override public int getItemCount() { return myData.length; } } 

Aquí hay una muestra de cómo deberían verse estos titulares de vistas:

 public MyHeaderViewHolder extends ViewHolder { private TextView headerLabel; public MyHeaderViewHolder(View view) { super(view); headerLabel = (TextView)view.findViewById(R.id.headerLabel); } public void setHeaderText(String text) { headerLabel.setText(text); } } public MyNormalViewHolder extends ViewHolder { private TextView titleLabel; private TextView descriptionLabel; public MyNormalViewHolder(View view) { super(view); titleLabel = (TextView)view.findViewById(R.id.titleLabel); descriptionLabel = (TextView)view.findViewById(R.id.descriptionLabel); } public void bindData(MyModel model) { titleLabel.setText(model.getTitle()); descriptionLabel.setText(model.getDescription()); } } 

Por supuesto, esta muestra supone que ha construido su fuente de datos (myData) de una manera que hace que sea fácil implementar un adaptador de esta manera. Como ejemplo, le mostraré cómo construiría una fuente de datos que muestre una lista de nombres y un encabezado cada vez que cambie la primera letra del nombre (suponiendo que la lista está ordenada alfabéticamente), de forma similar a como lo hacen los contactos. la lista se vería así:

 // Assume names & descriptions are non-null and have the same length. // Assume names are alphabetized private void processDataSource(String[] names, String[] descriptions) { String nextFirstLetter = ""; String currentFirstLetter; List data = new ArrayList(); for (int i = 0; i < names.length; i++) { currentFirstLetter = names[i].substring(0, 1); // get the 1st letter of the name // if the first letter of this name is different from the last one, add a header row if (!currentFirstLetter.equals(nextFirstLetter)) { nextFirstLetter = currentFirstLetter; data.add(nextFirstLetter); } data.add(new MyModel(names[i], descriptions[i])); } myData = data.toArray(); } 

Este ejemplo sirve para resolver un problema bastante específico, pero espero que esto le brinde una buena visión general de cómo manejar diferentes tipos de fila en una recicladora y le permita realizar las adaptaciones necesarias en su propio código para satisfacer sus necesidades.

El truco es crear subclases de ViewHolder y luego lanzarlas.

 public class GroupViewHolder extends RecyclerView.ViewHolder { TextView mTitle; TextView mContent; public GroupViewHolder(View itemView) { super (itemView); // init views... } } public class ImageViewHolder extends RecyclerView.ViewHolder { ImageView mImage; public ImageViewHolder(View itemView) { super (itemView); // init views... } } private static final int TYPE_IMAGE = 1; private static final int TYPE_GROUP = 2; 

Y luego, en tiempo de ejecución, haga algo como esto:

 @Override public int getItemViewType(int position) { // here your custom logic to choose the view type return position == 0 ? TYPE_IMAGE : TYPE_GROUP; } @Override public void onBindViewHolder (ViewHolder viewHolder, int i) { switch (viewHolder.getItemViewType()) { case TYPE_IMAGE: ImageViewHolder imageViewHolder = (ImageViewHolder) viewHolder; imageViewHolder.mImage.setImageResource(...); break; case TYPE_GROUP: GroupViewHolder groupViewHolder = (GroupViewHolder) viewHolder; groupViewHolder.mContent.setText(...) groupViewHolder.mTitle.setText(...); break; } } 

Espero eso ayude.

De acuerdo con la gran respuesta de Gil, la resolvé Anulando el getItemViewType explicado por Gil. Su respuesta es excelente y debe marcarse como correcta. En cualquier caso, agrego el código para alcanzar el puntaje:

En su adaptador de reciclador:

 @Override public int getItemViewType(int position) { int viewType = 0; // add here your booleans or switch() to set viewType at your needed // IE if (position == 0) viewType = 1; etc. etc. return viewType; } @Override public FileViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == 0) { return new MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.my_layout_for_first_row, parent, false)); } return new MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.my_other_rows, parent, false)); } 

¡Al hacer esto, puede establecer el diseño personalizado para cualquier fila!

Es bastante complicado, pero es muy difícil, simplemente copie el código a continuación y listo

 package com.yuvi.sample.main; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import com.yuvi.sample.R; import java.util.List; /** * Created by yubraj on 6/17/15. */ public class NavDrawerAdapter extends RecyclerView.Adapter { List mainOptionlist; Context context; private static final int TYPE_PROFILE = 1; private static final int TYPE_OPTION_MENU = 2; private int selectedPos = 0; public NavDrawerAdapter(Context context){ this.mainOptionlist = MainOption.getDrawableDataList(); this.context = context; } @Override public int getItemViewType(int position) { return (position == 0? TYPE_PROFILE : TYPE_OPTION_MENU); } @Override public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { switch (viewType){ case TYPE_PROFILE: return new ProfileViewHolder(LayoutInflater.from(context).inflate(R.layout.row_profile, parent, false)); case TYPE_OPTION_MENU: return new MyViewHolder(LayoutInflater.from(context).inflate(R.layout.row_nav_drawer, parent, false)); } return null; } @Override public void onBindViewHolder(MainViewHolder holder, int position) { if(holder.getItemViewType() == TYPE_PROFILE){ ProfileViewHolder mholder = (ProfileViewHolder) holder; setUpProfileView(mholder); } else { MyViewHolder mHolder = (MyViewHolder) holder; MainOption mo = mainOptionlist.get(position); mHolder.tv_title.setText(mo.title); mHolder.iv_icon.setImageResource(mo.icon); mHolder.itemView.setSelected(selectedPos == position); } } private void setUpProfileView(ProfileViewHolder mholder) { } @Override public int getItemCount() { return mainOptionlist.size(); } public class MyViewHolder extends MainViewHolder{ TextView tv_title; ImageView iv_icon; public MyViewHolder(View v){ super(v); this.tv_title = (TextView) v.findViewById(R.id.tv_title); this.iv_icon = (ImageView) v.findViewById(R.id.iv_icon); v.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // Redraw the old selection and the new notifyItemChanged(selectedPos); selectedPos = getLayoutPosition(); notifyItemChanged(selectedPos); } }); } } public class ProfileViewHolder extends MainViewHolder{ TextView tv_name, login; ImageView iv_profile; public ProfileViewHolder(View v){ super(v); this.tv_name = (TextView) v.findViewById(R.id.tv_profile); this.iv_profile = (ImageView) v.findViewById(R.id.iv_profile); this.login = (TextView) v.findViewById(R.id.tv_login); } } public void trace(String tag, String message){ Log.d(tag , message); } public class MainViewHolder extends RecyclerView.ViewHolder { public MainViewHolder(View v) { super(v); } } } 

disfruta !!!!

Podemos lograr vistas múltiples en RecyclerView individual de la siguiente manera:

Dependencias en Gradle, así que agregue el siguiente código: –

 compile 'com.android.support:cardview-v7:23.0.1' compile 'com.android.support:recyclerview-v7:23.0.1' 

RecyclerView en XML

  

Código de actividad

 private RecyclerView mRecyclerView; private CustomAdapter mAdapter; private RecyclerView.LayoutManager mLayoutManager; private String[] mDataset = {“Data - one ”, “Data - two”,    “Showing data three”, “Showing data four”}; private int mDatasetTypes[] = {DataOne, DataTwo, DataThree}; //view types ... mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView); mLayoutManager = new LinearLayoutManager(MainActivity.this); mRecyclerView.setLayoutManager(mLayoutManager); //Adapter is created in the last step mAdapter = new CustomAdapter(mDataset, mDataSetTypes); mRecyclerView.setAdapter(mAdapter); 

Primer XML

                        

Segundo XML

                            

Tercer XML

                                    

Ahora es el momento de hacer el adaptador y esto es lo principal para mostrar una vista diferente de -2 en la misma vista del reciclador, así que compruebe este código completamente: –

 public class CustomAdapter extends RecyclerView.Adapter {    private static final String TAG = "CustomAdapter";    private String[] mDataSet;    private int[] mDataSetTypes;    public static final int dataOne = 0;    public static final int dataTwo = 1;    public static final int dataThree = 2;    public static class ViewHolder extends RecyclerView.ViewHolder {        public ViewHolder(View v) {            super(v);        }    }    public class DataOne extends ViewHolder {        TextView temp;        public DataOne(View v) {            super(v);            this.temp = (TextView) v.findViewById(R.id.temp);        }    }    public class DataTwo extends ViewHolder {        TextView score;        public DataTwo(View v) {            super(v);            this.score = (TextView) v.findViewById(R.id.score);        }    }    public class DataThree extends ViewHolder {        TextView headline;        Button read_more;        public DataThree(View v) {            super(v);            this.headline = (TextView) v.findViewById(R.id.headline);            this.read_more = (Button) v.findViewById(R.id.read_more);        }    }    public CustomAdapter(String[] dataSet, int[] dataSetTypes) {        mDataSet = dataSet;        mDataSetTypes = dataSetTypes;    }    @Override    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {        View v;        if (viewType == dataOne) {            v = LayoutInflater.from(viewGroup.getContext())                    .inflate(R.layout.weather_card, viewGroup, false);            return new DataOne(v);        } else if (viewType == dataTwo) {            v = LayoutInflater.from(viewGroup.getContext())                    .inflate(R.layout.news_card, viewGroup, false);            return new DataThree(v);        } else {            v = LayoutInflater.from(viewGroup.getContext())                    .inflate(R.layout.score_card, viewGroup, false);            return new DataTwo(v);        }    }    @Override    public void onBindViewHolder(ViewHolder viewHolder, final int position) {        if (viewHolder.getItemViewType() == dataOne) {            DataOne holder = (DataOne) viewHolder;            holder.temp.setText(mDataSet[position]);        }        else if (viewHolder.getItemViewType() == dataTwo) {            DataThree holder = (DataTwo) viewHolder;            holder.headline.setText(mDataSet[position]);        }        else {            DataTwo holder = (DataTwo) viewHolder;            holder.score.setText(mDataSet[position]);        }    }    @Override    public int getItemCount() {        return mDataSet.length;    }  @Override    public int getItemViewType(int position) {        return mDataSetTypes[position];    } } 

También puede consultar este enlace para más información.

getItemViewType() implementar el método getItemViewType() en RecyclerView.Adapter . Por defecto onCreateViewHolder(ViewGroup parent, int viewType) implementación viewType de este método devuelve 0 . En primer lugar, necesita ver el tipo de elemento en posición para reciclar vistas y para eso debe anular el método getItemViewType() en el que puede pasar viewType que devolverá su posición de artículo. La muestra del código se da a continuación

 @Override public MyViewholder onCreateViewHolder(ViewGroup parent, int viewType) { int listViewItemType = getItemViewType(viewType); switch (listViewItemType) { case 0: return new ViewHolder0(...); case 2: return new ViewHolder2(...); } } @Override public int getItemViewType(int position) { return position; } // and in the similar way you can set data according // to view holder position by passing position in getItemViewType @Override public void onBindViewHolder(MyViewholder viewholder, int position) { int listViewItemType = getItemViewType(position); // ... } 

Solo puede devolver ItemViewType y usarlo. Vea el siguiente código:

 @Override public int getItemViewType(int position) { Message item = messageList.get(position); // return my message layout if(item.getUsername() == Message.userEnum.I) return R.layout.item_message_me; else return R.layout.item_message; // return other message layout } @Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { View view = LayoutInflater.from(viewGroup.getContext()).inflate(viewType, viewGroup, false); return new ViewHolder(view); } 

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

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

ViewHolder: es un simple visor de la vista de reciclador.

SomeModel: es su modelo con la interfaz ItemModel

 public class SomeViewRenderer extends ViewRenderer { 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, puede buscar documentación.

Puedes usar esta biblioteca:
https://github.com/kmfish/MultiTypeListViewAdapter (escrito por mí)

  • Mejor reutilizar el código de una celda
  • Mejor expansión
  • Mejor desacoplamiento

Adaptador de instalación:

 adapter = new BaseRecyclerAdapter(); adapter.registerDataAndItem(TextModel.class, LineListItem1.class); adapter.registerDataAndItem(ImageModel.class, LineListItem2.class); adapter.registerDataAndItem(AbsModel.class, AbsLineItem.class); 

Para cada artículo de línea:

 public class LineListItem1 extends BaseListItem { TextView tvName; TextView tvDesc; @Override public int onGetLayoutRes() { return R.layout.list_item1; } @Override public void bindViews(View convertView) { Log.d("item1", "bindViews:" + convertView); tvName = (TextView) convertView.findViewById(R.id.text_name); tvDesc = (TextView) convertView.findViewById(R.id.text_desc); tvName.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (null != attachInfo) { attachInfo.onNameClick(getData()); } } }); tvDesc.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (null != attachInfo) { attachInfo.onDescClick(getData()); } } }); } @Override public void updateView(TextModel model, int pos) { if (null != model) { Log.d("item1", "updateView model:" + model + "pos:" + pos); tvName.setText(model.getName()); tvDesc.setText(model.getDesc()); } } public interface OnItem1ClickListener { void onNameClick(TextModel model); void onDescClick(TextModel model); } }