Android 5.0 - Ajouter un en-tête / pied de page à un RecyclerView

123

J'ai passé un moment à essayer de trouver un moyen d'ajouter un en-tête à un RecyclerView, sans succès.

Voici ce que j'ai obtenu jusqu'à présent:

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...

    layouManager = new LinearLayoutManager(getActivity());
    recyclerView.setLayoutManager(layouManager);

    LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    headerPlaceHolder = inflater.inflate(R.layout.view_header_holder_medium, null, false);
    layouManager.addView(headerPlaceHolder, 0);

   ...
}

Le LayoutManagersemble être l'objet qui gère la disposition des RecyclerViewéléments. Comme je ne pouvais trouver aucune addHeaderView(View view)méthode, je décide d'aller avec le LayoutManagerde » addView(View view, int position)méthode et d'ajouter mon point de vue d' en- tête dans la première position d'agir comme un en- tête.

Et c'est là que les choses deviennent plus laides:

java.lang.NullPointerException: Attempt to read from field 'android.support.v7.widget.RecyclerView$ViewHolder android.support.v7.widget.RecyclerView$LayoutParams.mViewHolder' on a null object reference
    at android.support.v7.widget.RecyclerView.getChildViewHolderInt(RecyclerView.java:2497)
    at android.support.v7.widget.RecyclerView$LayoutManager.addViewInt(RecyclerView.java:4807)
    at android.support.v7.widget.RecyclerView$LayoutManager.addView(RecyclerView.java:4803)
    at com.mathieumaree.showz.fragments.CategoryFragment.setRecyclerView(CategoryFragment.java:231)
    at com.mathieumaree.showz.fragments.CategoryFragment.access$200(CategoryFragment.java:47)
    at com.mathieumaree.showz.fragments.CategoryFragment$2.success(CategoryFragment.java:201)
    at com.mathieumaree.showz.fragments.CategoryFragment$2.success(CategoryFragment.java:196)
    at retrofit.CallbackRunnable$1.run(CallbackRunnable.java:41)
    at android.os.Handler.handleCallback(Handler.java:739)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:135)
    at android.app.ActivityThread.main(ActivityThread.java:5221)
    at java.lang.reflect.Method.invoke(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:372)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)

