web-dev-qa-db-fra.com

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

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!

114
MathieuMaree

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.

102
Reaz Murshed

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.

27
Nishant Shah

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

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.

25
seb

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.

10
Ian Newson

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:

Preview

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));
9
lopez.mikhael

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.

6
darnmason

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 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());
        }
    }
}
1
yefeng

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.

1
mato

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:

enter image description here

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);
0
Gustavo

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 .

0
Davideas

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.

0
blurkidi