Android 5.0 - Kopf- / Fußzeile zu einer RecyclerView hinzufügen


122

Ich habe einen Moment lang RecyclerViewerfolglos versucht, einen Header zu einem hinzuzufügen .

Das habe ich bisher bekommen:

@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);

   ...
}

Das LayoutManagerscheint das Objekt zu sein, das die Disposition der RecyclerViewGegenstände handhabt. Da ich keine finden konnte , addHeaderView(View view)Verfahren, entschied ich mich mit dem Thema LayoutManager‚s addView(View view, int position)Methode und meine Kopfansicht in der ersten Position als Kopf zu handeln hinzuzufügen.

Und hier wird es hässlicher:

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)

Nachdem ich mehrere NullPointerExceptionsVersuche unternommen hatte, die addView(View view)zu verschiedenen Zeitpunkten der Aktivitätserstellung aufzurufen (ich habe auch versucht, die Ansicht hinzuzufügen, sobald alles eingerichtet ist, sogar die Daten des Adapters), wurde mir klar, dass ich keine Ahnung habe, ob dies der richtige Weg ist (und das auch) sieht nicht so aus).

PS: Auch eine Lösung, die das GridLayoutManagerzusätzlich zum bewältigen könnte , LinearLayoutManagerwäre sehr dankbar!



Das Problem liegt im Adaptercode. Es bedeutet, dass Sie in der Funktion onCreateViewHolder irgendwie den Null-Viewholder zurückgeben.
Neo

Es gibt eine gute Möglichkeit, Header zu StaggeredGridLayout stackoverflow.com/questions/42202735/… hinzuzufügen
Aleksey Timoshchenko

Antworten:


119

Ich musste eine Fußzeile zu meiner hinzufügen RecyclerViewund hier teile ich mein Code-Snippet, da ich dachte, dass es nützlich sein könnte. Bitte überprüfen Sie die Kommentare im Code, um den Gesamtfluss besser zu verstehen.

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
        }
    }
}

Das obige Code-Snippet fügt der Fußzeile eine Fußzeile hinzu RecyclerView. Sie können dieses GitHub-Repository überprüfen, um die Implementierung des Hinzufügens von Kopf- und Fußzeilen zu überprüfen.


2
Funktioniert gut. Dies sollte als richtige Antwort markiert werden.
Naga Mallesh Maddali

1
Genau das habe ich getan. Aber was ist, wenn ich möchte, dass mein RecyclerView eine gestaffelte Liste anpasst? Das erste Element (der Header) wird ebenfalls versetzt. :(
Neon Warge

Dies ist ein Tutorial darüber, wie Sie Ihre RecyclerViewDaten dynamisch füllen können. Sie können jedes Ihrer Elemente steuern RecyclerView. Bitte schauen Sie im Code-Abschnitt nach einem Arbeitsprojekt. Hoffe es könnte helfen. github.com/comeondude/dynamic-recyclerview/wiki
Reaz Murshed

2
int getItemViewType (int position)- Geben Sie den Ansichtstyp des Elements zum Zwecke des Ansichtsrecyclings an Position zurück. Die Standardimplementierung dieser Methode gibt 0 zurück, wobei ein einzelner Ansichtstyp für den Adapter angenommen wird. Im Gegensatz zu ListViewAdaptern müssen Typen nicht zusammenhängend sein. Erwägen Sie die Verwendung von ID-Ressourcen, um Elementansichtstypen eindeutig zu identifizieren. - Aus der Dokumentation. developer.android.com/reference/android/support/v7/widget/…
Reaz Murshed

3
So viel Handarbeit für etwas, das die Leute immer machen wollen. Ich kann es nicht glauben ...
JohnyTex

28

Sehr einfach zu lösen !!

Ich mag die Idee nicht, Logik im Adapter als einen anderen Ansichtstyp zu haben, da jedes Mal nach dem Ansichtstyp gesucht wird, bevor die Ansicht zurückgegeben wird. Die folgende Lösung vermeidet zusätzliche Überprüfungen.

Fügen Sie einfach die LinearLayout-Kopfansicht (vertikal) + die Recyclingansicht + die Fußzeile in android.support.v4.widget.NestedScrollView hinzu .

Überprüfen Sie dies heraus:

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

Fügen Sie diese Codezeile für einen reibungslosen Bildlauf hinzu

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

Dadurch geht die gesamte Leistung des Wohnmobils verloren, und das Wohnmobil versucht, alle Ansichtsinhaber unabhängig layout_heightvom Wohnmobil auszulegen

Empfohlen für kleine Listen wie Navigationsschubladen oder Einstellungen usw.


1
Arbeitete für mich ziemlich einfach
Hitesh Sahu

1
Dies ist eine sehr einfache Methode, um einer Recycler-Ansicht Kopf- und Fußzeilen hinzuzufügen, wenn sich die Kopf- und Fußzeilen nicht innerhalb einer Liste wiederholen müssen.
user1841702

34
Dies ist eine sehr einfache Möglichkeit, alle Vorteile zu RecyclerViewverlieren - Sie verlieren das eigentliche Recycling und die damit verbundene Optimierung.
Marcin Koziński

1
Ich habe diesen Code ausprobiert, das Scrollen funktioniert nicht richtig ... es wurde zu langsam gescrollt. Bitte schlagen Sie vor, ob Sie etwas dafür tun könnten
Nibha Jain

2
Wenn Sie eine verschachtelte Bildlaufansicht verwenden, wird die gesamte Ansicht in der Recyclingansicht zwischengespeichert. Wenn die Größe der Recyclingansicht größer ist, wird die Bildlaufzeit erhöht und die Ladezeit erhöht. Ich schlage vor, diesen Code nicht zu verwenden
Tushar Saha

25

Ich hatte das gleiche Problem mit Lollipop und habe zwei Ansätze zum Umwickeln des RecyclerviewAdapters entwickelt. Eines ist ziemlich einfach zu bedienen, aber ich bin mir nicht sicher, wie es sich bei einem sich ändernden Datensatz verhalten wird. Weil es Ihren Adapter umschließt und Sie sicherstellen müssen, dass Sie Methoden wie notifyDataSetChangedauf dem richtigen Adapterobjekt aufrufen .

Der andere sollte solche Probleme nicht haben. Lassen Sie einfach Ihren regulären Adapter die Klasse erweitern, implementieren Sie die abstrakten Methoden und Sie sollten bereit sein. Und hier sind sie:

Kern

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

}