Après avoir NullPointerExceptionsessayé plusieurs fois d'appeler le addView(View view)à différents moments de la création de l'activité (j'ai également essayé d'ajouter la vue une fois que tout est configuré, même les données de l'adaptateur), j'ai réalisé que je n'avais aucune idée si c'était la bonne façon de le faire (et il ne semble pas être).

PS: Aussi, une solution qui pourrait gérer le GridLayoutManageren plus de la LinearLayoutManagerserait vraiment appréciée!

MathieuMaree
la source
jetez un oeil à ce stackoverflow.com/a/26573338/2127203
EC84B4
Le problème est dans le code de l'adaptateur. Cela signifie que, d'une manière ou d'une autre, vous retournez un visualiseur nul dans la fonction onCreateViewHolder.
Neo
Il existe un bon moyen d'ajouter un en-tête à StaggeredGridLayout stackoverflow.com/questions/42202735/…
Aleksey Timoshchenko

Réponses:

121

J'ai dû ajouter un pied de page à mon RecyclerViewet ici je partage mon extrait de code car je pensais que cela pourrait être utile. Veuillez vérifier les commentaires à l'intérieur du code pour une meilleure compréhension du flux global.

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;

public class RecyclerViewWithFooterAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private static final int FOOTER_VIEW = 1;
    private ArrayList<String> data; // Take any list that matches your requirement.
    private Context context;

    // Define a constructor
    public RecyclerViewWithFooterAdapter(Context context, ArrayList<String> data) {
        this.context = context;
        this.data = data;
    }

    // Define a ViewHolder for Footer view
    public class FooterViewHolder extends ViewHolder {
        public FooterViewHolder(View itemView) {
            super(itemView);
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // Do whatever you want on clicking the item
                }
            });
        }
    }

    // Now define the ViewHolder for Normal list item
    public class NormalViewHolder extends ViewHolder {
        public NormalViewHolder(View itemView) {
            super(itemView);

            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // Do whatever you want on clicking the normal items
                }
            });
        }
    }

    // And now in onCreateViewHolder you have to pass the correct view
    // while populating the list item.

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View v;

        if (viewType == FOOTER_VIEW) {
            v = LayoutInflater.from(context).inflate(R.layout.list_item_footer, parent, false);
            FooterViewHolder vh = new FooterViewHolder(v);
            return vh;
        }

        v = LayoutInflater.from(context).inflate(R.layout.list_item_normal, parent, false);

        NormalViewHolder vh = new NormalViewHolder(v);

        return vh;
    }

    // Now bind the ViewHolder in onBindViewHolder
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

        try {
            if (holder instanceof NormalViewHolder) {
                NormalViewHolder vh = (NormalViewHolder) holder;

                vh.bindView(position);
            } else if (holder instanceof FooterViewHolder) {
                FooterViewHolder vh = (FooterViewHolder) holder;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // Now the critical part. You have return the exact item count of your list
    // I've only one footer. So I returned data.size() + 1
    // If you've multiple headers and footers, you've to return total count
    // like, headers.size() + data.size() + footers.size()

    @Override
    public int getItemCount() {
        if (data == null) {
            return 0;
        }

        if (data.size() == 0) {
            //Return 1 here to show nothing
            return 1;
        }

        // Add extra view to show the footer view
        return data.size() + 1;
    }

    // Now define getItemViewType of your own.

    @Override
    public int getItemViewType(int position) {
        if (position == data.size()) {
            // This is where we'll add footer.
            return FOOTER_VIEW;
        }

        return super.getItemViewType(position);
    }

    // So you're done with adding a footer and its action on onClick.
    // Now set the default ViewHolder for NormalViewHolder

    public class ViewHolder extends RecyclerView.ViewHolder {
        // Define elements of a row here
        public ViewHolder(View itemView) {
            super(itemView);
            // Find view by ID and initialize here
        }

        public void bindView(int position) {
            // bindView() method to implement actions
        }
    }
}

L'extrait de code ci-dessus ajoute un pied de page au RecyclerView. Vous pouvez vérifier ce référentiel GitHub pour vérifier l'implémentation de l'ajout d'un en-tête et d'un pied de page.

Reaz Murshed
la source
2
Fonctionne bien. Cela doit être marqué comme une bonne réponse.
Naga Mallesh Maddali
1
C'est exactement ce que j'ai fait. Mais que faire si je voulais que mon RecyclerView adapte la liste échelonnée? Le premier élément (l'en-tête) sera également décalé. :(
Neon Warge
Ceci est un tutoriel sur la façon dont vous pouvez remplir votre RecyclerViewfichier dynamiquement. Vous pouvez avoir le contrôle sur chacun des éléments de votre RecyclerView. Veuillez consulter la section code pour un projet de travail. J'espère que cela pourrait aider. github.com/comeondude/dynamic-recyclerview/wiki
Reaz Murshed
2
int getItemViewType (int position)- Renvoie le type de vue de l'élément à la position à des fins de recyclage de vue. L'implémentation par défaut de cette méthode renvoie 0, faisant l'hypothèse d'un seul type de vue pour l'adaptateur. Contrairement aux ListViewadaptateurs, les types n'ont pas besoin d'être contigus. Envisagez d'utiliser des ressources d'identifiant pour identifier de manière unique les types de vues d'élément. - De la documentation. developer.android.com/reference/android/support/v7/widget/…
Reaz Murshed
3
Tant de travail manuel pour quelque chose que les gens veulent toujours faire. Je ne peux pas y croire ...
JohnyTex
28

Très simple à résoudre !!

Je n'aime pas l'idée d'avoir la logique à l'intérieur de l'adaptateur comme type de vue différent, car chaque fois qu'il vérifie le type de vue avant de renvoyer la vue. La solution ci-dessous évite les contrôles supplémentaires.

Ajoutez simplement la vue d'en-tête LinearLayout (vertical) + la vue de recyclage + la vue de pied de page dans android.support.v4.widget.NestedScrollView .

Regarde ça:

 <android.support.v4.widget.NestedScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

       <View
            android:id="@+id/header"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

        <android.support.v7.widget.RecyclerView
            android:id="@+id/list"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layoutManager="LinearLayoutManager"/>

        <View
            android:id="@+id/footer"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>
</android.support.v4.widget.NestedScrollView>

Ajoutez cette ligne de code pour un défilement fluide

RecyclerView v = (RecyclerView) findViewById(...);
v.setNestedScrollingEnabled(false);

Cela perdra toutes les performances du VR et RV essaiera de disposer tous les détenteurs de vue quel que soit le layout_heightRV.

Utilisation recommandée pour la liste de petite taille comme le tiroir de navigation ou les paramètres, etc.

Nishant Shah
la source
1
A travaillé pour moi assez simple
Hitesh Sahu
1
Il s'agit d'un moyen très simple d'ajouter des en-têtes et des pieds de page à une vue de l'outil de recyclage lorsque les en-têtes et les pieds de page n'ont pas à se répéter dans une liste.
user1841702
34
C'est un moyen très simple de perdre tous les avantages que cela RecyclerViewapporte - vous perdez le recyclage réel et l'optimisation qu'il apporte.
Marcin Koziński
1
J'ai essayé ce code, le défilement ne fonctionne pas correctement ... il est devenu un défilement trop lent .. veuillez suggérer si vous pouviez faire quelque chose pour cela
Nibha Jain
2
l'utilisation de scrollview imbriquée amènera recyclerview à mettre en cache toute la vue et si la taille de la vue recycleur est plus, le défilement et le temps de chargement augmenteront. Je suggère de ne pas utiliser ce code
Tushar Saha
25

J'ai eu le même problème sur Lollipop et j'ai créé deux approches pour envelopper l' Recyclerviewadaptateur. L'un est assez facile à utiliser, mais je ne sais pas comment il se comportera avec un ensemble de données changeant. Parce qu'il enveloppe votre adaptateur et que vous devez vous assurer d'appeler des méthodes comme notifyDataSetChangedsur le bon objet adaptateur.

L'autre ne devrait pas avoir de tels problèmes. Laissez simplement votre adaptateur régulier étendre la classe, implémentez les méthodes abstraites et vous devriez être prêt. Et les voici:

l'essentiel

HeaderRecyclerViewAdapterV1

import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;

/**
 * Created by sebnapi on 08.11.14.
 * <p/>
 * This is a Plug-and-Play Approach for adding a Header or Footer to
 * a RecyclerView backed list
 * <p/>
 * Just wrap your regular adapter like this
 * <p/>
 * new HeaderRecyclerViewAdapterV1(new RegularAdapter())
 * <p/>
 * Let RegularAdapter implement HeaderRecyclerView, FooterRecyclerView or both
 * and you are ready to go.
 * <p/>
 * I'm absolutely not sure how this will behave with changes in the dataset.
 * You can always wrap a fresh adapter and make sure to not change the old one or
 * use my other approach.
 * <p/>
 * With the other approach you need to let your Adapter extend HeaderRecyclerViewAdapterV2
 * (and therefore change potentially more code) but possible omit these shortcomings.
 * <p/>
 * TOTALLY UNTESTED - USE WITH CARE - HAVE FUN :)
 */
public class HeaderRecyclerViewAdapterV1 extends RecyclerView.Adapter {
    private static final int TYPE_HEADER = Integer.MIN_VALUE;
    private static final int TYPE_FOOTER = Integer.MIN_VALUE + 1;
    private static final int TYPE_ADAPTEE_OFFSET = 2;

    private final RecyclerView.Adapter mAdaptee;


    public HeaderRecyclerViewAdapterV1(RecyclerView.Adapter adaptee) {
        mAdaptee = adaptee;
    }

    public RecyclerView.Adapter getAdaptee() {
        return mAdaptee;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_HEADER && mAdaptee instanceof HeaderRecyclerView) {
            return ((HeaderRecyclerView) mAdaptee).onCreateHeaderViewHolder(parent, viewType);
        } else if (viewType == TYPE_FOOTER && mAdaptee instanceof FooterRecyclerView) {
            return ((FooterRecyclerView) mAdaptee).onCreateFooterViewHolder(parent, viewType);
        }
        return mAdaptee.onCreateViewHolder(parent, viewType - TYPE_ADAPTEE_OFFSET);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (position == 0 && holder.getItemViewType() == TYPE_HEADER && useHeader()) {
            ((HeaderRecyclerView) mAdaptee).onBindHeaderView(holder, position);
        } else if (position == mAdaptee.getItemCount() && holder.getItemViewType() == TYPE_FOOTER && useFooter()) {
            ((FooterRecyclerView) mAdaptee).onBindFooterView(holder, position);
        } else {
            mAdaptee.onBindViewHolder(holder, position - (useHeader() ? 1 : 0));
        }
    }

    @Override
    public int getItemCount() {
        int itemCount = mAdaptee.getItemCount();
        if (useHeader()) {
            itemCount += 1;
        }
        if (useFooter()) {
            itemCount += 1;
        }
        return itemCount;
    }

    private boolean useHeader() {
        if (mAdaptee instanceof HeaderRecyclerView) {
            return true;
        }
        return false;
    }

    private boolean useFooter() {
        if (mAdaptee instanceof FooterRecyclerView) {
            return true;
        }
        return false;
    }

    @Override
    public int getItemViewType(int position) {
        if (position == 0 && useHeader()) {
            return TYPE_HEADER;
        }
        if (position == mAdaptee.getItemCount() && useFooter()) {
            return TYPE_FOOTER;
        }
        if (mAdaptee.getItemCount() >= Integer.MAX_VALUE - TYPE_ADAPTEE_OFFSET) {
            new IllegalStateException("HeaderRecyclerViewAdapter offsets your BasicItemType by " + TYPE_ADAPTEE_OFFSET + ".");
        }
        return mAdaptee.getItemViewType(position) + TYPE_ADAPTEE_OFFSET;
    }


    public static interface HeaderRecyclerView {
        public RecyclerView.ViewHolder onCreateHeaderViewHolder(ViewGroup parent, int viewType);

        public void onBindHeaderView(RecyclerView.ViewHolder holder, int position);
    }

    public static interface FooterRecyclerView {
        public RecyclerView.ViewHolder onCreateFooterViewHolder(ViewGroup parent, int viewType);

        public void onBindFooterView(RecyclerView.ViewHolder holder, int position);
    }

}

HeaderRecyclerViewAdapterV2

import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;

/**
 * Created by sebnapi on 08.11.14.
 * <p/>
 * If you extend this Adapter you are able to add a Header, a Footer or both
 * by a similar ViewHolder pattern as in RecyclerView.
 * <p/>
 * If you want to omit changes to your class hierarchy you can try the Plug-and-Play
 * approach HeaderRecyclerViewAdapterV1.
 * <p/>
 * Don't override (Be careful while overriding)
 * - onCreateViewHolder
 * - onBindViewHolder
 * - getItemCount
 * - getItemViewType
 * <p/>
 * You need to override the abstract methods introduced by this class. This class
 * is not using generics as RecyclerView.Adapter make yourself sure to cast right.
 * <p/>
 * TOTALLY UNTESTED - USE WITH CARE - HAVE FUN :)
 */
public abstract class HeaderRecyclerViewAdapterV2 extends RecyclerView.Adapter {
    private static final int TYPE_HEADER = Integer.MIN_VALUE;
    private static final int TYPE_FOOTER = Integer.MIN_VALUE + 1;
    private static final int TYPE_ADAPTEE_OFFSET = 2;

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_HEADER) {
            return onCreateHeaderViewHolder(parent, viewType);
        } else if (viewType == TYPE_FOOTER) {
            return onCreateFooterViewHolder(parent, viewType);
        }
        return onCreateBasicItemViewHolder(parent, viewType - TYPE_ADAPTEE_OFFSET);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (position == 0 && holder.getItemViewType() == TYPE_HEADER) {
            onBindHeaderView(holder, position);
        } else if (position == getBasicItemCount() && holder.getItemViewType() == TYPE_FOOTER) {
            onBindFooterView(holder, position);
        } else {
            onBindBasicItemView(holder, position - (useHeader() ? 1 : 0));
        }
    }

    @Override
    public int getItemCount() {
        int itemCount = getBasicItemCount();
        if (useHeader()) {
            itemCount += 1;
        }
        if (useFooter()) {
            itemCount += 1;
        }
        return itemCount;
    }

    @Override
    public int getItemViewType(int position) {
        if (position == 0 && useHeader()) {
            return TYPE_HEADER;
        }
        if (position == getBasicItemCount() && useFooter()) {
            return TYPE_FOOTER;
        }
        if (getBasicItemType(position) >= Integer.MAX_VALUE - TYPE_ADAPTEE_OFFSET) {
            new IllegalStateException("HeaderRecyclerViewAdapter offsets your BasicItemType by " + TYPE_ADAPTEE_OFFSET + ".");
        }
        return getBasicItemType(position) + TYPE_ADAPTEE_OFFSET;
    }

    public abstract boolean useHeader();

    public abstract RecyclerView.ViewHolder onCreateHeaderViewHolder(ViewGroup parent, int viewType);

    public abstract void onBindHeaderView(RecyclerView.ViewHolder holder, int position);

    public abstract boolean useFooter();

    public abstract RecyclerView.ViewHolder onCreateFooterViewHolder(ViewGroup parent, int viewType);

    public abstract void onBindFooterView(RecyclerView.ViewHolder holder, int position);

    public abstract RecyclerView.ViewHolder onCreateBasicItemViewHolder(ViewGroup parent, int viewType);

    public abstract void onBindBasicItemView(RecyclerView.ViewHolder holder, int position);

    public abstract int getBasicItemCount();

    /**
     * make sure you don't use [Integer.MAX_VALUE-1, Integer.MAX_VALUE] as BasicItemViewType
     *
     * @param position
     * @return
     */
    public abstract int getBasicItemType(int position);

}

