Разделить элементы на группы в RecyclerView
Мне нужно разделить элементы в RecyclerView на группы с заголовками (например, в приложении "Входящие" на рисунке ниже), так что помогите мне разобраться, какой подход будет лучше для моего случая:
1) Я могу использовать Heterogenous layouts для него, но не так удобно вставлять новые элементы в группы (потому что мне нужно проверить, добавлены ли элементы той же группы или мне нужно добавить новый разделитель). Таким образом, в этом случае я перенесу все операции с такой структурой данных в отдельный класс.
2) Теоретически я могу обернуть каждую группу в свой собственный RecyclerView с меткой, это хорошая идея?
![Inbox app]()
Ответы
Ответ 1
Например, вы можете:
-
Используйте TreeMap<Date,List<Event>>
для разделения элементов по дате. Это будет сбор для хранения ваших бизнес-объектов. Конечно, если у вас уже есть аналогичная структура, вы можете сохранить ее. Просто важно иметь что-то для упрощения составления списка элементов для заполнения пользовательского интерфейса с правильным порядком элементов.
-
Определите выделенный абстрактный тип для элементов List
(например, ListItem
), чтобы обернуть ваши бизнес-объекты. Его реализация может быть примерно такой:
public abstract class ListItem {
public static final int TYPE_HEADER = 0;
public static final int TYPE_EVENT = 1;
abstract public int getType();
}
-
Определите класс для каждого типа вашего элемента списка (здесь я добавил только два типа, но вы можете использовать многие, как вам нужно):
public class HeaderItem extends ListItem {
private Date date;
// here getters and setters
// for title and so on, built
// using date
@Override
public int getType() {
return TYPE_HEADER;
}
}
public class EventItem extends ListItem {
private Event event;
// here getters and setters
// for title and so on, built
// using event
@Override
public int getType() {
return TYPE_EVENT;
}
}
-
Создайте список следующим образом (где mEventsMap - построение карты в точке 1):
List<ListItem> mItems;
// ...
mItems = new ArrayList<>();
for (Date date : mEventsMap.keySet()) {
HeaderItem header = new HeaderItem();
header.setDate(date);
mItems.add(header);
for (Event event : mEventsMap.get(date)) {
EventItem item = new EventItem();
item.setEvent(event);
mItems.add(item);
}
}
-
Определите адаптер для RecyclerView
, работающий на List
, определенный в точке 4. Здесь важно переопределить метод getItemViewType
следующим образом:
@Override
public int getItemViewType(int position) {
return mItems.get(position).getType();
}
Затем вам нужно иметь два макета и ViewHolder для заголовков и событий. Адаптеры должны позаботиться об этом следующим образом:
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == ListItem.TYPE_HEADER) {
View itemView = mLayoutInflater.inflate(R.layout.view_list_item_header, parent, false);
return new HeaderViewHolder(itemView);
} else {
View itemView = mLayoutInflater.inflate(R.layout.view_list_item_event, parent, false);
return new EventViewHolder(itemView);
}
}
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, final int position) {
int type = getItemViewType(position);
if (type == ListItem.TYPE_HEADER) {
HeaderItem header = (HeaderItem) mItems.get(position);
HeaderViewHolder holder = (HeaderViewHolder) viewHolder;
// your logic here
} else {
EventItem event = (EventItem) mItems.get(position);
EventViewHolder holder = (EventViewHolder) viewHolder;
// your logic here
}
}
Здесь это репозиторий на GitHub, обеспечивающий реализацию описанного выше подхода.
Ответ 2
Вы можете попытаться использовать библиотеку, которую я написал, чтобы решить эту проблему в моем проекте. Gradle зависимость (требуется ретрансляция jcenter):
dependencies {
//your other dependencies
compile 'su.j2e:rv-joiner:1.0.3'//latest version by now
}
Тогда, в вашей ситуации, вы можете сделать что-то вроде этого:
//init your RecyclerView as usual
RecyclerView rv = (RecyclerView) findViewById(R.id.rv);
rv.setLayoutManager(new LinearLayoutManager(this));
//construct a joiner
RvJoiner rvJoiner = new RvJoiner();
rvJoiner.add(new JoinableLayout(R.layout.today));
YourAdapter todayAdapter = new YourAdapter();
rvJoiner.add(new JoinableAdapter(todayAdapter));
rvJoiner.add(new JoinableLayout(R.layout.yesterday));
YourAdapter yesterdayAdapter = new YourAdapter();
rvJoiner.add(new JoinableAdapter(yesterdayAdapter));
//set join adapter to your RecyclerView
rv.setAdapter(rvJoiner.getAdapter());
Когда вам нужно добавить элемент, добавьте его в соответствующий адаптер, например:
if (timeIsToday) {
todayAdapter.addItem(item);//or other func you've written
} else if (timeIsYesterday) {
yesterdayAdapter.addItem(item);
}
Если вам нужно динамически добавлять новую группу в просмотр ресайклеров, вы можете использовать следующие методы:
rvJoiner.add(new JoinableLayout(R.layout.tomorrow));
YourAdapter tomorrowAdapter = new YourAdapter();
rvJoiner.add(new JoinableAdapter(tomorrowAdapter));
Вы можете проверить эту ссылку для более подробного описания библиотеки. Я не могу сказать, что это, безусловно, лучший способ достичь вашей цели, но иногда это помогает мне.
UPD:
Я нашел способ сделать это без использования внешних библиотек. Используйте класс RecyclerView.ItemDecoration. Например, чтобы группировать элементы по 3 элементам в группе, вы можете сделать это:
recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
private int textSize = 50;
private int groupSpacing = 100;
private int itemsInGroup = 3;
private Paint paint = new Paint();
{
paint.setTextSize(textSize);
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
for (int i = 0; i < parent.getChildCount(); i++) {
View view = parent.getChildAt(i);
int position = parent.getChildAdapterPosition(view);
if (position % itemsInGroup == 0) {
c.drawText("Group " + (position / itemsInGroup + 1), view.getLeft(),
view.getTop() - groupSpacing / 2 + textSize / 3, paint);
}
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (parent.getChildAdapterPosition(view) % itemsInGroup == 0) {
outRect.set(0, groupSpacing, 0, 0);
}
}
});
Надеюсь, что это поможет.