Sin embargo, manejar la lógica de filas / secciones similar a UITableView de iOS no es tan simple en Android como en iOS, cuando usa RecyclerView: la flexibilidad de lo que puede 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 resuelto, debería ser fácil navegar (en realidad no, pero al menos lo tendrás ordenado).
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 necesitará encontrar una manera de afirmar qué fila corresponde a qué tipo de vista. A diferencia de iOS, que maneja esto para usted con filas y secciones, aquí solo tendrá un índice en el que confiar, y deberá usar sus 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)
Necesitas anular este método de todos modos, pero generalmente las personas simplemente ignoran el parámetro viewType. Según el tipo de vista, deberá inflar el recurso de diseño correcto y crear su titular de vista en consecuencia. RecyclerView manejará el reciclaje de diferentes tipos de vista de una manera que evite el choque de diferentes tipos de vista.
Si planea usar un LayoutManager predeterminado, como LinearLayoutManager
, debería estar listo. Si planea hacer su propia implementación de LayoutManager, deberá trabajar un poco más duro. La única API con la que realmente tiene que trabajar es la findViewByPosition(int position)
que proporciona una vista determinada en una posición determinada. Dado que probablemente querrá diseñarlo de manera diferente según el tipo de vista que tenga, tiene algunas opciones:
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 de vista es agregando un campo en el titular de la vista que expresa esto.
Dado que 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 gestione los datos?), Y luego simplemente puede consultar el mismo método de acuerdo con 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<ViewHolder> {
@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 ser 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, este ejemplo supone que ha construido su fuente de datos (myData) de una manera que facilita la implementación de 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 (suponga que la lista está alfabetizada), de forma similar a cómo un contacto 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<Object> data = new ArrayList<Object>();
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 viene a resolver un problema bastante específico, pero espero que le brinde una buena visión general sobre cómo manejar diferentes tipos de filas en un reciclador y le permita realizar las adaptaciones necesarias en su propio código para satisfacer sus necesidades.