Commentaires et fourchettes appréciés. Je vais utiliser HeaderRecyclerViewAdapterV2par moi-même et évoluer, tester et publier les changements dans le futur.

EDIT : @OvidiuLatcu Oui, j'ai eu quelques problèmes. En fait, j'ai arrêté de compenser implicitement l'en-tête par position - (useHeader() ? 1 : 0)et j'ai plutôt créé une méthode publique int offsetPosition(int position)pour cela. Parce que si vous définissez un OnItemTouchListenerRecyclerview, vous pouvez intercepter le toucher, obtenir les coordonnées x, y du toucher, trouver la vue enfant correspondante , puis appeler recyclerView.getChildPosition(...)et vous obtiendrez toujours la position non décalée dans l'adaptateur! Ceci est un raccourci dans le code RecyclerView, je ne vois pas de méthode facile pour surmonter cela. C'est pourquoi je décale maintenant les positions explicites quand j'en ai besoin par mon propre code.

Seb
la source
Cela semble bon ! avoir des problèmes avec ça? ou pouvons-nous l'utiliser en toute sécurité? : D
Ovidiu Latcu
1
@OvidiuLatcu voir l'article
seb
Dans ces implémentations, il semble que vous ayez supposé que le nombre d'en-têtes et de pieds de page n'était que de 1 chacun?
rishabhmhjn
@seb Version 2 fonctionne comme du charme !! la seule chose que je devais modifier est la condition pour obtenir le pied de page sur les méthodes onBindViewHolder et getItemViewType. Le problème est que si vous obtenez la position en utilisant position == getBasicItemCount (), il ne retournera pas vrai pour la dernière position réelle, mais la dernière position - 1. Il a fini par placer le FooterView là (pas en bas). Nous l'avons corrigé en changeant la condition en position == getBasicItemCount () + 1 et cela a très bien fonctionné!
mmark
@seb version 2 fonctionne si bien. Merci beaucoup. je l'utilise. une petite chose que je suggère est d'ajouter le mot-clé «final» pour la fonction de remplacement.
RyanShao
10

