Existe-t-il un équivalent addHeaderView pour RecyclerView?

290

Je cherche un équivalent à addHeaderView pour une vue recycleur. Fondamentalement, je veux qu'une image avec 2 boutons soit ajoutée comme en-tête à la liste. Existe-t-il une manière différente d'ajouter une vue d'en-tête à une vue de recycleur? Un exemple d'orientation serait utile

EDIT 2 (dispositions de fragments ajoutées):

Après avoir ajouté des instructions de journal, il semble que getViewType ne reçoive que la position 0. Cela conduit à onCreateView ne charger que la seule disposition:

10-26 16:32:53.766    5449-5449/co.testapp I/logger info Adapter-> getItemCount: 5
10-26 16:32:53.766    5449-5449/co.testapp I/logger info Adapter-> getItemCount: 5
10-26 16:32:53.766    5449-5449/co.testapp I/logger info Adapter-> getItemCount: 5
10-26 16:32:53.766    5449-5449/co.testapp I/logger info Adapter-> getItemCount: 5
10-26 16:32:53.766    5449-5449/co.testapp I/logger info Adapter-> getItemCount: 5
10-26 16:32:53.766    5449-5449/co.testapp I/logger info Adapter-> getItemCount: 5
10-26 16:32:53.766    5449-5449/co.testapp I/logger info Adapter-> getItemCount: 5
10-26 16:32:53.766    5449-5449/co.testapp I/logger info Adapter-> getItemCount: 5
10-26 16:32:53.766    5449-5449/co.testapp I/logger info Adapter-> getItemCount: 5
10-26 16:32:53.766    5449-5449/co.testapp I/logger info Adapter-> getItemCount: 5
10-26 16:32:53.766    5449-5449/co.testapp I/logger info Adapter-> getItemCount: 5
10-26 16:32:53.766    5449-5449/co.testapp I/logger info Adapter-> getItemViewType position: 0
10-26 16:32:53.766    5449-5449/co.testapp I/logger info Adapter-> getItemViewType position: 0
10-26 16:32:53.766    5449-5449/co.testapp I/logger info Adapter-> getItemViewType position: 0
10-26 16:32:53.766    5449-5449/co.testapp I/logger info Adapter-> onCreateViewHolder, viewtype: 0
10-26 16:32:53.766    5449-5449/co.testapp I/logger info Adapter-> onBindViewHolder, viewType: 0

La transition du fragment pour charger le CommentFragment:

@Override
public void onPhotoFeedItemClick(View view, int position) {
    if (fragmentManager == null)
        fragmentManager = getSupportFragmentManager();

FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

    if (view.getId() == R.id.button_comment){
        CommentFragment commentFragment = CommentFragment.newInstance("","", position);
        fragmentTransaction.add(R.id.main_activity, commentFragment,"comment_fragment_tag");
        fragmentTransaction.addToBackStack(Constants.TAG_COMMENTS);
        fragmentTransaction.commit();
    }
}

OnCreateView du fragment:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {

    View view = inflater.inflate(R.layout.fragment_comment, container, false);
    mRecyclerView = (RecyclerView) view.findViewById(R.id.list_recylclerview);
    mRecyclerView.setLayoutManager(new LinearLayoutManager(_context));
    mRecyclerView.setItemAnimator(new DefaultItemAnimator());
    mAdapter = new CommentAdapter(R.layout.row_list_comments, R.layout.row_header_comments, _context, comments);
    mRecyclerView.setAdapter(mAdapter);
    return view;
}

Le fragment contenant la recycleview:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    tools:context="co.testapp.fragments.CommentFragment"
    android:background="@color/white">
        <RelativeLayout
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:orientation="vertical">
            <android.support.v7.widget.RecyclerView
                xmlns:android="http://schemas.android.com/apk/res/android"
                android:id="@+id/list_recylclerview"
                android:layout_width="match_parent"
                android:layout_height="200dp" />
        </RelativeLayout>
</RelativeLayout>

La disposition des lignes de commentaires:

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent" android:layout_margin="10dp"
    android:background="@color/white">
    <!--Profile Picture-->
    <ImageView
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:id="@+id/profile_picture"
        android:background="@color/blue_testapp"/>
    <!--Name-->
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:text="First Name Last Name"
        android:textSize="16dp"
        android:textColor="@color/blue_testapp"
        android:id="@+id/name_of_poster"
        android:layout_toRightOf="@id/profile_picture"
        />
    <!--Comment-->
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:layout_marginTop="-5dp"
        android:text="This is a test comment"
        android:textSize="14dp"
        android:textColor="@color/black"
        android:id="@+id/comment"
        android:layout_below="@id/name_of_poster"
        android:layout_toRightOf="@id/profile_picture"/>
</RelativeLayout>

L'en-tête

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="300dp"
        android:id="@+id/header_photo"
        android:layout_gravity="center_horizontal"/>
</LinearLayout>

