Android 5.0 - Fügen Sie Kopf- / Fußzeile zu einem RecyclerView

? MathieuMaree @ | Original: StackOverFlow

Ich verbrachte einen Moment Zeit, um herauszufinden, einen Weg, um eine Kopfzeile auf eine RecyclerView hinzuzufügen, ohne Erfolg . Dies ist, was ich bisher habe :

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

   ...
}

Der Layoutmanager scheint das Objekt der Handhabung der Anordnung der RecyclerView Gegenstände. Als ich konnte keine addHeaderView(View view) Methode nicht finden, habe ich beschlossen, mit der Layoutmanager -Methode addView(View view, int position) zu gehen und meine Kopfsicht in der ersten Position, wie ein Kopf handeln hinzuzufügen.

Aaand das ist, wo die Dinge 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 sie mehrere NullPointerExceptions versucht, als die addView(View view) zu verschiedenen Zeitpunkten des Activity -Erstellung (auch versucht, indem die Ansicht einmal alles eingerichtet ist, auch des Adapters Datensatz ), erkannte ich, ich habe keine Ahnung, ob dies der richtige Weg,, es zu tun ( und es sieht nicht so zu sein).

Kann mir jemand helfen ? Oder eine Vorstellung / neue Richtung zu erkunden mindestens geben?

Vielen Dank im Voraus!

VieuMa

PS: Auch eine Lösung, die die GridLayoutManager neben der LinearLayoutManager umgehen konnte wäre wirklich dankbar !



Top 5 Antwort

1Ian Newson @

Ich habe nicht versucht, aber ich würde fügen Sie einfach 1 (oder 2, wenn Sie sowohl eine Kopf- und Fußzeile werden soll) auf die ganze Zahl von getItemCount in den Adapter zurück. Anschließend können Sie überschreiben getItemViewType in den Adapter, um eine andere ganze Zahl zurück, wenn i==0 : https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemViewType(int)

createViewHolder Wird dann übergeben Sie die Ganzzahl von getItemViewType zurück, so dass Sie die Ansicht Halter anders für die Kopfsicht zu erstellen oder zu konfigurieren: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#createViewHolder(android.view.ViewGroup, Int)

Vergessen Sie nicht, einen von der Position Ganzzahl geführt, um bindViewHolder subtrahieren .

2seb @

Ich hatte das gleiche Problem und erstellt zwei Ansätze, um den Adapter zu wickeln. Einer ist ziemlich einfach zu bedienen, aber ich bin mir nicht sicher, wie es mit einer sich verändernden Datensatzes verhalten. Weil es wickelt den Adapter, und Sie müssen auf den richtigen Adapter - Objekt machen Sie sicher, dass Sie Methoden wie als notifyDataSetChanged .

Die anderen sollten nicht solche Probleme . Lassen Sie Ihre regelmäßigen Adapter erweitern die Klasse, setzen die abstrakten Methoden und Sie sollten bereit sein. Und hier sind sie :

gists

https://gist.github.com/sebnapi/a2596ec3d1730ea47986 usage new HeaderRecyclerViewAdapterV1(new RegularAdapter()); https://gist.github.com/sebnapi/fde648c17616d9d3bcde usage RegularAdapter extends HeaderRecyclerViewAdapterV2

HeaderRecyclerViewAdapterV1

import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;

/**
 * Created by sebnapi on 08.11.14.
 * <p/>
 * This is a Plug-and-Play Approach for adding a Header or Footer to
 * a RecyclerView backed list
 * <p/>
 * Just wrap your regular adapter like this
 * <p/>
 * new HeaderRecyclerViewAdapterV1(new RegularAdapter())
 * <p/>
 * Let RegularAdapter implement HeaderRecyclerView, FooterRecyclerView or both
 * and you are ready to go.
 * <p/>
 * I'm absolutely not sure how this will behave with changes in the dataset.
 * You can always wrap a fresh adapter and make sure to not change the old one or
 * use my other approach.
 * <p/>
 * With the other approach you need to let your Adapter extend HeaderRecyclerViewAdapterV2
 * (and therefore change potentially more code) but possible omit these shortcomings.
 * <p/>
 * TOTALLY UNTESTED - USE WITH CARE - HAVE FUN :)
 */
public class HeaderRecyclerViewAdapterV1 extends RecyclerView.Adapter {
    private static final int TYPE_HEADER = Integer.MIN_VALUE;
    private static final int TYPE_FOOTER = Integer.MIN_VALUE + 1;
    private static final int TYPE_ADAPTEE_OFFSET = 2;

    private final RecyclerView.Adapter mAdaptee;