Je n'ai pas essayé cela, mais j'ajouterais simplement 1 (ou 2, si vous voulez à la fois un en-tête et un pied de page) à l'entier retourné par getItemCount dans votre adaptateur. Vous pouvez ensuite remplacer getItemViewTypedans votre adaptateur pour renvoyer un entier différent lorsque i==0: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemViewType(int)

createViewHolderest ensuite passé l'entier que vous avez renvoyé getItemViewType, vous permettant de créer ou de configurer différemment le support de vue pour la vue d'en-tête: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html# createViewHolder (android.view.ViewGroup , int)

N'oubliez pas de soustraire un de l'entier de position passé à bindViewHolder.

Ian Newson
la source
Je ne pense pas que ce soit le travail de recyclerview. Le travail de Recyclerviews consiste simplement à recycler les vues. Écrire un layoutmanager avec une implémentation d'en-tête ou de pied de page est la voie à suivre.
IZI_Shadow_IZI
Merci @IanNewson pour votre réponse. Premièrement, cette solution semble fonctionner, même en utilisant uniquement getItemViewType(int position) { return position == 0 ? 0 : 1; }( RecyclerViewn'a pas de getViewTypeCount()méthode). D'un autre côté, je suis d'accord avec @IZI_Shadow_IZI, j'ai vraiment le sentiment que le LayoutManager devrait être celui qui gère ce genre de choses. Une autre idée?
MathieuMaree
@VieuMa vous avez probablement tous les deux raison, mais je ne sais pas comment faire cela actuellement et j'étais à peu près sûr que ma solution fonctionnerait. Une solution sous-optimale vaut mieux que pas de solution, ce que vous aviez auparavant.
Ian Newson
@VieuMa également, l'abstraction de l'en-tête et du pied de page dans l'adaptateur signifie qu'il doit gérer les deux types de mise en page demandés, l'écriture de votre propre gestionnaire de mise en page signifie la réimplémentation pour les deux types de mise en page.
Ian Newson
créez simplement un adaptateur qui encapsule n'importe quel adaptateur et ajoute la prise en charge de la vue d'en-tête à l'index 0. HeaderView dans listview crée de nombreux cas de bord et la valeur ajoutée est minime étant donné qu'il s'agit d'un problème facile à résoudre à l'aide d'un adaptateur wrapper.
yigit
9

Vous pouvez utiliser cette bibliothèque GitHub permettant d'ajouter un en- tête et / ou un pied de page dans votre RecyclerView de la manière la plus simple possible.

Vous devez ajouter la bibliothèque HFRecyclerView dans votre projet ou vous pouvez également la récupérer à partir de Gradle:

compile 'com.mikhaellopez:hfrecyclerview:1.0.0'

Ceci est un résultat en image:

Aperçu

ÉDITER:

Si vous souhaitez simplement ajouter une marge en haut et / ou en bas avec cette bibliothèque: SimpleItemDecoration :

int offsetPx = 10;
recyclerView.addItemDecoration(new StartOffsetItemDecoration(offsetPx));
recyclerView.addItemDecoration(new EndOffsetItemDecoration(offsetPx));
lopez.mikhael
la source
Cette bibliothèque ajoute correctement la vue sur l'en-tête dans LinearLayoutManager mais je veux définir la vue comme en-tête dans GridLayoutManager, qui se déroule sur toute la largeur de l'écran. Est-ce possible avec cette bibliothèque.
cb
Non, cette bibliothèque vous permet de modifier le premier et le dernier élément d'un recyclerView lors de l'adaptation (RecyclerView.Adapter). Vous pouvez utiliser cet adaptateur sur un GridView sans problème. Je pense donc que cette bibliothèque permet de faire ce que vous voulez.
lopez.mikhael
6

J'ai fini par implémenter mon propre adaptateur pour envelopper tout autre adaptateur et fournir des méthodes pour ajouter des vues d'en-tête et de pied de page.

Créé un résumé ici: HeaderViewRecyclerAdapter.java

La principale fonctionnalité que je voulais était une interface similaire à une ListView, je voulais donc pouvoir gonfler les vues dans mon Fragment et les ajouter au RecyclerViewin onCreateView. Cela se fait en créant un HeaderViewRecyclerAdapterpassage de l'adaptateur à encapsuler, et en appelant addHeaderViewet en addFooterViewpassant vos vues gonflées. Définissez ensuite l' HeaderViewRecyclerAdapterinstance en tant qu'adaptateur sur le RecyclerView.

Une exigence supplémentaire était que je devais être en mesure de remplacer facilement les adaptateurs tout en conservant les en-têtes et les pieds de page, je ne voulais pas avoir plusieurs adaptateurs avec plusieurs instances de ces en-têtes et pieds de page. Vous pouvez donc appeler setAdapterpour changer l'adaptateur enveloppé en laissant les en-têtes et les pieds de page intacts, en RecyclerViewétant averti du changement.

darnmason
la source
3

ma façon "Keep it simple stupid" ... ça gaspille des ressources, je sais, mais je m'en fiche car mon code reste simple alors ... 1) ajouter un pied de page avec visibilité GONE à votre item_layout

  <LinearLayout
        android:id="@+id/footer"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:orientation="vertical"
        android:visibility="gone">
    </LinearLayout>

2) puis mettez-le visible sur le dernier élément

