J'ai passé un moment à essayer de trouver un moyen d'ajouter un en-tête à un RecyclerView
, sans succès. C'est ce que j'ai eu 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);
...
}
La LayoutManager
semble être l'objet qui gère la disposition des éléments RecyclerView
. N'ayant pu trouver aucune méthode addHeaderView(View view)
, j'ai décidé d'utiliser la méthode LayoutManager
's addView(View view, int position)
et d'ajouter ma vue d'en-tête en première position pour agir comme un en-tête.
Aaand c'est là que les choses se compliquent:
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
en essayant d'appeler la 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 ne savais pas si cela est la bonne façon de le faire (et cela ne semble pas être le cas).
PS: En outre, une solution pouvant gérer la GridLayoutManager
en plus de la LinearLayoutManager
serait vraiment appréciée!
Je devais ajouter un pied de page à mon RecyclerView
et je partage ici mon extrait de code car je pensais que cela pourrait être utile. Veuillez vérifier les commentaires dans le 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.
Très simple à résoudre !!
Je n'aime pas l'idée que la logique à l'intérieur de l'adaptateur soit un type de vue différent car, chaque fois, le type de vue est vérifié avant de retourner la vue. La solution ci-dessous évite des vérifications supplémentaires.
Il suffit d'ajouter la vue d'en-tête LinearLayout (verticale) + la vue Recyclerview + Footer à l'intérieur de 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 régulier
RecyclerView v = (RecyclerView) findViewById(...);
v.setNestedScrollingEnabled(false);
Cela perdra toutes les performances du VR et celui-ci essaiera de disposer tous les détenteurs de vues, quel que soit le layout_height
du VR
Recommandé pour la liste de petite taille comme le tiroir de navigation ou les paramètres, etc.
J'ai eu le même problème sur Lollipop et créé deux approches pour envelopper l'adaptateur Recyclerview
. L'un est assez facile à utiliser, mais je ne sais pas comment cela se comportera avec un jeu de données changeant. Parce que cela enveloppe votre adaptateur et que vous devez vous assurer d’appeler des méthodes telles que notifyDataSetChanged
sur le bon objet adaptateur.
L'autre ne devrait pas avoir de tels problèmes. Laissez simplement votre adaptateur habituel étendre la classe, implémenter les méthodes abstraites et vous devriez être prêt. Et les voici:
critiques
new HeaderRecyclerViewAdapterV1(new RegularAdapter());
RegularAdapter extends HeaderRecyclerViewAdapterV2
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 HeaderRecyclerViewAdapterV2
par moi-même et évoluer, tester et publier les modifications à venir.
EDIT: @OvidiuLatcu Oui, j'ai eu quelques problèmes. En fait, j'ai arrêté de décaler implicitement l'en-tête par position - (useHeader() ? 1 : 0)
et à la place j'ai créé une méthode publique int offsetPosition(int position)
. Parce que si vous définissez une variable OnItemTouchListener
sur Recyclerview, vous pouvez intercepter le contact, obtenir les coordonnées x, y du contact, trouver la vue enfant correspondante , puis appelez 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 une méthode facile pour surmonter cela. C'est pourquoi je décale maintenant les positions explicites lorsque je dois le faire avec mon code propre.
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 renvoyé par getItemCount dans votre adaptateur. Vous pouvez ensuite remplacer getItemViewType
dans 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)
createViewHolder
reçoit ensuite l'entier que vous avez renvoyé de getItemViewType
, ce qui vous permet de créer ou de configurer le détenteur de la 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 l'entier de position passé à bindViewHolder
.
Vous pouvez utiliser cette bibliothèque GitHub permettant d’ajouter en-tête et/ou 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 depuis Gradle:
compile 'com.mikhaellopez:hfrecyclerview:1.0.0'
Ceci est un résultat dans l'image:
EDIT:
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));
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 Gist ici: HeaderViewRecyclerAdapter.Java
La fonctionnalité principale que je souhaitais était une interface similaire à un ListView. Je souhaitais donc pouvoir gonfler les vues dans mon fragment et les ajouter à la RecyclerView
de onCreateView
. Pour ce faire, créez un HeaderViewRecyclerAdapter
en passant l'adaptateur à emballer, puis en appelant addHeaderView
et addFooterView
en transmettant vos vues gonflées. Puis définissez l’instance HeaderViewRecyclerAdapter
comme adaptateur sur le 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 modifier l’adaptateur enveloppé en laissant les en-têtes et les pieds de page intacts, le RecyclerView
étant averti du changement.
Vous pouvez utiliser viewtype pour résoudre ce problème, voici ma démo: https://github.com/yefengfreedom/RecyclerViewWithHeaderFooterLoadingEmptyViewErrorView
vous pouvez définir un mode d'affichage de 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. retrouver la méthode 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.override la méthode getItemCount
@Override
public int getItemCount() {
if (mMode == RecyclerViewMode.MODE_DATA) {
return mData.size() + mHeaders.size() + mFooters.size();
} else {
return 1;
}
}
4. remplacez la méthode onCreateViewHolder. créer un détenteur 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());
}
}
}
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 aussi que cette chose devrait être gérée par 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 le prolonger).
Je le teste toujours, mais vous pouvez l'essayer si vous voulez. S'il vous plaît laissez-moi savoir si vous trouvez des problèmes avec elle.
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:
D'abord, vous créez 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 le 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);
Je sais que je viens en retard, mais ce n'est que récemment que j'ai pu implémenter un tel "addHeader" sur l'adaptateur. Dans mon FlexibleAdapter projet, vous pouvez appeler setHeader
sur un élément sectionable , puis vous appelez showAllHeaders
. Si vous n'avez besoin que d'un seul en-tête, le premier élément devrait avoir cet 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 avoir un coup d'oeil: https://github.com/davideas/FlexibleAdapter .
Je voudrais juste ajouter une alternative à toutes ces implémentation HeaderRecyclerViewAdapter. CompoundAdapter:
https://github.com/negusoft/CompoundAdapter-Android
C'est une approche plus flexible, car vous pouvez créer un AdapterGroup à partir d'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.