    public HeaderRecyclerViewAdapterV1(RecyclerView.Adapter adaptee) {
        mAdaptee = adaptee;
    }

    public RecyclerView.Adapter getAdaptee() {
        return mAdaptee;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_HEADER && mAdaptee instanceof HeaderRecyclerView) {
            return ((HeaderRecyclerView) mAdaptee).onCreateHeaderViewHolder(parent, viewType);
        } else if (viewType == TYPE_FOOTER && mAdaptee instanceof FooterRecyclerView) {
            return ((FooterRecyclerView) mAdaptee).onCreateFooterViewHolder(parent, viewType);
        }
        return mAdaptee.onCreateViewHolder(parent, viewType - TYPE_ADAPTEE_OFFSET);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (position == 0 && holder.getItemViewType() == TYPE_HEADER && useHeader()) {
            ((HeaderRecyclerView) mAdaptee).onBindHeaderView(holder, position);
        } else if (position == mAdaptee.getItemCount() && holder.getItemViewType() == TYPE_FOOTER && useFooter()) {
            ((FooterRecyclerView) mAdaptee).onBindFooterView(holder, position);
        } else {
            mAdaptee.onBindViewHolder(holder, position - (useHeader() ? 1 : 0));
        }
    }

    @Override
    public int getItemCount() {
        int itemCount = mAdaptee.getItemCount();
        if (useHeader()) {
            itemCount += 1;
        }
        if (useFooter()) {
            itemCount += 1;
        }
        return itemCount;
    }

    private boolean useHeader() {
        if (mAdaptee instanceof HeaderRecyclerView) {
            return true;
        }
        return false;
    }

    private boolean useFooter() {
        if (mAdaptee instanceof FooterRecyclerView) {
            return true;
        }
        return false;
    }

    @Override
    public int getItemViewType(int position) {
        if (position == 0 && useHeader()) {
            return TYPE_HEADER;
        }
        if (position == mAdaptee.getItemCount() && useFooter()) {
            return TYPE_FOOTER;
        }
        if (mAdaptee.getItemCount() >= Integer.MAX_VALUE - TYPE_ADAPTEE_OFFSET) {
            new IllegalStateException("HeaderRecyclerViewAdapter offsets your BasicItemType by " + TYPE_ADAPTEE_OFFSET + ".");
        }
        return mAdaptee.getItemViewType(position) + TYPE_ADAPTEE_OFFSET;
    }


    public static interface HeaderRecyclerView {
        public RecyclerView.ViewHolder onCreateHeaderViewHolder(ViewGroup parent, int viewType);

        public void onBindHeaderView(RecyclerView.ViewHolder holder, int position);
    }

    public static interface FooterRecyclerView {
        public RecyclerView.ViewHolder onCreateFooterViewHolder(ViewGroup parent, int viewType);

        public void onBindFooterView(RecyclerView.ViewHolder holder, int position);
    }

}

HeaderRecyclerViewAdapterV2

import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;

/**
 * Created by sebnapi on 08.11.14.
 * <p/>
 * If you extend this Adapter you are able to add a Header, a Footer or both
 * by a similar ViewHolder pattern as in RecyclerView.
 * <p/>
 * If you want to omit changes to your class hierarchy you can try the Plug-and-Play
 * approach HeaderRecyclerViewAdapterV1.
 * <p/>
 * Don't override (Be careful while overriding)
 * - onCreateViewHolder
 * - onBindViewHolder
 * - getItemCount
 * - getItemViewType
 * <p/>
 * You need to override the abstract methods introduced by this class. This class
 * is not using generics as RecyclerView.Adapter make yourself sure to cast right.
 * <p/>
 * TOTALLY UNTESTED - USE WITH CARE - HAVE FUN :)
 */
