web-dev-qa-db-fra.com

Diviser des éléments sur des groupes dans RecyclerView

J'ai besoin de diviser des éléments dans RecyclerView sur des groupes avec des titres (comme dans l'application Boîte de réception sur l'image ci-dessous). Aidez-moi donc à déterminer quelle approche serait la meilleure pour mon cas: 1) Je peux utiliser des mises en page hétérogènes, mais c'est pas si pratique d'insérer de nouveaux éléments dans des groupes (car j'ai besoin de vérifier si des éléments du même groupe ont déjà été ajoutés ou si j'ai besoin d'ajouter un nouveau séparateur). Donc, dans ce cas, je vais emballer toutes les opérations avec une telle structure de données dans une classe séparée.

2) Théoriquement, je peux emballer chaque groupe dans son propre RecyclerView avec une étiquette. Est-ce une bonne idée?

Inbox app

40
Leo

Par exemple, vous pouvez:

  1. Utiliser un TreeMap<Date,List<Event>> pour scinder des éléments par date. Ce sera une collection pour garder vos objets métier. Bien sûr, si vous avez déjà une structure similaire, vous pouvez la conserver. Il est simplement important de pouvoir facilement créer une liste d'éléments pour remplir l'interface utilisateur avec le bon ordre d'éléments.

  2. Définissez un type abstrait dédié pour les éléments List (par exemple, ListItem) pour envelopper vos objets métier. Sa mise en œuvre pourrait être quelque chose comme ceci:

    public abstract class ListItem {
    
        public static final int TYPE_HEADER = 0;
        public static final int TYPE_EVENT = 1;
    
        abstract public int getType();
    } 
    
  3. Définissez une classe pour chacun des types d’éléments de votre liste (ici, j’ai ajouté seulement deux types, mais vous pouvez en utiliser plusieurs selon vos besoins):

    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. Créez une liste comme suit (où mEventsMap correspond à la construction de carte au point 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. Définissez un adaptateur pour votre RecyclerView, en travaillant sur List défini au point 4. Ici, l'important est de remplacer la méthode getItemViewType comme suit:

    @Override
    public int getItemViewType(int position) {
        return mItems.get(position).getType();
    }
    

    Ensuite, vous devez disposer de deux dispositions et de ViewHolder pour les éléments d’en-tête et d’événement. Les méthodes d’adaptateur doivent s’occuper de cela en conséquence:

    @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
        }
    }
    

Ici c'est un dépôt sur GitHub fournissant une implémentation de l'approche expliquée ci-dessus.

93
thetonrifles

Vous pouvez essayer d'utiliser la bibliothèque que j'ai écrite pour résoudre ce problème dans mon projet. Dépendance de Gradle (nécessite jcenter repo inclus):

dependencies {
    //your other dependencies
    compile 'su.j2e:rv-joiner:1.0.3'//latest version by now
}

Ensuite, dans votre situation, vous pouvez faire comme ceci:

//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());

Lorsque vous devez ajouter un élément, ajoutez-le à l'adaptateur approprié, par exemple:

if (timeIsToday) {
    todayAdapter.addItem(item);//or other func you've written
} else if (timeIsYesterday) {
    yesterdayAdapter.addItem(item);
}

Si vous devez ajouter un nouveau groupe à la vue Recycler de manière dynamique, vous pouvez utiliser ces méthodes:

rvJoiner.add(new JoinableLayout(R.layout.tomorrow));
YourAdapter tomorrowAdapter = new YourAdapter();
rvJoiner.add(new JoinableAdapter(tomorrowAdapter));

Vous pouvez vérifier ce lien pour plus de description de la bibliothèque. Je ne peux pas dire que c'est sûrement le meilleur moyen d'atteindre votre objectif, mais cela m'aide parfois.

UPD:

J'ai trouvé le moyen de faire cela sans utiliser de bibliothèques externes . Utilisez la classe RecyclerView.ItemDecoration . Par exemple, pour regrouper des éléments par 3 éléments dans un groupe, vous pouvez procéder comme suit:

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);
            }
        }
    });

J'espère que ça aide.

10
j2esu