Le code adaptateur (merci à hister de m'avoir aidé à démarrer):

public class CommentAdapter extends RecyclerView.Adapter<ViewHolder>{

    private final int rowCardLayout;
    public static Context mContext;
    private final int headerLayout;
    private final String [] comments;
    private static final int HEADER = 0;
    private static final int OTHER = 0;

    public CommentAdapter(int rowCardLayout, int headerLayout, Context context, String [] comments) {
        this.rowCardLayout = rowCardLayout;
        this.mContext = context;
        this.comments = comments;
        this.headerLayout = headerLayout;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        logger.i("onCreateViewHolder, viewtype: " + i); //viewtype always returns 0 so OTHER layout is never inflated
        if (i == HEADER) {
            View v = LayoutInflater.from(viewGroup.getContext()).inflate(headerLayout, viewGroup, false);
            return new ViewHolderHeader(v);
        }
        else if (i == OTHER){
            View v = LayoutInflater.from(viewGroup.getContext()).inflate(rowCardLayout, viewGroup, false);
            return new ViewHolderComments(v);
        }
        else 
          throw new RuntimeException("Could not inflate layout");
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int i) {
        logger.i("onBindViewHolder, viewType: " + i);

        if (viewHolder instanceof ViewHolderComments)
            ((ViewHolderComments) viewHolder).comment.setText(comments[i].toString());
        if (viewHolder instanceof ViewHolderHeader)
           ((ViewHolderHeader) viewHolder).header.setImageResource(R.drawable.image2);
        else {
            logger.e("no instance of viewholder found");
        }
    }

    @Override
    public int getItemCount() {
        int count = comments.length + 1;
        logger.i("getItemCount: " + count);
        return count;
    }

    @Override
    public int getItemViewType(int position) {
        logger.i("getItemViewType position: " + position);
        if (position == HEADER)
            return HEADER;
        else
            return OTHER;
    }

    public static class ViewHolderComments extends RecyclerView.ViewHolder {
        public TextView comment;
        public ImageView image;

        public ViewHolderComments(View itemView) {
            super(itemView);
            comment = (TextView) itemView.findViewById(R.id.comment);
            image = (ImageView) itemView.findViewById(R.id.image);
        }
    }

    public static class ViewHolderHeader extends RecyclerView.ViewHolder {
        public final ImageView header;

        public ViewHolderHeader(View itemView){
            super(itemView);
            header = (ImageView) itemView.findViewById(R.id.header_photo);
        }
    }
}

En utilisant le code ci-dessus, seule la disposition d'en-tête est affichée car viewType est toujours 0. Il ressemble à ceci . Si je force l'autre disposition, cela ressemble à ceci :

ViciDroid
la source
Comme il s'agit d'un double de la question ici , j'ai posté ma réponse - bas :
seb
Une solution élégante: stackoverflow.com/questions/33579800/…
Ignacio Hagopian

Réponses:

457

Il n'y a pas de moyen simple listview.addHeaderView()mais vous pouvez y parvenir en ajoutant un type à votre adaptateur pour l'en-tête.

Voici un exemple

public class HeaderAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private static final int TYPE_HEADER = 0;
    private static final int TYPE_ITEM = 1;
    String[] data;

    public HeaderAdapter(String[] data) {
        this.data = data;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_ITEM) {
            //inflate your layout and pass it to view holder
            return new VHItem(null);
        } else if (viewType == TYPE_HEADER) {
            //inflate your layout and pass it to view holder
            return new VHHeader(null);
        }

        throw new RuntimeException("there is no type that matches the type " + viewType + " + make sure your using types correctly");
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof VHItem) {
            String dataItem = getItem(position);
            //cast holder to VHItem and set data
        } else if (holder instanceof VHHeader) {
            //cast holder to VHHeader and set data for header.
        }
    }

    @Override
    public int getItemCount() {
        return data.length + 1;
    }

    @Override
    public int getItemViewType(int position) {
        if (isPositionHeader(position))
            return TYPE_HEADER;

        return TYPE_ITEM;
    }

    private boolean isPositionHeader(int position) {
        return position == 0;
    }

    private String getItem(int position) {
        return data[position - 1];
    }

    class VHItem extends RecyclerView.ViewHolder {
        TextView title;

        public VHItem(View itemView) {
            super(itemView);
        }
    }

    class VHHeader extends RecyclerView.ViewHolder {
        Button button;

        public VHHeader(View itemView) {
            super(itemView);
        }
    }
}

lien vers l'essentiel -> ici