public void onBindViewHolder(ChannelAdapter.MyViewHolder holder, int position) {
        boolean last = position==data.size()-1;
        //....
        holder.footer.setVisibility(View.GONE);
        if (last && showFooter){
            holder.footer.setVisibility(View.VISIBLE);
        }
    }

faire le contraire pour l'en-tête

Luca Rocchi
la source
1

Sur la base de la solution de @ seb, j'ai créé une sous-classe de RecyclerView.Adapter qui prend en charge un nombre arbitraire d'en-têtes et de pieds de page.

https://gist.github.com/mheras/0908873267def75dc746

Bien que cela semble être une solution, je pense également que cette chose devrait être gérée par le LayoutManager. Malheureusement, j'en ai besoin maintenant et je n'ai pas le temps d'implémenter un StaggeredGridLayoutManager à partir de zéro (ni même de l'étendre).

Je suis toujours en train de le tester, mais vous pouvez l'essayer si vous le souhaitez. Veuillez me faire savoir si vous rencontrez des problèmes avec celui-ci.

mato
la source
1

Vous pouvez utiliser viewtype pour résoudre ce problème, voici ma démo: https://github.com/yefengfreedom/RecyclerViewWithHeaderFooterLoadingEmptyViewErrorView

  1. vous pouvez définir un mode d'affichage de la vue recycleur:

    public static final int MODE_DATA = 0, MODE_LOADING = 1, MODE_ERROR = 2, MODE_EMPTY = 3, MODE_HEADER_VIEW = 4, MODE_FOOTER_VIEW = 5;

