Обработка логики строк / разделов, аналогичная UITableView в iOS, не так проста в Android, как в iOS, однако при использовании RecyclerView гибкость того, что вы можете делать, намного больше.
В конце концов, все дело в том, как вы определяете, какой тип представления вы показываете в адаптере. Как только вы это поймете, плавание должно быть легким (не совсем, но, по крайней мере, вы разберетесь с этим).
Адаптер предоставляет два метода, которые следует переопределить:
getItemViewType(int position)
Реализация этого метода по умолчанию всегда будет возвращать 0, указывая на то, что существует только 1 тип представления. В вашем случае это не так, и вам нужно будет найти способ утверждать, какая строка соответствует какому типу представления. В отличие от iOS, которая управляет этим за вас с помощью строк и разделов, здесь у вас будет только один индекс, на который можно положиться, и вам нужно будет использовать свои навыки разработчика, чтобы знать, когда позиция соотносится с заголовком раздела, а когда - с нормальный ряд.
createViewHolder(ViewGroup parent, int viewType)
В любом случае вам нужно переопределить этот метод, но обычно люди просто игнорируют параметр viewType. В соответствии с типом представления вам нужно будет создать правильный ресурс макета и соответственно создать держатель представления. RecyclerView будет обрабатывать переработку различных типов представлений таким образом, чтобы избежать конфликтов разных типов представлений.
Если вы планируете использовать LayoutManager по умолчанию, например LinearLayoutManager, вам должно быть хорошо идти. Если вы планируете создать собственную реализацию LayoutManager, вам придется немного потрудиться. Единственный API, с которым вам действительно нужно работать, - это то, findViewByPosition(int position)что дает заданное представление в определенной позиции. Поскольку вы, вероятно, захотите расположить его по-разному в зависимости от типа этого представления, у вас есть несколько вариантов:
Обычно при использовании шаблона ViewHolder вы устанавливаете тег представления с держателем представления. Вы можете использовать это во время выполнения в диспетчере компоновки, чтобы узнать, к какому типу относится представление, добавив поле в держателе представления, которое выражает это.
Поскольку вам понадобится функция, которая определяет, какая позиция коррелирует с каким типом представления, вы можете каким-то образом сделать этот метод глобально доступным (может быть, одноэлементный класс, который управляет данными?), А затем вы можете просто запросить тот же метод в соответствии с Положение.
Вот пример кода:
// 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;
}
}
Вот пример того, как должны выглядеть эти держатели просмотров:
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());
}
}
Конечно, в этом примере предполагается, что вы создали свой источник данных (myData) таким образом, чтобы упростить реализацию адаптера таким образом. В качестве примера я покажу вам, как я бы сконструировал источник данных, который показывает список имен и заголовок для каждого изменения 1-й буквы имени (предположим, что список составлен по алфавиту) - аналогично тому, как контакты список будет выглядеть так:
// 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();
}
Этот пример предназначен для решения довольно конкретной проблемы, но я надеюсь, что он дает вам хороший обзор того, как обрабатывать различные типы строк в переработчике, и позволяет вам внести необходимые изменения в свой собственный код в соответствии с вашими потребностями.