EC84B4
la source
2
tout semble correct et cela devrait fonctionner, mais faites en sorte que la vue du recycleur MATCH_PARENT vérifie si quelque chose change. si possible, faites en sorte que le recycleur affiche la racine de cette disposition (c'est bon pour les performances).
EC84B4
1
Le soleil d'une arme à feu, le nettoyage de la vue de recyclage et l'ajout d'un parent de match ont fonctionné ... Merci beaucoup pour l'aide! Je vous voterai dès que possible.
ViciDroid
14
private String getItem(int position) { return data[position + 1]; }Provoque un NPE. Étant donné que notre position a été augmentée d'une unité, en raison de l'en-tête, nous devons soustraire 1 pour obtenir l'élément correct de nos données []. private String getItem(int position) { return data[position - 1]; }
Tim Malseed
2
si votre grille n'est qu'une simple grille, utilisez simplement LinearLayoutManager et affichez-le sur une ligne. sa ligne de seau appelée j'ai vu que google l'utilise dans certaines de leurs applications.
EC84B4
4
@nsL vous pouvez utiliser cette approche et en ajouter setSpanSizeLookuppour GridLayoutManager, de sorte que votre en-tête prendra toutes les colonnes
Dmitry Zaytsev
62

Facile et réutilisable ItemDecoration

Les en- têtes statiques peuvent facilement être ajoutés avec ItemDecorationet sans autres modifications.

// add the decoration. done.
HeaderDecoration headerDecoration = new HeaderDecoration(/* init */);
recyclerView.addItemDecoration(headerDecoration);

La décoration est également réutilisable car il n'est pas nécessaire de modifier l'adaptateur ou du RecyclerViewtout.

L'exemple de code fourni ci-dessous nécessitera une vue à ajouter en haut qui peut simplement être gonflée comme tout le reste. Cela peut ressembler à ceci:

Échantillon d'en-tête de décoration

Pourquoi statique ?

Si vous n'avez qu'à afficher du texte et des images, cette solution est pour vous - il n'y a aucune possibilité d'interaction avec l'utilisateur comme des boutons ou des pagers, car elle sera simplement dessinée en haut de votre liste.

Gestion des listes vides

S'il n'y a pas de vue à décorer, la décoration ne sera pas dessinée. Vous devrez toujours gérer vous-même une liste vide. (Une solution de contournement possible consiste à ajouter un élément factice à l'adaptateur.)

Le code

Vous pouvez trouver le code source complet ici sur GitHub, y compris un Builderpour aider à l'initialisation du décorateur, ou utilisez simplement le code ci-dessous et fournissez vos propres valeurs au constructeur.

Assurez-vous de définir un correct layout_heightpour votre vue. par exemple match_parentpourrait ne pas fonctionner correctement.

public class HeaderDecoration extends RecyclerView.ItemDecoration {

    private final View mView;
    private final boolean mHorizontal;
    private final float mParallax;
    private final float mShadowSize;
    private final int mColumns;
    private final Paint mShadowPaint;

    public HeaderDecoration(View view, boolean scrollsHorizontally, float parallax, float shadowSize, int columns) {
        mView = view;
        mHorizontal = scrollsHorizontally;
        mParallax = parallax;
        mShadowSize = shadowSize;
        mColumns = columns;

        if (mShadowSize > 0) {
            mShadowPaint = new Paint();
            mShadowPaint.setShader(mHorizontal ?
                    new LinearGradient(mShadowSize, 0, 0, 0,
                            new int[]{Color.argb(55, 0, 0, 0), Color.argb(55, 0, 0, 0), Color.argb(3, 0, 0, 0)},
                            new float[]{0f, .5f, 1f},
                            Shader.TileMode.CLAMP) :
                    new LinearGradient(0, mShadowSize, 0, 0,
                            new int[]{Color.argb(55, 0, 0, 0), Color.argb(55, 0, 0, 0), Color.argb(3, 0, 0, 0)},
                            new float[]{0f, .5f, 1f},
                            Shader.TileMode.CLAMP));
        } else {
            mShadowPaint = null;
        }
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        // layout basically just gets drawn on the reserved space on top of the first view
        mView.layout(parent.getLeft(), 0, parent.getRight(), mView.getMeasuredHeight());

        for (int i = 0; i < parent.getChildCount(); i++) {
            View view = parent.getChildAt(i);
            if (parent.getChildAdapterPosition(view) == 0) {
                c.save();
                if (mHorizontal) {
                    c.clipRect(parent.getLeft(), parent.getTop(), view.getLeft(), parent.getBottom());
                    final int width = mView.getMeasuredWidth();
                    final float left = (view.getLeft() - width) * mParallax;
                    c.translate(left, 0);
                    mView.draw(c);
                    if (mShadowSize > 0) {
                        c.translate(view.getLeft() - left - mShadowSize, 0);
                        c.drawRect(parent.getLeft(), parent.getTop(), mShadowSize, parent.getBottom(), mShadowPaint);
                    }
                } else {
                    c.clipRect(parent.getLeft(), parent.getTop(), parent.getRight(), view.getTop());
                    final int height = mView.getMeasuredHeight();
                    final float top = (view.getTop() - height) * mParallax;
                    c.translate(0, top);
                    mView.draw(c);
                    if (mShadowSize > 0) {
                        c.translate(0, view.getTop() - top - mShadowSize);
                        c.drawRect(parent.getLeft(), parent.getTop(), parent.getRight(), mShadowSize, mShadowPaint);
                    }
                }
                c.restore();
                break;
            }
        }
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        if (parent.getChildAdapterPosition(view) < mColumns) {
            if (mHorizontal) {
                if (mView.getMeasuredWidth() <= 0) {
                    mView.measure(View.MeasureSpec.makeMeasureSpec(parent.getMeasuredWidth(), View.MeasureSpec.AT_MOST),
                            View.MeasureSpec.makeMeasureSpec(parent.getMeasuredHeight(), View.MeasureSpec.AT_MOST));
                }
                outRect.set(mView.getMeasuredWidth(), 0, 0, 0);
            } else {
                if (mView.getMeasuredHeight() <= 0) {
                    mView.measure(View.MeasureSpec.makeMeasureSpec(parent.getMeasuredWidth(), View.MeasureSpec.AT_MOST),
                            View.MeasureSpec.makeMeasureSpec(parent.getMeasuredHeight(), View.MeasureSpec.AT_MOST));
                }
                outRect.set(0, mView.getMeasuredHeight(), 0, 0);
            }
        } else {
            outRect.setEmpty();
        }
    }
}

Remarque: le projet GitHub est mon terrain de jeu personnel. Il n'est pas testé minutieusement, c'est pourquoi il n'y a pas encore de bibliothèque .

Qu'est ce que ça fait?

Un ItemDecorationdessin supplémentaire est ajouté à un élément d'une liste. Dans ce cas, une décoration est dessinée en haut du premier élément.

La vue est mesurée et présentée, puis elle est dessinée en haut du premier élément. Si un effet de parallaxe est ajouté, il sera également coupé aux limites correctes.

David Medenjak
la source
1
semble une solution élégante, mais, j'ai une question: quand dois-je appeler recyclerView.addItemDecoration?
Weibo
1
Merci, ça a bien fonctionné! Le seul inconvénient de l'utilisation de cette méthode est que vous devez avoir au moins 1 ligne dans RecyclerView.
Philip Giuliani
3
Comment onClickListener pour l'en-tête?
Prashant Kedia
1
Je reçois l'exception suivante: java.lang.NullPointerException: tentative d'invocation de la méthode virtuelle 'boolean android.support.v7.widget.RecyclerView $ ViewHolder.shouldIgnore ()' sur une référence d'objet null
Makalele
1
Existe-t-il un moyen d'accéder aux vues de la décoration? Je souhaite définir du texte de manière dynamique.
SMahdiS
44

N'hésitez pas à utiliser ma bibliothèque, disponible ici .

Il vous permet de créer un en-tête Viewpour tous ceux RecyclerViewqui utilisent LinearLayoutManagerou GridLayoutManageravec un simple appel de méthode.

entrez la description de l'image ici

Bartek Lipinski
la source
comment changer la hauteur de l'en-tête?
ingsaurabh
J'ai utilisé cela pour afficher l'en-tête en bas ou vous pouvez le dire en tant que pied de page, mais j'ai un problème que mon lorsque ma vue se charge pour la première fois, il affiche la dernière position et tous les éléments de la liste s'affichent dans l'ordre inverse. @blipinsk
Ronak Joshi
Pensez-vous que le problème que vous décrivez peut être lié à ceci: github.com/blipinsk/RecyclerViewHeader/issues/16 ?
Bartek Lipinski
Lipinski a retiré cette bibliothèque et suggère d'utiliser celle-ci à la place: github.com/Karumi/HeaderRecyclerView
radley
1
Je retire cette bibliothèque pour être exact. J'offre toujours un support modéré pour cela, mais en fait, je suggère d'aller avec l'adaptateur spécialisé à la place (par exemple celui de Karumi).
Bartek Lipinski
31

Je vais vous montrer comment créer un en-tête avec des articles dans une vue Recycler.Vue du recycleur avec en-tête

Étape 1- Ajoutez une dépendance dans votre fichier Gradle.

compile 'com.android.support:recyclerview-v7:23.2.0'
// CardView
compile 'com.android.support:cardview-v7:23.2.0'

Cardview est utilisé à des fins de décoration.

Étape 2 - Créez trois fichiers xml. Un pour l'activité principale. Deuxièmement pour la disposition des en-têtes. Troisième pour la disposition des éléments de liste.

activity_main.xml

<android.support.v7.widget.RecyclerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/my_recycler_view"
    android:scrollbars="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

header.xml

<android.support.v7.widget.CardView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:cardElevation="2dp">

    <TextView
        android:id="@+id/txtHeader"
        android:gravity="center"
        android:textColor="#000000"
        android:textSize="@dimen/abc_text_size_large_material"
        android:background="#DCDCDC"
        android:layout_width="match_parent"
        android:layout_height="50dp" />

</android.support.v7.widget.CardView>

list.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    xmlns:app="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:cardElevation="1dp">

        <TextView
            android:id="@+id/txtName"
            android:text="abc"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </android.support.v7.widget.CardView>

</LinearLayout>

Étape 3- Créez trois classes de bean.

Header.java

public class Header extends ListItem {
    private String header;

    public String getHeader() {
        return header;
    }
    public void setHeader(String header) {
        this.header = header;
    }
}

ContentItem.java

public class ContentItem extends ListItem {

    private String name;
    private String rollnumber;

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }

    public String getRollnumber() {
        return rollnumber;
    }

    public void setRollnumber(String rollnumber) {
        this.rollnumber = rollnumber;
    }
}

ListItem.java

public class ListItem {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    private int id;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

Étape 4 - Créez un adaptateur nommé MyRecyclerAdapter.java

public class MyRecyclerAdapter extends  RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private static final int TYPE_HEADER = 0;
    private static final int TYPE_ITEM = 1;

    //Header header;
    List<ListItem> list;
    public MyRecyclerAdapter(List<ListItem> headerItems) {
        this.list = headerItems;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        if (viewType == TYPE_HEADER) {
            View v = inflater.inflate(R.layout.header, parent, false);
            return  new VHHeader(v);
        } else {
            View v = inflater.inflate(R.layout.list, parent, false);
            return new VHItem(v);
        }
        throw new IllegalArgumentException();
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof VHHeader) {
           // VHHeader VHheader = (VHHeader)holder;
            Header  currentItem = (Header) list.get(position);
            VHHeader VHheader = (VHHeader)holder;
            VHheader.txtTitle.setText(currentItem.getHeader());
        } else if (holder instanceof VHItem) 
            ContentItem currentItem = (ContentItem) list.get(position);
            VHItem VHitem = (VHItem)holder;
            VHitem.txtName.setText(currentItem.getName());
        }
    }

    @Override
    public int getItemViewType(int position) {
        if (isPositionHeader(position))
            return TYPE_HEADER;
        return TYPE_ITEM;
    }

    private boolean isPositionHeader(int position) {
        return list.get(position) instanceof Header;
    }

    @Override
    public int getItemCount() {
        return list.size();
    }

    class VHHeader extends RecyclerView.ViewHolder{
        TextView txtTitle;
        public VHHeader(View itemView) {
            super(itemView);
            this.txtTitle = (TextView) itemView.findViewById(R.id.txtHeader);
        }
    }
    class VHItem extends RecyclerView.ViewHolder{
        TextView txtName;
        public VHItem(View itemView) {
            super(itemView);
            this.txtName = (TextView) itemView.findViewById(R.id.txtName);
        }
    }
}