2. outrepasser le mothod getItemViewType

 @Override
public int getItemViewType(int position) {
    if (mMode == RecyclerViewMode.MODE_LOADING) {
        return RecyclerViewMode.MODE_LOADING;
    }
    if (mMode == RecyclerViewMode.MODE_ERROR) {
        return RecyclerViewMode.MODE_ERROR;
    }
    if (mMode == RecyclerViewMode.MODE_EMPTY) {
        return RecyclerViewMode.MODE_EMPTY;
    }
    //check what type our position is, based on the assumption that the order is headers > items > footers
    if (position < mHeaders.size()) {
        return RecyclerViewMode.MODE_HEADER_VIEW;
    } else if (position >= mHeaders.size() + mData.size()) {
        return RecyclerViewMode.MODE_FOOTER_VIEW;
    }
    return RecyclerViewMode.MODE_DATA;
}

3. outrepasser la méthode getItemCount

@Override
public int getItemCount() {
    if (mMode == RecyclerViewMode.MODE_DATA) {
        return mData.size() + mHeaders.size() + mFooters.size();
    } else {
        return 1;
    }
}

4. annulez la méthode onCreateViewHolder. créer un support de vue par viewType

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (viewType == RecyclerViewMode.MODE_LOADING) {
        RecyclerView.ViewHolder loadingViewHolder = onCreateLoadingViewHolder(parent);
        loadingViewHolder.itemView.setLayoutParams(
                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, parent.getHeight() - mToolBarHeight)
        );
        return loadingViewHolder;
    }
    if (viewType == RecyclerViewMode.MODE_ERROR) {
        RecyclerView.ViewHolder errorViewHolder = onCreateErrorViewHolder(parent);
        errorViewHolder.itemView.setLayoutParams(
                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, parent.getHeight() - mToolBarHeight)
        );
        errorViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View v) {
                if (null != mOnErrorViewClickListener) {
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            mOnErrorViewClickListener.onErrorViewClick(v);
                        }
                    }, 200);
                }
            }
        });
        return errorViewHolder;
    }
    if (viewType == RecyclerViewMode.MODE_EMPTY) {
        RecyclerView.ViewHolder emptyViewHolder = onCreateEmptyViewHolder(parent);
        emptyViewHolder.itemView.setLayoutParams(
                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, parent.getHeight() - mToolBarHeight)
        );
        emptyViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View v) {
                if (null != mOnEmptyViewClickListener) {
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            mOnEmptyViewClickListener.onEmptyViewClick(v);
                        }
                    }, 200);
                }
            }
        });
        return emptyViewHolder;
    }
    if (viewType == RecyclerViewMode.MODE_HEADER_VIEW) {
        RecyclerView.ViewHolder headerViewHolder = onCreateHeaderViewHolder(parent);
        headerViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View v) {
                if (null != mOnHeaderViewClickListener) {
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            mOnHeaderViewClickListener.onHeaderViewClick(v, v.getTag());
                        }
                    }, 200);
                }
            }
        });
        return headerViewHolder;
    }
    if (viewType == RecyclerViewMode.MODE_FOOTER_VIEW) {
        RecyclerView.ViewHolder footerViewHolder = onCreateFooterViewHolder(parent);
        footerViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View v) {
                if (null != mOnFooterViewClickListener) {
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            mOnFooterViewClickListener.onFooterViewClick(v, v.getTag());
                        }
                    }, 200);
                }
            }
        });
        return footerViewHolder;
    }
    RecyclerView.ViewHolder dataViewHolder = onCreateDataViewHolder(parent);
    dataViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(final View v) {
            if (null != mOnItemClickListener) {
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        mOnItemClickListener.onItemClick(v, v.getTag());
                    }
                }, 200);
            }
        }
    });
    dataViewHolder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(final View v) {
            if (null != mOnItemLongClickListener) {
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        mOnItemLongClickListener.onItemLongClick(v, v.getTag());
                    }
                }, 200);
                return true;
            }
            return false;
        }
    });
    return dataViewHolder;
}