public abstract class HeaderRecyclerViewAdapterV2 extends RecyclerView.Adapter {
    private static final int TYPE_HEADER = Integer.MIN_VALUE;
    private static final int TYPE_FOOTER = Integer.MIN_VALUE + 1;
    private static final int TYPE_ADAPTEE_OFFSET = 2;

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_HEADER) {
            return onCreateHeaderViewHolder(parent, viewType);
        } else if (viewType == TYPE_FOOTER) {
            return onCreateFooterViewHolder(parent, viewType);
        }
        return onCreateBasicItemViewHolder(parent, viewType - TYPE_ADAPTEE_OFFSET);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (position == 0 && holder.getItemViewType() == TYPE_HEADER) {
            onBindHeaderView(holder, position);
        } else if (position == getBasicItemCount() && holder.getItemViewType() == TYPE_FOOTER) {
            onBindFooterView(holder, position);
        } else {
            onBindBasicItemView(holder, position - (useHeader() ? 1 : 0));
        }
    }

    @Override
    public int getItemCount() {
        int itemCount = getBasicItemCount();
        if (useHeader()) {
            itemCount += 1;
        }
        if (useFooter()) {
            itemCount += 1;
        }
        return itemCount;
    }

    @Override
    public int getItemViewType(int position) {
        if (position == 0 && useHeader()) {
            return TYPE_HEADER;
        }
        if (position == getBasicItemCount() && useFooter()) {
            return TYPE_FOOTER;
        }
        if (getBasicItemType(position) >= Integer.MAX_VALUE - TYPE_ADAPTEE_OFFSET) {
            new IllegalStateException("HeaderRecyclerViewAdapter offsets your BasicItemType by " + TYPE_ADAPTEE_OFFSET + ".");
        }
        return getBasicItemType(position) + TYPE_ADAPTEE_OFFSET;
    }

    public abstract boolean useHeader();

    public abstract RecyclerView.ViewHolder onCreateHeaderViewHolder(ViewGroup parent, int viewType);

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

    public abstract boolean useFooter();

    public abstract RecyclerView.ViewHolder onCreateFooterViewHolder(ViewGroup parent, int viewType);

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

    public abstract RecyclerView.ViewHolder onCreateBasicItemViewHolder(ViewGroup parent, int viewType);

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

    public abstract int getBasicItemCount();

    /**
     * make sure you don't use [Integer.MAX_VALUE-1, Integer.MAX_VALUE] as BasicItemViewType
     *
     * @param position
     * @return
     */
    public abstract int getBasicItemType(int position);

}

Rückmeldung und Gabeln geschätzt. Ich werde verwenden HeaderRecyclerViewAdapterV2 von mir selbst und zu entwickeln, zu testen und veröffentlichen Sie die Änderungen in der Zukunft.

EDIT: OvidiuLatcu Ja, ich hatte ein paar Probleme . Eigentlich habe ich aufgehört Verrechnung der Kopfzeile implizit durch position - (useHeader() ? 1 : 0) und stattdessen schuf eine öffentliche Methode int offsetPosition(int position) dafür. Denn wenn Sie setzen einen OnItemTouchListener auf Recyclerview, können Sie den Touch abfangen, bekommen Sie die x, y Koordinaten der Berührung, finden Sie das nach Unteransicht und rufen Sie dann recyclerView.getChildPosition(...), und Sie werden immer die Nicht offsetted Position in der Adapter! Dies ist eine shortcomming im RecyclerView -Code, sehe ich nicht, eine einfache Methode, dies zu überwinden . Deshalb habe ich jetzt Offset die Positionen explizit, wenn ich meine eigenen Code durch .

3darnmason @

Ich landete Umsetzung meiner eigenen Versorgung eines anderen Adapter wickeln und bieten Methoden, um Kopf- und Fußzeilen Ansichten hinzufügen .

Erstellt einen Kern hier: https://gist.github.com/darnmason/7bbf8beae24fe7296c8a

Das Hauptmerkmal ich wollte, war eine ähnliche Schnittstelle zu einem Listview, deshalb wollte ich in der Lage sein, um die Ansichten in meiner Fragment aufzublasen und fügen Sie sie der RecyclerView in onCreateView sein . Dies wird durch die Schaffung eines HeaderViewRecyclerAdapter Weitergabe der Adapter gewickelt werden, und mit der Aufforderung addHeaderView und addFooterView, indem Sie Ihre aufgeblähten Blick getan . Stellen Sie dann den HeaderViewRecyclerAdapter Instanz wie der Adapter auf dem RecyclerView .

Eine zusätzliche Anforderung war, dass ich in der Lage, einfach die Swap -Adapter, während die Kopf- und Fußzeilen, wollte ich nicht mehrere Adapter mit mehreren Instanzen dieser Kopf- und Fußzeilen haben. So können Sie anrufen setAdapter, um den Adapter eingewickelt so dass die Kopf- und Fußzeilen intakt, mit dem RecyclerView werden über die Änderung informiert zu ändern.

4mato @

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

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

Obwohl es scheint, um eine Lösung zu sein, ich denke auch, dieses Ding sollte vom Layoutmanager verwaltet werden. Leider muss ich es jetzt und ich habe keine Zeit, um ein von Grund auf neu zu implementieren StaggeredGridLayoutManager ( nicht einmal zu verlängern von ihm ) .

Ich bin immer noch testen, aber Sie können es versuchen, wenn Sie wollen . Informieren Sie mich bitte, wenn Sie irgendwelche Probleme mit ihm zu finden.