Étape 5 - Dans MainActivity, ajoutez le code suivant:

public class MainActivity extends AppCompatActivity {
    RecyclerView recyclerView;
    List<List<ListItem>> arraylist;
    MyRecyclerAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        recyclerView = (RecyclerView)findViewById(R.id.my_recycler_view);
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        adapter = new MyRecyclerAdapter(getList());
        recyclerView.setLayoutManager(linearLayoutManager);
        recyclerView.setAdapter(adapter);
    }

    private ArrayList<ListItem> getList() {
        ArrayList<ListItem> arrayList = new ArrayList<>();
        for(int j = 0; j <= 4; j++) {
            Header header = new Header();
            header.setHeader("header"+j);
            arrayList.add(header);
            for (int i = 0; i <= 3; i++) {
                ContentItem item = new ContentItem();
                item.setRollnumber(i + "");
                item.setName("A" + i);
                arrayList.add(item);
            }
        }
        return arrayList;
    }

}

La fonction getList () génère dynamiquement les données des en-têtes et des éléments de liste.

Anshul Aggarwal
la source
L'une des meilleures réponses. Je l'ai utilisé dans navigationView for navigation menu
Saurabh Bhandari
Ceci est une bonne réponse simple - je ne pouvais pas penser à une astuce pour combiner facilement 2 éléments de données en un seul ListItem - l'héritage le rendait facile et réalisable - duh!
yura
8

Vous pouvez y parvenir en utilisant la bibliothèque SectionedRecyclerViewAdapter , elle a le concept de "Sections", où quelle section a un en-tête, un pied de page et un contenu (liste d'éléments). Dans votre cas, il se peut que vous n'ayez besoin que d'une section, mais vous pouvez en avoir plusieurs:

entrez la description de l'image ici

1) Créez une classe Section personnalisée:

class MySection extends StatelessSection {

    List<String> myList = Arrays.asList(new String[] {"Item1", "Item2", "Item3" });