5. Remplacez la méthode onBindViewHolder. lier les données par viewType

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    if (mMode == RecyclerViewMode.MODE_LOADING) {
        onBindLoadingViewHolder(holder, position);
    } else if (mMode == RecyclerViewMode.MODE_ERROR) {
        onBindErrorViewHolder(holder, position);
    } else if (mMode == RecyclerViewMode.MODE_EMPTY) {
        onBindEmptyViewHolder(holder, position);
    } else {
        if (position < mHeaders.size()) {
            if (mHeaders.size() > 0) {
                onBindHeaderViewHolder(holder, position);
            }
        } else if (position >= mHeaders.size() + mData.size()) {
            if (mFooters.size() > 0) {
                onBindFooterViewHolder(holder, position - mHeaders.size() - mData.size());
            }
        } else {
            onBindDataViewHolder(holder, position - mHeaders.size());
        }
    }
}
yefeng
la source
Et si votre lien était rompu à l'avenir?
Gopal Singh Sirvi
Bonne question Je vais éditer ma réponse et publier mon code ici
yefeng
1

Vous pouvez utiliser la bibliothèque SectionedRecyclerViewAdapter pour regrouper vos éléments en sections et ajouter un en-tête à chaque section, comme sur l'image ci-dessous:

entrez la description de l'image ici

Commencez par créer votre classe de section:

