Gestire la logica di righe / sezioni simile a UITableView di iOS non è così semplice in Android come in iOS, tuttavia, quando si utilizza RecyclerView, la flessibilità di ciò che è possibile fare è molto maggiore.
Alla fine, è tutto su come capire quale tipo di vista stai visualizzando nell'adattatore. Una volta capito questo, dovrebbe essere facile navigare (non proprio, ma almeno lo avrai risolto).
L'adapter espone due metodi che è necessario ignorare:
getItemViewType(int position)
L'implementazione predefinita di questo metodo restituirà sempre 0, indicando che esiste solo 1 tipo di vista. Nel tuo caso, non è così, quindi dovrai trovare un modo per affermare quale riga corrisponde a quale tipo di vista. A differenza di iOS, che lo gestisce per te con righe e sezioni, qui avrai un solo indice su cui contare e dovrai usare le tue capacità di sviluppatore per sapere quando una posizione è correlata a un'intestazione di sezione e quando è correlata a una fila normale.
createViewHolder(ViewGroup parent, int viewType)
È necessario ignorare questo metodo comunque, ma di solito le persone ignorano semplicemente il parametro viewType. A seconda del tipo di vista, è necessario gonfiare la risorsa di layout corretta e creare il proprio supporto di visualizzazione di conseguenza. RecyclerView gestirà il riciclaggio di diversi tipi di vista in modo da evitare lo scontro di diversi tipi di vista.
Se hai intenzione di utilizzare un LayoutManager predefinito, ad esempio LinearLayoutManager, dovresti essere pronto per partire. Se stai pensando di realizzare la tua implementazione LayoutManager, dovrai lavorare un po 'di più. L'unica API con cui devi davvero lavorare è quella findViewByPosition(int position)che fornisce una determinata vista in una determinata posizione. Poiché probabilmente vorrai disporlo in modo diverso a seconda del tipo di questa vista, hai alcune opzioni:
Di solito quando si utilizza il modello ViewHolder, si imposta il tag della vista con il supporto della vista. È possibile utilizzarlo durante il runtime nel gestore del layout per scoprire di che tipo è la vista aggiungendo un campo nel supporto della vista che lo esprime.
Dato che avrai bisogno di una funzione che determina quale posizione è correlata a quale tipo di vista, potresti anche rendere questo metodo accessibile in tutto il mondo in qualche modo (forse una classe singleton che gestisce i dati?), E quindi puoi semplicemente interrogare lo stesso metodo in base a la posizione.
Ecco un esempio di codice:
// 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;
}
}
Ecco un esempio di come dovrebbero apparire questi possessori di vista:
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());
}
}
Naturalmente, questo esempio presuppone che tu abbia costruito la tua fonte di dati (myData) in un modo che semplifichi l'implementazione di un adattatore in questo modo. Ad esempio, ti mostrerò come costruirò un'origine dati che mostra un elenco di nomi e un'intestazione per ogni volta che cambia la 1a lettera del nome (supponiamo che l'elenco sia alfabetico) - simile a come un contatto la lista sarebbe simile a:
// 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();
}
Questo esempio viene a risolvere un problema abbastanza specifico, ma spero che questo ti dia una buona panoramica su come gestire diversi tipi di riga in un riciclatore e ti permetta di fare gli adattamenti necessari nel tuo codice per soddisfare le tue esigenze.