    public MySection() {
        // call constructor with layout resources for this Section header, footer and items 
        super(R.layout.section_header, R.layout.section_footer,  R.layout.section_item);
    }

    @Override
    public int getContentItemsTotal() {
        return myList.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(myList.get(position));
    }
}

2) Créez un ViewHolder personnalisé pour les éléments:

class MyItemViewHolder extends RecyclerView.ViewHolder {

    private final TextView tvItem;

    public MyItemViewHolder(View itemView) {
        super(itemView);

        tvItem = (TextView) itemView.findViewById(R.id.tvItem);
    }
}

3) Configurez votre ReclyclerView avec le SectionedRecyclerViewAdapter

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

MySection mySection = new MySection();

// Add your Sections
sectionAdapter.addSection(mySection);

// Set up your RecyclerView with the SectionedRecyclerViewAdapter
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerView.setAdapter(sectionAdapter);
Gustavo
la source
c'est facile à utiliser, mais comment l'utiliser pour le défilement horizontal .. chaque fois que je change d'orientation en horizontal puis l'ensemble du recyclage change en défilement horizontal mais je veux que la partie de l'article soit en défilement horizontal uniquement ... aidez-moi à le faire
roshan posakya
6

Vous pouvez simplement placer votre en-tête et votre RecyclerView dans un NestedScrollView:

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

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

        <include
            layout="@layout/your_header"/>

        <android.support.v7.widget.RecyclerView
            android:id="@+id/list_recylclerview"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            />

    </LinearLayout>

</android.support.v4.widget.NestedScrollView>

Pour que le défilement fonctionne correctement, vous devez désactiver le défilement imbriqué sur votre RecyclerView:

myRecyclerView.setNestedScrollingEnabled(false);
Cristan
la source
3
ou utilisez android: nestedScrollingEnabled = "false"
radley
android: nestedScrollingEnabled = "false" nécessite le niveau d'api 21, ce qui est nul. Quoi qu'il en soit une excellente solution! De même, vous devez ajouter un autre pied de page ou peut-être même une deuxième vue d'ensemble du recyclage?
Makalele
47
Ne faites jamais ça. Cela semblera bien fonctionner. Mais ce qu'il fait, c'est qu'il passe le défilement à srollView. Donc recyclerView ne fait rien par lui-même. Pas de vue de recyclage. L'application se bloque si vous obtenez trop d'éléments dans votre recyclerView, car elle agit maintenant comme un simple scrollView.
VipulKumar
2
@ Le commentaire de VipulKumar est tout à fait vrai. Lorsque vous utilisez recyclerview dans une autre scrollview, il n'y aura pas de recyclage et cela créera tous les éléments.
VolkanSahin45
vous pouvez le faire si vos articles dans recyclerView sont inférieurs à 10. 😄
JIE WANG
5

L'API native n'a pas une telle fonctionnalité "addHeader", mais a le concept "addItem".

J'ai pu inclure cette fonctionnalité spécifique des en-têtes et des extensions pour les pieds de page également dans mon projet FlexibleAdapter . Je l'ai appelé en -têtes et pieds de page défilables .

Voici comment ils fonctionnent:

Les en-têtes et pieds de page défilables sont des éléments spéciaux qui défilent avec tous les autres, mais ils n'appartiennent pas aux éléments principaux (éléments commerciaux) et ils sont toujours gérés par l'adaptateur à côté des éléments principaux. Ces éléments sont constamment placés aux première et dernière positions.

entrez la description de l'image ici

Il y a beaucoup à dire à leur sujet, mieux vaut lire la page wiki détaillée .

De plus, le FlexibleAdapter vous permet de créer des en-têtes / sections, vous pouvez également les coller et des dizaines d'autres fonctionnalités comme des éléments extensibles, un défilement sans fin, des extensions d'interface utilisateur, etc. dans une seule bibliothèque!

Davideas
la source
4

Sur la base de ce message , 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 de mettre en œuvre un StaggeredGridLayoutManager à partir de zéro (ni même de l'étendre).

Je le teste encore, mais vous pouvez l'essayer si vous le souhaitez. Veuillez me faire savoir si vous rencontrez des problèmes avec celui-ci.

mato
la source
2

Il existe une autre solution qui couvre tous les cas d'utilisation ci-dessus: CompoundAdapter: https://github.com/negusoft/CompoundAdapter-android

Vous pouvez créer un groupe d'adaptateurs qui contient votre adaptateur tel quel, ainsi qu'un adaptateur avec un seul élément pour représenter l'en-tête. Le code est simple et lisible:

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

recyclerView.setAdapter(adapterGroup);

AdapterGroup permet également l'imbrication, donc pour un adaptateur avec des sections, vous pouvez créer un AdapterGroup par section. Ensuite, placez toutes les sections dans un AdapterGroup racine.

blurkidi
la source
1

HeaderView dépend du LayoutManager. Aucun des LayoutManagers par défaut ne le prend en charge et ne le fera probablement pas. HeaderView dans ListView crée beaucoup de complexité sans aucun avantage significatif.

Je suggère de créer une classe d'adaptateur de base qui ajoute des éléments pour les en-têtes, le cas échéant. N'oubliez pas de remplacer les méthodes notify * pour les compenser correctement selon que l'en-tête est présent ou non.

yigit
la source
Y a-t-il un exemple sur lequel vous pourriez me montrer qui utilise une vue de recycleur avec un adaptateur de base? Merci!
ViciDroid
Il y a un avantage vraiment significatif pour l'en-tête / pied de page dans une liste: vous pouvez le faire défiler hors de la vue. Voir cet exemple où le nombre de lignes visibles double presque dès que les pinguins sont sortis , je ne connais pas d'autre moyen de le faire, mais ListView.addHeaderViewou la réponse à cette question.
TWiStErRob
Je ne pense pas avoir bien compris. S'il s'agit du premier élément de l'adaptateur, pourquoi ne pas le faire défiler comme celui de l'exemple?
yigit
2
Vous ne pouvez pas remplacer les méthodes de notification *, car elles sont marquées comme définitives.
mato
hmm je n'ai pas vérifié ça désolé. Au lieu de cela, vous pouvez créer un wrapper d'adaptateur qui ajoute un observable à l'adaptateur encapsulé et distribue les événements décalés de lui-même.
yigit
1
First - extends RecyclerView.Adapter<RecyclerView.ViewHolder>

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

Après - Remplacez la méthode getItemViewTpe *** Plus important

@Override
public int getItemViewType(int position) {
    return position;
}

méthode onCreateViewHolder

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.menu_item, parent, false);
    View header = LayoutInflater.from(parent.getContext()).inflate(R.layout.menu_header_item, parent, false);
    Log.d("onCreateViewHolder", String.valueOf(viewType));

    if (viewType == 0) {
        return new MenuLeftHeaderViewHolder(header, onClickListener);
    } else {
        return new MenuLeftViewHolder(view, onClickListener);
    }
}