class MySection extends StatelessSection {

    String title;
    List<String> list;

    public MySection(String title, List<String> list) {
        // call constructor with layout resources for this Section header, footer and items 
        super(R.layout.section_header, R.layout.section_item);

        this.title = title;
        this.list = list;
    }

    @Override
    public int getContentItemsTotal() {
        return list.size(); // number of items of this section
    }

    @Override
    public RecyclerView.ViewHolder getItemViewHolder(View view) {
        // return a custom instance of ViewHolder for the items of this section
        return new MyItemViewHolder(view);
    }

    @Override
    public void onBindItemViewHolder(RecyclerView.ViewHolder holder, int position) {
        MyItemViewHolder itemHolder = (MyItemViewHolder) holder;

        // bind your view here
        itemHolder.tvItem.setText(list.get(position));
    }

    @Override
    public RecyclerView.ViewHolder getHeaderViewHolder(View view) {
        return new SimpleHeaderViewHolder(view);
    }

    @Override
    public void onBindHeaderViewHolder(RecyclerView.ViewHolder holder) {
        MyHeaderViewHolder headerHolder = (MyHeaderViewHolder) holder;

        // bind your header view here
        headerHolder.tvItem.setText(title);
    }
}

Ensuite, vous configurez RecyclerView avec vos sections et modifiez le SpanSize des en-têtes avec un GridLayoutManager:

// Create an instance of SectionedRecyclerViewAdapter 
SectionedRecyclerViewAdapter sectionAdapter = new SectionedRecyclerViewAdapter();

// Create your sections with the list of data
MySection section1 = new MySection("My Section 1 title", dataList1);
MySection section2 = new MySection("My Section 2 title", dataList2);

// Add your Sections to the adapter
sectionAdapter.addSection(section1);
sectionAdapter.addSection(section2);

// Set up a GridLayoutManager to change the SpanSize of the header
GridLayoutManager glm = new GridLayoutManager(getContext(), 2);
glm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
    @Override
    public int getSpanSize(int position) {
        switch(sectionAdapter.getSectionItemViewType(position)) {
            case SectionedRecyclerViewAdapter.VIEW_TYPE_HEADER:
                return 2;
            default:
                return 1;
        }
    }
});

// Set up your RecyclerView with the SectionedRecyclerViewAdapter
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(glm);
recyclerView.setAdapter(sectionAdapter);
Gustavo
la source
0

Je sais que j'arrive en retard, mais ce n'est que récemment que j'ai pu implémenter un tel "addHeader" sur l'adaptateur. Dans mon projet FlexibleAdapter , vous pouvez appeler setHeaderun élément Sectionable , puis vous appelez showAllHeaders. Si vous n'avez besoin que d'un seul en-tête, le premier élément doit avoir l'en-tête. Si vous supprimez cet élément, l'en-tête est automatiquement lié au suivant.

Malheureusement, les pieds de page ne sont pas (encore) couverts.

Le FlexibleAdapter vous permet de faire bien plus que de créer des en-têtes / sections. Vous devriez vraiment jeter un coup d'œil: https://github.com/davideas/FlexibleAdapter .

Davideas
la source
0

Je voudrais simplement ajouter une alternative à toutes ces implémentations de HeaderRecyclerViewAdapter. CompoundAdapter:

https://github.com/negusoft/CompoundAdapter-android

C'est une approche plus flexible, car vous pouvez créer un AdapterGroup à partir des adaptateurs. Pour l'exemple d'en-tête, utilisez votre adaptateur tel quel, avec un adaptateur contenant un élément pour l'en-tête:

AdapterGroup adapterGroup = new AdapterGroup();
adapterGroup.addAdapter(SingleAdapter.create(R.layout.header));
adapterGroup.addAdapter(new MyAdapter(...));

recyclerView.setAdapter(adapterGroup);

C'est assez simple et lisible. Vous pouvez facilement implémenter un adaptateur plus complexe en utilisant le même principe.

blurkidi
la source
0

recyclerview:1.2.0introduit la classe ConcatAdapter qui concatène plusieurs adaptateurs en un seul. Cela permet donc de créer des adaptateurs d'en-tête / pied de page séparés et de les réutiliser sur plusieurs listes.

myRecyclerView.adapter = ConcatAdapter(headerAdapter, listAdapter, footerAdapter)

Jetez un œil à l' article d'annonce . Il contient un exemple d'affichage de la progression du chargement dans l'en-tête et le pied de page à l'aide de ConcatAdapter.

Pour le moment où je poste cette réponse, la version 1.2.0de la bibliothèque est en phase alpha, donc l'API pourrait changer. Vous pouvez vérifier le statut ici .

Valeriy Katkov
la source