Feedback und Gabeln geschätzt. Ich werde es selbst nutzen HeaderRecyclerViewAdapterV2und die Änderungen in Zukunft weiterentwickeln, testen und veröffentlichen.

EDIT : @OvidiuLatcu Ja, ich hatte einige Probleme. Eigentlich habe ich aufgehört, den Header implizit um zu versetzen, position - (useHeader() ? 1 : 0)und stattdessen eine öffentliche Methode int offsetPosition(int position)dafür erstellt. Denn wenn Sie eine OnItemTouchListenerauf Recyclerview setzen, können Sie die Berührung abfangen, die x-, y-Koordinaten der Berührung abrufen , die entsprechende untergeordnete Ansicht finden und dann anrufen, recyclerView.getChildPosition(...)und Sie erhalten immer die nicht versetzte Position im Adapter! Dies ist ein Mangel im RecyclerView-Code. Ich sehe keine einfache Methode, um dies zu überwinden. Aus diesem Grund versetze ich jetzt die Positionen explizit, wenn ich muss, durch meinen eigenen Code.


sieht gut aus ! Haben Sie Probleme damit? oder können wir es sicher benutzen? : D
Ovidiu Latcu

1
@OvidiuLatcu siehe Beitrag
September

In diesen Implementierungen haben Sie anscheinend angenommen, dass die Anzahl der Kopf- und Fußzeilen jeweils nur 1 beträgt.
Rishabhmhjn

@seb Version 2 funktioniert wie Charme !! Das einzige, was ich ändern musste, ist die Bedingung, um die Fußzeile sowohl für die onBindViewHolder- als auch für die getItemViewType-Methode abzurufen. Das Problem ist, dass wenn Sie die Position mit position == getBasicItemCount () erhalten, sie nicht für die tatsächliche letzte Position, sondern für die letzte Position - 1 zurückgegeben wird. Am Ende wurde die Fußzeile dort platziert (nicht unten). Wir haben das Problem behoben, indem wir die Bedingung in Position == getBasicItemCount () + 1 geändert haben, und es hat großartig funktioniert!
mmark

@seb Version 2 funktioniert so toll. vielen Dank. ich benutze es. Eine kleine Sache, die ich vorschlage, ist das Hinzufügen des Schlüsselworts 'final' für die Überschreibungsfunktion.
RyanShao

10

Ich habe dies nicht versucht, aber ich würde einfach 1 (oder 2, wenn Sie sowohl eine Kopf- als auch eine Fußzeile möchten) zu der von getItemCount in Ihrem Adapter zurückgegebenen Ganzzahl hinzufügen. Sie können dann getItemViewTypeIhren Adapter überschreiben , um eine andere Ganzzahl zurückzugeben, wenn i==0: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemViewType(int)