méthode onBindViewHolder

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    if (position == 0) {
        MenuHeaderViewHolder menuHeaderViewHolder = (MenuHeaderViewHolder) holder;
        menuHeaderViewHolder.mTitle.setText(sMenuTitles[position]);
        menuHeaderViewHolder.mImage.setImageResource(sMenuImages[position]);
    } else {
        MenuViewHolder menuLeftViewHolder = (MenuLeftViewHolder) holder;
        menuViewHolder.mTitle.setText(sMenuTitles[position]);
        menuViewHolder.mImage.setImageResource(sMenuImages[position]);
    }
}

in finish implémente la classe ViewHolders statique

public static class MenuViewHolder extends RecyclerView.ViewHolder 

public static class MenuLeftHeaderViewHolder extends RecyclerView.ViewHolder 
vrbsm
la source
1

ici une décoration pour le recyclage

public class HeaderItemDecoration extends RecyclerView.ItemDecoration {

private View customView;

public HeaderItemDecoration(View view) {
    this.customView = view;
}

@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    super.onDraw(c, parent, state);
    customView.layout(parent.getLeft(), 0, parent.getRight(), customView.getMeasuredHeight());
    for (int i = 0; i < parent.getChildCount(); i++) {
        View view = parent.getChildAt(i);
        if (parent.getChildAdapterPosition(view) == 0) {
            c.save();
            final int height = customView.getMeasuredHeight();
            final int top = view.getTop() - height;
            c.translate(0, top);
            customView.draw(c);
            c.restore();
            break;
        }
    }
}

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    if (parent.getChildAdapterPosition(view) == 0) {
        customView.measure(View.MeasureSpec.makeMeasureSpec(parent.getMeasuredWidth(), View.MeasureSpec.AT_MOST),
                View.MeasureSpec.makeMeasureSpec(parent.getMeasuredHeight(), View.MeasureSpec.AT_MOST));
        outRect.set(0, customView.getMeasuredHeight(), 0, 0);
    } else {
        outRect.setEmpty();
    }
}
}      
Trunks ssj
la source
1

J'ai fait une implémentation basée sur celle de @ hister à mes fins personnelles, mais en utilisant l'héritage.

Je cache les mécanismes de détails d'implémentation (comme ajouter 1 à itemCount, soustraire 1 à position) dans une super classe abstraite HeadingableRecycleAdapter, en implémentant les méthodes requises de Adapter like onBindViewHolder, getItemViewTypeet getItemCount, en rendant ces méthodes finales et en fournissant de nouvelles méthodes avec une logique cachée au client:

  • onAddViewHolder(RecyclerView.ViewHolder holder, int position),
  • onCreateViewHolder(ViewGroup parent),
  • itemCount()

Voici la HeadingableRecycleAdapterclasse et un client. J'ai laissé la disposition de l'en-tête un peu codée en dur car elle correspond à mes besoins.

public abstract class HeadingableRecycleAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private static final int HEADER_VIEW_TYPE = 0;

    @LayoutRes
    private int headerLayoutResource;
    private String headerTitle;
    private Context context;

    public HeadingableRecycleAdapter(@LayoutRes int headerLayoutResourceId, String headerTitle, Context context) {
        this.headerLayoutResource = headerLayoutResourceId;
        this.headerTitle = headerTitle;
        this.context = context;
    }

    public Context context() {
        return context;
    }

    @Override
    public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == HEADER_VIEW_TYPE) {
            return new HeaderViewHolder(LayoutInflater.from(context).inflate(headerLayoutResource, parent, false));
        }
        return onCreateViewHolder(parent);
    }

    @Override
    public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        int viewType = getItemViewType(position);
        if (viewType == HEADER_VIEW_TYPE) {
            HeaderViewHolder vh = (HeaderViewHolder) holder;
            vh.bind(headerTitle);
        } else {
            onAddViewHolder(holder, position - 1);
        }
    }

    @Override
    public final int getItemViewType(int position) {
        return position == 0 ? 0 : 1;
    }

    @Override
    public final int getItemCount() {
        return itemCount() + 1;
    }

    public abstract int itemCount();

    public abstract RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent);

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

}



@PerActivity
public class IngredientsAdapter extends HeadingableRecycleAdapter {
    public static final String TITLE = "Ingredients";
    private List<Ingredient> itemList;


    @Inject
    public IngredientsAdapter(Context context) {
        super(R.layout.layout_generic_recyclerview_cardified_header, TITLE, context);
    }

    public void setItemList(List<Ingredient> itemList) {
        this.itemList = itemList;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
        return new ViewHolder(LayoutInflater.from(context()).inflate(R.layout.item_ingredient, parent, false));
    }

    @Override
    public void onAddViewHolder(RecyclerView.ViewHolder holder, int position) {
        ViewHolder vh = (ViewHolder) holder;
        vh.bind(itemList.get(position));
    }

    @Override
    public int itemCount() {
        return itemList == null ? 0 : itemList.size();
    }

    private String getQuantityFormated(double quantity, String measure) {
        if (quantity == (long) quantity) {
            return String.format(Locale.US, "%s %s", String.valueOf(quantity), measure);
        } else {
            return String.format(Locale.US, "%.1f %s", quantity, measure);
        }
    }


