Обработка логики строк / разделов, аналогичная 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();
}
Этот пример предназначен для решения довольно конкретной проблемы, но я надеюсь, что он дает вам хороший обзор того, как обрабатывать различные типы строк в переработчике, и позволяет вам внести необходимые изменения в свой собственный код в соответствии с вашими потребностями.