Разделите элементы на группы в RecyclerView

Мне нужно разделить элементы в RecyclerView на группы с заголовками (как в приложении "Входящие" на рисунке ниже), поэтому помогите мне выяснить, какой подход был бы лучше для моего случая: 1) Я могу использовать гетерогенные макеты для него, но это не так удобно вставлять новые элементы в группы (потому что мне нужно проверить, уже добавлены ли элементы той же группы или мне нужно добавить новый разделитель). Поэтому в этом случае все операции с такой структурой данных я оберну в отдельный класс.

2) Теоретически я могу обернуть каждую группу в свой собственный RecyclerView с меткой, это хорошая идея?

Приложение Входящие

2 ответа

Решение

Например, вы можете:

  1. Использовать TreeMap<Date,List<Event>> для разделения элементов по дате. Это будет коллекция для хранения ваших бизнес-объектов. Конечно, если у вас уже есть подобная структура, вы можете сохранить ее. Просто важно иметь что-то для простого создания списка элементов для заполнения пользовательского интерфейса с правильным порядком элементов.

  2. Определите выделенный абстрактный тип для List предметы (например, ListItem) чтобы обернуть ваши бизнес-объекты. Его реализация может быть примерно такой:

    public abstract class ListItem {
    
        public static final int TYPE_HEADER = 0;
        public static final int TYPE_EVENT = 1;
    
        abstract public int getType();
    } 
    
  3. Определите класс для каждого типа элемента List (здесь я добавил только два типа, но вы можете использовать их по мере необходимости):

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

Вы можете попробовать использовать библиотеку, которую я написал, чтобы решить эту проблему в моем проекте. Зависимость 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);
            }
        }
    });

Надеюсь, поможет.

Этот вопрос задан еще в 2016 году, а тем временем (2020 год) доступны разные библиотеки для группировки представлений ресайклера. Самые популярные:

Другие вопросы по тегам