    class ViewHolder extends RecyclerView.ViewHolder {
        @BindView(R.id.text_ingredient)
        TextView txtIngredient;

        ViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }

        void bind(Ingredient ingredient) {
            String ingredientText = ingredient.getIngredient();
            txtIngredient.setText(String.format(Locale.US, "%s %s ", getQuantityFormated(ingredient.getQuantity(),
                    ingredient.getMeasure()), Character.toUpperCase(ingredientText.charAt(0)) +
                    ingredientText
                            .substring(1)));
        }
    }
}
alexpfx
la source
1

Peut-être envelopper l'en-tête et la vue d'ensemble du recyclage dans une présentation de coordinateur :

<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.design.widget.AppBarLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:elevation="0dp">

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

</android.support.design.widget.AppBarLayout>

<android.support.v7.widget.RecyclerView
    android:id="@+id/list"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior" />

lenhuy2106
la source
Le problème avec cela est qu'il permet toujours de faire défiler la hauteur de l'AppBarLayout, même si la liste est plus courte que l'écran. Pour résoudre ce problème, vous pouvez faire comme expliqué ici
Daniel López Lacalle
0

Http://alexzh.com/tutorials/multiple-row-layouts-using-recyclerview/ vous aidera probablement . Il utilise uniquement RecyclerView et CardView. Voici un adaptateur:

public class DifferentRowAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private List<CityEvent> mList;
    public DifferentRowAdapter(List<CityEvent> list) {
        this.mList = list;
    }
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view;
        switch (viewType) {
            case CITY_TYPE:
                view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_city, parent, false);
                return new CityViewHolder(view);
            case EVENT_TYPE:
                view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_event, parent, false);
                return new EventViewHolder(view);
        }
        return null;
    }
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        CityEvent object = mList.get(position);
        if (object != null) {
            switch (object.getType()) {
                case CITY_TYPE:
                    ((CityViewHolder) holder).mTitle.setText(object.getName());
                    break;
                case EVENT_TYPE:
                    ((EventViewHolder) holder).mTitle.setText(object.getName());
                    ((EventViewHolder) holder).mDescription.setText(object.getDescription());
                    break;
            }
        }
    }
    @Override
    public int getItemCount() {
        if (mList == null)
            return 0;
        return mList.size();
    }
    @Override
    public int getItemViewType(int position) {
        if (mList != null) {
            CityEvent object = mList.get(position);
            if (object != null) {
                return object.getType();
            }
        }
        return 0;
    }
    public static class CityViewHolder extends RecyclerView.ViewHolder {
        private TextView mTitle;
        public CityViewHolder(View itemView) {
            super(itemView);
            mTitle = (TextView) itemView.findViewById(R.id.titleTextView);
        }
    }
    public static class EventViewHolder extends RecyclerView.ViewHolder {
        private TextView mTitle;
        private TextView mDescription;
        public EventViewHolder(View itemView) {
            super(itemView);
            mTitle = (TextView) itemView.findViewById(R.id.titleTextView);
            mDescription = (TextView) itemView.findViewById(R.id.descriptionTextView);
        }
    }
}

Et voici une entité:

public class CityEvent {
    public static final int CITY_TYPE = 0;
    public static final int EVENT_TYPE = 1;
    private String mName;
    private String mDescription;
    private int mType;
    public CityEvent(String name, String description, int type) {
        this.mName = name;
        this.mDescription = description;
        this.mType = type;
    }
    public String getName() {
        return mName;
    }
    public void setName(String name) {
        this.mName = name;
    }
    public String getDescription() {
        return mDescription;
    }
    public void setDescription(String description) {
        this.mDescription = description;
    }
    public int getType() {
        return mType;
    }
    public void setType(int type) {
        this.mType = type;
    }
}
CoolMind
la source
0

vous pouvez créer addHeaderView et utiliser

adapter.addHeaderView(View).

Ce code crée le addHeaderViewpour plus d'un en-tête. les en-têtes doivent avoir:

android:layout_height="wrap_content"

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private static final int TYPE_ITEM = -1;
    public class MyViewSHolder extends RecyclerView.ViewHolder {
        public MyViewSHolder (View view) {
            super(view);
        }
        // put you code. for example:
        View mView;
        ...
    }

    public class ViewHeader extends RecyclerView.ViewHolder {
        public ViewHeader(View view) {
            super(view);
        }
    }

    private List<View> mHeaderViews = new ArrayList<>();
    public void addHeaderView(View headerView) {
        mHeaderViews.add(headerView);
    }

    @Override
    public int getItemCount() {
       return ... + mHeaderViews.size();
    }

    @Override
    public int getItemViewType(int position) {
        if (mHeaderViews.size() > position) {
            return position;
        }

        return TYPE_ITEM;
    }
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType != TYPE_ITEM) {
            //inflate your layout and pass it to view holder
            return new ViewHeader(mHeaderViews.get(viewType));
        }
        ...
    }
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int basePosition1) {
        if (holder instanceof ViewHeader) {
            return;
        }
        int basePosition = basePosition1 -  mHeaderViews.size();
        ...
    }
}
iftach barshem
la source
0

Cela fait quelques années, mais juste au cas où quelqu'un lirait ça plus tard ...

En utilisant le code ci-dessus, seule la disposition d'en-tête est affichée car viewType est toujours 0.

Le problème est dans la déclaration constante:

private static final int HEADER = 0;
private static final int OTHER = 0;  <== bug 

Si vous les déclarez tous les deux à zéro, vous obtiendrez toujours zéro!

kiwi
la source
0

J'ai mis en œuvre la même approche que celle proposée par la réponse EC84B4 , mais j'ai résumé RecycleViewAdapter et le rendre facilement réutilisable au moyen d'interfaces.

Donc, pour utiliser mon approche, vous devez ajouter les classes de base et les interfaces suivantes à votre projet:

1) Interface fournissant des données pour l'adaptateur (collecte de type générique T et paramètres supplémentaires (si nécessaire) de type générique P)