createViewHolderAnschließend wird die von Ihnen zurückgegebene Ganzzahl übergeben getItemViewType, sodass Sie den Ansichtsinhaber für die Header-Ansicht anders erstellen oder konfigurieren können: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html# createViewHolder (android.view.ViewGroup , int)

Vergessen Sie nicht, eine von der an übergebenen Positionszahl zu subtrahieren bindViewHolder.


Ich denke nicht, dass dies die Aufgabe von recyclerview ist. Die Aufgabe von Recyclerviews besteht darin, Ansichten einfach zu recyceln. Das Schreiben eines Layoutmanagers mit einer Kopf- oder Fußzeilenimplementierung ist der richtige Weg.
IZI_Shadow_IZI

Vielen Dank an IanNewson für Ihre Antwort. Erstens scheint diese Lösung zu funktionieren, auch wenn sie nur verwendet wird getItemViewType(int position) { return position == 0 ? 0 : 1; }(es RecyclerViewgibt keine getViewTypeCount()Methode). Andererseits stimme ich @IZI_Shadow_IZI zu, ich habe wirklich das Gefühl, dass der LayoutManager derjenige sein sollte, der diese Art von Dingen handhabt. Irgendeine andere Idee?
MathieuMaree

@VieuMa Sie haben wahrscheinlich beide Recht, aber ich weiß derzeit nicht, wie das geht, und ich war mir ziemlich sicher, dass meine Lösung funktionieren würde. Eine suboptimale Lösung ist besser als keine Lösung, wie Sie sie zuvor hatten.
Ian Newson

@VieuMa Wenn Sie die Kopf- und Fußzeile in den Adapter abstrahieren, sollten beide angeforderten Layouttypen verarbeitet werden. Wenn Sie Ihren eigenen Layout-Manager schreiben, müssen Sie beide Layouttypen neu implementieren.
Ian Newson

Erstellen Sie einfach einen Adapter, der jeden Adapter umschließt und die Header-Ansicht bei Index 0 unterstützt. HeaderView in der Listenansicht erstellt viele Randfälle und der Mehrwert ist minimal, da die Lösung mit einem Wrapper-Adapter einfach ist.
Yigit

9

Sie können diese GitHub- Bibliothek verwenden, um auf einfachste Weise Kopf- und / oder Fußzeilen in Ihre RecyclerView einzufügen .

Sie müssen Ihrem Projekt eine HFRecyclerView- Bibliothek hinzufügen, oder Sie können sie auch aus Gradle herunterladen :

compile 'com.mikhaellopez:hfrecyclerview:1.0.0'

Dies ist ein Ergebnis im Bild:

Vorschau

BEARBEITEN:

Wenn Sie mit dieser Bibliothek nur einen Rand oben und / oder unten hinzufügen möchten: SimpleItemDecoration :

int offsetPx = 10;
recyclerView.addItemDecoration(new StartOffsetItemDecoration(offsetPx));
recyclerView.addItemDecoration(new EndOffsetItemDecoration(offsetPx));

Diese Bibliothek fügt die Ansicht in der Kopfzeile in LinearLayoutManager ordnungsgemäß hinzu, aber ich möchte die Ansicht in GridLayoutManager als Kopfzeile festlegen, die über die gesamte Bildschirmbreite erfolgt. Kann es mit dieser Bibliothek möglich sein.
ved

Nein, mit dieser Bibliothek können Sie das erste und letzte Element einer recyclerView beim Anpassen ändern (RecyclerView.Adapter). Sie können diesen Adapter problemlos für eine GridView verwenden. Ich denke, diese Bibliothek ermöglicht es Ihnen, das zu tun, was Sie wollen.
lopez.mikhael

6

Am Ende habe ich meinen eigenen Adapter implementiert, um jeden anderen Adapter zu verpacken und Methoden zum Hinzufügen von Kopf- und Fußzeilenansichten bereitzustellen.

Hier wurde ein Kernstück erstellt: HeaderViewRecyclerAdapter.java

Die Hauptfunktion, die ich wollte, war eine ähnliche Oberfläche wie eine ListView, daher wollte ich die Ansichten in meinem Fragment aufblasen und zur hinzufügen können RecyclerView InonCreateView . Dazu erstellen Sie eine HeaderViewRecyclerAdapterÜbergabe des zu verpackenden Adapters und rufen Ihre aufgeblasenen Ansichten auf addHeaderViewund addFooterViewübergeben sie. Stellen Sie dann die HeaderViewRecyclerAdapterInstanz als Adapter auf dem ein RecyclerView.

