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

J’ai passé un moment à essayer de trouver un moyen d’append un en-tête à RecyclerView , sans succès. C’est ce que j’ai eu jusqu’ici:

 @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 LayoutManager semble être l’object gérant la disposition des éléments RecyclerView . Comme je ne addHeaderView(View view) aucune addHeaderView(View view) , j’ai décidé d’utiliser la LayoutManager addView(View view, int position) et d’append ma vue d’en-tête en première position pour agir comme un en-tête.

Aaand c’est là que les choses deviennent plus moche:

 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 obtenu plusieurs NullPointerExceptions essayant d’appeler l’ addView(View view) à différents moments de la création d’activité (également essayé d’append la vue une fois que tout est configuré, même les données de l’adaptateur), j’ai réalisé que je ne savais pas le faire (et ça ne semble pas être).

PS: En outre, une solution capable de gérer le GridLayoutManager en plus du LinearLayoutManager serait vraiment appréciée!

J’ai trouvé un très bon article sur cette https://plus.google.com/+WillBlaschko/posts/3MFmgPbQuWx

Je devais append un pied de page à mon RecyclerView et ici je partage mon extrait de code car je pensais que cela pourrait être utile.

 public class RecentCallsAdapter extends RecyclerView.Adapter { private static final int FOOTER_VIEW = 1; // Define a view holder 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(parent.getContext()).inflate(R.layout.list_item_footer, parent, false); FooterViewHolder vh = new FooterViewHolder(v); return vh; } v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_normal, parent, false); NormalViewHolder vh = new NormalViewHolder(v); return vh; } // Now bind the viewholders 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 } } } 

J’ai eu le même problème sur Lollipop et créé deux approches pour enrouler l’adaptateur Recyclerview . L’un est assez facile à utiliser, mais je ne sais pas comment il se comportera avec un dataset changeant. Parce qu’il encapsule votre adaptateur et que vous devez vous assurer d’appeler des méthodes comme notifyDataSetChanged sur le bon object-adaptateur.

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

les gists

  • Utilisation de HeaderRecyclerViewAdapterV1.java new HeaderRecyclerViewAdapterV1(new RegularAdapter());
  • Utilisation de RegularAdapter extends HeaderRecyclerViewAdapterV2

HeaderRecyclerViewAdapterV1

 import android.support.v7.widget.RecyclerView; import android.view.ViewGroup; /** * Created by sebnapi on 08.11.14. * 

* This is a Plug-and-Play Approach for adding a Header or Footer to * a RecyclerView backed list *

* Just wrap your regular adapter like this *

* new HeaderRecyclerViewAdapterV1(new RegularAdapter()) *

* Let RegularAdapter implement HeaderRecyclerView, FooterRecyclerView or both * and you are ready to go. *

* 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. *

* With the other approach you need to let your Adapter extend HeaderRecyclerViewAdapterV2 * (and therefore change potentially more code) but possible omit these shortcomings. *

* 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. * 

* If you extend this Adapter you are able to add a Header, a Footer or both * by a similar ViewHolder pattern as in RecyclerView. *

* If you want to omit changes to your class hierarchy you can try the Plug-and-Play * approach HeaderRecyclerViewAdapterV1. *

* Don't override (Be careful while overriding) * - onCreateViewHolder * - onBindViewHolder * - getItemCount * - getItemViewType *

* 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. *

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

Feedback et forks appréciés. Je vais utiliser HeaderRecyclerViewAdapterV2 par moi-même et évoluer, tester et publier les modifications à l’avenir.

EDIT : @OvidiuLatcu Oui, j’ai eu quelques problèmes. En fait, j’ai arrêté d’implémenter l’en-tête implicitement par position - (useHeader() ? 1 : 0) et à la place créé une méthode publique int offsetPosition(int position) pour cela. Parce que si vous définissez un OnItemTouchListener sur Recyclerview, 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 le non-décalage. position 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 compense maintenant les positions explicites lorsque j’en ai besoin par mon propre code.

Très simple à résoudre !!

Je n’aime pas l’idée d’avoir une logique dans l’adaptateur en tant que type de vue différent, car à chaque fois qu’il vérifie le type de vue avant de retourner la vue. La solution ci-dessous évite les vérifications supplémentaires.

Ajoutez simplement la vue d’en-tête LinearLayout (vertical) + la vue recyclée + le pied de page dans android.support.v4.widget.NestedScrollView .

Regarde ça:

         

Ajouter cette ligne de code pour un défilement fluide

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

PS Pour conserver l’avantage de recyclage de recycleview, gardez sa taille wrap_content. Voir mon fil Twitter!

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

createViewHolder reçoit alors l’entier que vous avez renvoyé de getItemViewType , vous permettant de créer ou de configurer le titulaire de vue différemment 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 la position entière passée à bindViewHolder .

Vous pouvez utiliser cette bibliothèque GitHub pour append des en- têtes et / ou des pieds de page dans votre RecyclerView de la manière la plus simple possible.

Vous devez append la bibliothèque HFRecyclerView dans votre projet ou vous pouvez également la récupérer depuis Gradle:

 comstack 'com.mikhaellopez:hfrecyclerview:1.0.0' 

Ceci est un résultat dans l’image:

Aperçu

MODIFIER:

Si vous voulez juste append 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)); 

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

A créé un aperçu ici: HeaderViewRecyclerAdapter.java

La caractéristique principale que je voulais était une interface similaire à un ListView, donc je voulais pouvoir gonfler les vues dans mon fragment et les append au RecyclerView dans onCreateView . Cela se fait en créant un HeaderViewRecyclerAdapter transmettant l’adaptateur à addHeaderView et en appelant addHeaderView et addFooterView transmettant vos vues gonflées. HeaderViewRecyclerAdapter instance HeaderViewRecyclerAdapter tant qu’adaptateur sur RecyclerView .

Une exigence supplémentaire était que je devais pouvoir échanger facilement des 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 setAdapter pour changer l’adaptateur enveloppé en laissant les en-têtes et les pieds de page intacts, avec RecyclerView notifié de la modification.

Basé sur 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 rien (ni même de l’étendre).

Je le teste toujours, mais vous pouvez l’essayer si vous le souhaitez. S’il vous plaît laissez-moi savoir si vous trouvez des problèmes avec elle.

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 du recycleur:

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

2.ferridez 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. déplacer la méthode getItemCount

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

4.installez la méthode onCreateViewHolder. créer un titulaire 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.Mettez la méthode onBindViewHolder en place. lier des 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()); } } } 

Je sais que je suis 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 setHeader sur un élément sectionnable , puis vous appelez showAllHeaders . Si vous n’avez besoin que d’un en-tête, le premier élément devrait 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.

FlexibleAdapter vous permet de faire beaucoup plus que créer des en-têtes / sections. Vous devriez vraiment jeter un coup d’oeil: https://github.com/davideas/FlexibleAdapter .

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

entrer la description de l'image ici

Vous créez d’abord votre classe de section:

 class MySection extends StatelessSection { Ssortingng title; List list; public MySection(Ssortingng title, List 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 le RecyclerView avec vos sections et modifiez la taille 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); 

Je voudrais juste append une alternative à toutes ces implémentations HeaderRecyclerViewAdapter. CompoundAdapter:

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

C’est une approche plus souple, 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 implémenter un adaptateur plus complexe facilement en utilisant le même principe.