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

? MathieuMaree @ | Original: StackOverFlow

Je ai passé un moment à essayer de comprendre un moyen d'ajouter un en-tête à un RecyclerView, sans succès . Ce est ce que je ai à ce jour:

@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 la manipulation de la disposition des éléments de RecyclerView objet. Comme je ne pouvais pas trouver une méthode «____», je ai décidé d'aller avec addHeaderView(View view) la méthode de LayoutManager et d'ajouter ma tête vue en première position d'agir comme un en-tête .

Aaand ce est là que les choses deviennent plus laid :

addView(View view, int position)

Après avoir obtenu plusieurs 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) essayer d'appeler le «____» à différents moments de la création d'activité (également essayé d'ajouter la vue une fois que tout est mis en place, même les données de l'adaptateur ), je ai réalisé que je ne ai aucune idée si ce est la bonne façon de le faire ( et il ne semble pas être ) .

Quelqu'un pourrait- il me aider? Ou au moins me donner une idée / nouvelle direction à explorer ?

Merci d'avance!

VieuMa

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



Top 5 Respuesta

1Ian Newson @

Je ne ai pas essayé, mais je voudrais simplement ajouter une (ou deux, si vous voulez à la fois un en-tête et pied de page ) à l'entier retourné par GetItemCount votre adaptateur . Vous pouvez ensuite remplacer NullPointerExceptions dans votre adaptateur pour retourner un entier différent lorsque addView(View view) : https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemViewType(int)

getItemViewType Est ensuite passé l'entier votre retour de i==0, vous permettant de créer ou de configurer le support 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 )

Ne oubliez pas de soustraire l'un de l' entier de position transmis à «____» .



2seb @

Je ai eu le même problème et a créé deux approches pour envelopper l'adaptateur . On est assez facile à utiliser, mais je ne suis pas sûr de savoir comment il se comportera avec un ensemble de données en évolution. Parce qu'il se enroule votre carte et vous avez besoin de vous faire vous d'appeler des méthodes comme createViewHolder sur l'adaptateur objet droite.

L'autre ne devraient pas avoir de tels problèmes . Laissez votre carte régulière étendre la classe, mettre en œuvre les méthodes abstraites et vous devriez être prêt . Et ils sont ici:

gists

https://gist.github.com/sebnapi/a2596ec3d1730ea47986 usage getItemViewType https://gist.github.com/sebnapi/fde648c17616d9d3bcde usage bindViewHolder

HeaderRecyclerViewAdapterV1

notifyDataSetChanged

HeaderRecyclerViewAdapterV2

new HeaderRecyclerViewAdapterV1(new RegularAdapter());

Commentaires et fourches appréciés. Je vais utiliser RegularAdapter extends HeaderRecyclerViewAdapterV2 par mon auto et d'évoluer, tester et poster les changements à l'avenir .

EDIT: Oui OvidiuLatcu Je ai eu quelques problèmes . En fait, je ai arrêté de compenser la tête implicitement par « ____ » et la place créé une méthode publique 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); } } pour elle. Parce que si vous définissez une 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); } sur Recyclerview, vous pouvez intercepter le contact, obtenir les coordonnées x, y de la toucher, de trouver le point de vue selon des enfants et ensuite appeler «____» et vous obtiendrez toujours la position de non - offsetted dans le adaptateur ! Ce est un shortcomming dans le Code RecyclerView, je ne vois pas une méthode facile pour surmonter cela. Ce est pourquoi je décalage maintenant les positions explicites quand je dois par mon propre code .

3darnmason @

Je ai fini par mettre en œuvre ma propre adaptateur pour envelopper tout autre adaptateur et fournir des méthodes pour ajouter tête et de pied vues .

Crée un point essentiel ici: https://gist.github.com/darnmason/7bbf8beae24fe7296c8a

La principale caractéristique que je voulais était une interface similaire à un ListView, donc je voulais être capable de gonfler les vues dans mon Fragment et les ajouter à la « ____ » dans «____» . Cela se fait par la création d'un HeaderRecyclerViewAdapterV2 passer l'adaptateur à emballer, et appelant position - (useHeader() ? 1 : 0) et int offsetPosition(int position) passant vos vues gonflés . Réglez ensuite le OnItemTouchListener instance que l'adaptateur sur le «____» .

Une exigence supplémentaire était que je avais besoin d'être en mesure d'échanger facilement des adaptateurs tout en gardant les en-têtes et pieds de page, je ne voulais pas d'avoir plusieurs cartes avec plusieurs instances de ces en-têtes et pieds de page . Ainsi, vous pouvez appeler recyclerView.getChildPosition(...) pour changer l'adaptateur enveloppé laissant les en-têtes et pieds de page intact, avec le RecyclerView avoir été informé du changement.

4mato @

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

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

Bien que cela semble être une solution, je pense aussi que cette chose devrait être géré par le gestionnaire LayoutManager . Malheureusement, je en ai besoin maintenant et je ne ai pas le temps de mettre en œuvre un StaggeredGridLayoutManager à partir de zéro ( ni même se étendre d'elle ) .

Je suis toujours le tester, mais vous pouvez essayer si vous voulez . Se il vous plaît laissez-moi savoir si vous trouvez des problèmes avec elle .