Eine zusätzliche Anforderung war, dass ich in der Lage sein musste, Adapter einfach auszutauschen, während die Kopf- und Fußzeilen beibehalten wurden. Ich wollte nicht mehrere Adapter mit mehreren Instanzen dieser Kopf- und Fußzeilen haben. Sie können also anrufen setAdapter, um den umschlossenen Adapter zu ändern, wobei die Kopf- und Fußzeilen intakt bleibenRecyclerView über die Änderung informiert werden.


3

Mein "halte es einfach dumm" Weg ... es verschwendet einige Ressourcen, ich weiß, aber es ist mir egal, da mein Code einfach bleibt, also ... 1) füge Fußzeile mit Sichtbarkeit GONE zu deinem item_layout hinzu

  <LinearLayout
        android:id="@+id/footer"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:orientation="vertical"
        android:visibility="gone">
    </LinearLayout>

2) Setzen Sie es dann auf dem letzten Element sichtbar

public void onBindViewHolder(ChannelAdapter.MyViewHolder holder, int position) {
        boolean last = position==data.size()-1;
        //....
        holder.footer.setVisibility(View.GONE);
        if (last && showFooter){
            holder.footer.setVisibility(View.VISIBLE);
        }
    }

Machen Sie das Gegenteil für den Header


1

Basierend auf der Lösung von @ seb habe ich eine Unterklasse von RecyclerView.Adapter erstellt, die eine beliebige Anzahl von Kopf- und Fußzeilen unterstützt.

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

Obwohl es eine Lösung zu sein scheint, denke ich auch, dass dieses Ding vom LayoutManager verwaltet werden sollte. Leider brauche ich es jetzt und ich habe keine Zeit, einen StaggeredGridLayoutManager von Grund auf neu zu implementieren (oder sogar zu erweitern).

Ich teste es immer noch, aber Sie können es ausprobieren, wenn Sie möchten. Bitte lassen Sie mich wissen, wenn Sie Probleme damit haben.


1

Sie können viewtype verwenden, um dieses Problem zu lösen. Hier ist meine Demo: https://github.com/yefengfreedom/RecyclerViewWithHeaderFooterLoadingEmptyViewErrorView

  1. Sie können einen Anzeigemodus für die Recycler-Ansicht definieren:

    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. Überschreiben Sie die getItemViewType-Motte

 @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. Überschreiben Sie die Methode getItemCount

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

4. Überschreiben Sie die onCreateViewHolder-Methode. Erstellen Sie einen Ansichtshalter mit 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.Überschreiben Sie die onBindViewHolder-Methode. Daten nach viewType binden

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

Was ist, wenn Ihr Link in Zukunft unterbrochen wird?
Gopal Singh Sirvi

Gute Frage. Ich werde meine Antwort bearbeiten und meinen Code hier
posten

1

Mit der Bibliothek SectionedRecyclerViewAdapter können Sie Ihre Elemente in Abschnitte gruppieren und jedem Abschnitt eine Kopfzeile hinzufügen, wie in der Abbildung unten dargestellt:

Geben Sie hier die Bildbeschreibung ein

Zuerst erstellen Sie Ihre Abschnittsklasse:

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

Anschließend richten Sie die RecyclerView mit Ihren Abschnitten ein und ändern die SpanSize der Header mit einem 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

Ich weiß, dass ich zu spät komme, aber erst kürzlich konnte ich einen solchen "addHeader" für den Adapter implementieren. In meinem FlexibleAdapter- Projekt können Sie setHeaderein Sectionable- Element aufrufen und dann aufrufenshowAllHeaders . Wenn Sie nur 1 Kopfzeile benötigen, sollte das erste Element die Kopfzeile haben. Wenn Sie dieses Element löschen, wird der Header automatisch mit dem nächsten verknüpft.

Fußzeilen sind leider (noch) nicht abgedeckt.

Mit dem FlexibleAdapter können Sie viel mehr als nur Kopfzeilen / Abschnitte erstellen. Sie sollten wirklich einen Blick darauf werfen: https://github.com/davideas/FlexibleAdapter .


0

Ich würde nur eine Alternative zu all diesen HeaderRecyclerViewAdapter-Implementierungen hinzufügen. CompoundAdapter:

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

Dies ist ein flexiblerer Ansatz, da Sie aus Adaptern eine Adaptergruppe erstellen können. Verwenden Sie für das Header-Beispiel Ihren Adapter so wie er ist, zusammen mit einem Adapter, der ein Element für den Header enthält:

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

recyclerView.setAdapter(adapterGroup);

Es ist ziemlich einfach und lesbar. Sie können komplexere Adapter einfach nach demselben Prinzip implementieren.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.