public interface IRecycleViewListHolder<T,P>{
            P getAdapterParameters();
            T getItem(int position);
            int getSize();
    }

2) Usine de reliure de vos articles (en-tête / article):

public interface IViewHolderBinderFactory<T,P> {
        void bindView(RecyclerView.ViewHolder holder, int position,IRecycleViewListHolder<T,P> dataHolder);
}

3) Usine pour viewHolders (en-tête / articles):

public interface IViewHolderFactory {
    RecyclerView.ViewHolder provideInflatedViewHolder(int viewType, LayoutInflater layoutInflater,@NonNull ViewGroup parent);
}

4) Classe de base pour l'adaptateur avec en-tête:

public class RecycleViewHeaderBased<T,P> extends RecyclerView.Adapter<RecyclerView.ViewHolder>{

    public final static int HEADER_TYPE = 1;
    public final static int ITEM_TYPE = 0;
    private final IRecycleViewListHolder<T, P> dataHolder;
    private final IViewHolderBinderFactory<T,P> binderFactory;
    private final IViewHolderFactory viewHolderFactory;

    public RecycleViewHeaderBased(IRecycleViewListHolder<T,P> dataHolder, IViewHolderBinderFactory<T,P> binderFactory, IViewHolderFactory viewHolderFactory) {
        this.dataHolder = dataHolder;
        this.binderFactory = binderFactory;
        this.viewHolderFactory = viewHolderFactory;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
        return viewHolderFactory.provideInflatedViewHolder(viewType,layoutInflater,parent);
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
            binderFactory.bindView(holder, position,dataHolder);
    }

    @Override
    public int getItemViewType(int position) {
        if(position == 0)
            return HEADER_TYPE;
        return ITEM_TYPE;
    }

    @Override
    public int getItemCount() {
        return dataHolder.getSize()+1;
    }
}

Exemple d'utilisation :

1) Implémentation d'IRecycleViewListHolder:

public class AssetTaskListData implements IRecycleViewListHolder<Map.Entry<Integer, Integer>, GroupedRecord> {
    private List<Map.Entry<Integer, Integer>> assetCountList;
    private GroupedRecord record;

    public AssetTaskListData(Map<Integer, Integer> assetCountListSrc, GroupedRecord record) {
        this.assetCountList =  new ArrayList<>();
        for(Object  entry: assetCountListSrc.entrySet().toArray()){
            Map.Entry<Integer,Integer> entryTyped = (Map.Entry<Integer,Integer>)entry;
            assetCountList.add(entryTyped);
        }
        this.record = record;
    }

    @Override
    public GroupedRecord getAdapterParameters() {
        return record;
    }

    @Override
    public Map.Entry<Integer, Integer> getItem(int position) {
        return assetCountList.get(position-1);
    }

    @Override
    public int getSize() {
        return assetCountList.size();
    }
}

2) Implémentation IViewHolderBinderFactory:

public class AssetTaskListBinderFactory implements IViewHolderBinderFactory<Map.Entry<Integer, Integer>, GroupedRecord> {
    @Override
    public void bindView(RecyclerView.ViewHolder holder, int position, IRecycleViewListHolder<Map.Entry<Integer, Integer>, GroupedRecord> dataHolder) {
        if (holder instanceof AssetItemViewHolder) {
            Integer assetId = dataHolder.getItem(position).getKey();
            Integer assetCount = dataHolder.getItem(position).getValue();
            ((AssetItemViewHolder) holder).bindItem(dataHolder.getAdapterParameters().getRecordId(), assetId, assetCount);
        } else {
            ((AssetHeaderViewHolder) holder).bindItem(dataHolder.getAdapterParameters());
        }
    }
}

3) Implémentation IViewHolderFactory:

public class AssetTaskListViewHolderFactory implements IViewHolderFactory {
    private IPropertyTypeIconMapper iconMapper;
    private ITypeCaster caster;

    public AssetTaskListViewHolderFactory(IPropertyTypeIconMapper iconMapper, ITypeCaster caster) {
        this.iconMapper = iconMapper;
        this.caster = caster;
    }

    @Override
    public RecyclerView.ViewHolder provideInflatedViewHolder(int viewType, LayoutInflater layoutInflater, @NonNull ViewGroup parent) {
        if (viewType == RecycleViewHeaderBased.HEADER_TYPE) {
            AssetBasedHeaderItemBinding item = DataBindingUtil.inflate(layoutInflater, R.layout.asset_based_header_item, parent, false);
            return new AssetHeaderViewHolder(item.getRoot(), item, caster);
        }
        AssetBasedListItemBinding item = DataBindingUtil.inflate(layoutInflater, R.layout.asset_based_list_item, parent, false);
        return new AssetItemViewHolder(item.getRoot(), item, iconMapper, parent.getContext());
    }
}

4) Adaptateur dérivant

public class AssetHeaderTaskListAdapter extends RecycleViewHeaderBased<Map.Entry<Integer, Integer>, GroupedRecord> {
   public AssetHeaderTaskListAdapter(IRecycleViewListHolder<Map.Entry<Integer, Integer>, GroupedRecord> dataHolder,
                                      IViewHolderBinderFactory binderFactory,
                                      IViewHolderFactory viewHolderFactory) {
        super(dataHolder, binderFactory, viewHolderFactory);
    }
}

5) Instancier la classe d'adaptateur:

private void setUpAdapter() {
        Map<Integer, Integer> objectTypesCountForGroupedTask = groupedTaskRepository.getObjectTypesCountForGroupedTask(this.groupedRecordId);
        AssetTaskListData assetTaskListData = new AssetTaskListData(objectTypesCountForGroupedTask, getGroupedRecord());
        adapter = new AssetHeaderTaskListAdapter(assetTaskListData,new AssetTaskListBinderFactory(),new AssetTaskListViewHolderFactory(iconMapper,caster));
        assetTaskListRecycler.setAdapter(adapter);
    }

PS : AssetItemViewHolder, AssetBasedListItemBinding, etc. mes propres structures d'application qui devraient être échangées par les vôtres, à vos propres fins.

Sergei Yendiyarov
la source