Obsługa logiki wierszy / sekcji podobna do UITableView systemu iOS nie jest tak prosta w systemie Android, jak w systemie iOS, jednak w przypadku korzystania z RecyclerView - elastyczność tego, co możesz zrobić, jest znacznie większa.
Ostatecznie chodzi o to, jak dowiedzieć się, jaki typ widoku wyświetlasz w adapterze. Kiedy już to zrozumiesz, żeglowanie powinno być łatwe (nie do końca, ale przynajmniej to załatwisz).
Adapter udostępnia dwie metody, które należy ominąć:
getItemViewType(int position)
Domyślna implementacja tej metody zawsze zwróci 0, co oznacza, że istnieje tylko 1 typ widoku. W twoim przypadku tak nie jest, więc będziesz musiał znaleźć sposób, aby stwierdzić, który wiersz odpowiada danemu typowi widoku. W przeciwieństwie do iOS, który zarządza tym za Ciebie za pomocą wierszy i sekcji, tutaj będziesz mieć tylko jeden indeks, na którym możesz polegać, i będziesz musiał wykorzystać swoje umiejętności programisty, aby wiedzieć, kiedy pozycja jest skorelowana z nagłówkiem sekcji, a kiedy jest skorelowana z normalny rząd.
createViewHolder(ViewGroup parent, int viewType)
I tak musisz zastąpić tę metodę, ale zwykle ludzie po prostu ignorują parametr viewType. Zgodnie z typem widoku musisz nadmuchać odpowiedni zasób układu i odpowiednio utworzyć uchwyt widoku. RecyclerView będzie obsługiwać recykling różnych typów widoków w sposób, który pozwala uniknąć kolizji różnych typów widoków.
Jeśli planujesz używać domyślnego LayoutManagera, takiego jak LinearLayoutManager
, powinieneś być gotowy . Jeśli planujesz stworzyć własną implementację LayoutManager, będziesz musiał popracować trochę ciężej. Jedynym API, z którym naprawdę musisz pracować, jest to, findViewByPosition(int position)
które daje dany widok w określonej pozycji. Ponieważ prawdopodobnie będziesz chciał to ułożyć inaczej w zależności od typu tego widoku, masz kilka opcji:
Zwykle, gdy używasz wzorca ViewHolder, ustawiasz etykietę widoku za pomocą uchwytu widoku. Możesz użyć tego w czasie wykonywania w menedżerze układu, aby dowiedzieć się, jakiego typu jest widok, dodając pole w uchwycie widoku, które to wyraża.
Ponieważ będziesz potrzebować funkcji, która określa, która pozycja odpowiada typowi widoku, równie dobrze możesz w jakiś sposób uczynić tę metodę globalnie dostępną (może pojedynczą klasą zarządzającą danymi?), A następnie możesz po prostu zapytać o tę samą metodę zgodnie z pozycja.
Oto przykładowy kod:
// 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;
}
}
Oto próbka tego, jak powinny wyglądać te osoby posiadające widoki:
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());
}
}
Oczywiście w tym przykładzie założono, że źródło danych (myData) zostało utworzone w sposób ułatwiający implementację adaptera w ten sposób. Jako przykład pokażę, jak skonstruowałbym źródło danych, które pokazuje listę nazwisk i nagłówek za każdym razem, gdy zmienia się pierwsza litera nazwiska (zakładając, że lista jest alfabetyczna) - podobnie jak w przypadku kontaktów lista wyglądałaby następująco:
// 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();
}
Ten przykład ma na celu rozwiązanie dość konkretnego problemu, ale mam nadzieję, że daje to dobry przegląd sposobów obsługi różnych typów wierszy w recyklerze i pozwala na dokonanie niezbędnych dostosowań we własnym kodzie do własnych potrzeb.