Android 5.0 - Aggiungi intestazione / piè di pagina a RecyclerView


123

Ho passato un momento a cercare di trovare un modo per aggiungere un'intestazione a un RecyclerView, senza successo.

Questo è quello che ho ottenuto finora:

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

   ...
}

L' LayoutManagersembra essere la gestione della disposizione degli oggetti RecyclerViewprodotti. Siccome non riuscivo a trovare alcun addHeaderView(View view)metodo, ho deciso di utilizzare LayoutManager'saddView(View view, int position) metodo e di aggiungere il mio vista intestazione nella prima posizione di agire come un colpo di testa.

E qui è dove le cose si fanno più brutte:

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)

Dopo aver ottenuto diversi NullPointerExceptionstentativi di chiamare il fileaddView(View view) in diversi momenti della creazione dell'attività (ho anche provato ad aggiungere la vista una volta che tutto è stato impostato, anche i dati dell'adattatore), mi sono reso conto che non ho idea se questo sia il modo giusto per farlo (e non sembra essere).

PS: Inoltre, una soluzione che potesse gestire GridLayoutManagerin aggiunta alla LinearLayoutManagersarebbe davvero apprezzata!


dare un'occhiata a questo stackoverflow.com/a/26573338/2127203
EC84B4

Il problema è nel codice dell'adattatore. Significa che in qualche modo stai restituendo un viewholder nullo nella funzione onCreateViewHolder.
Neo

C'è un buon modo per aggiungere un'intestazione a StaggeredGridLayout stackoverflow.com/questions/42202735/...
Aleksey Timoshchenko

Risposte:


121

Ho dovuto aggiungere un piè di pagina al mio RecyclerViewe qui sto condividendo il mio frammento di codice perché pensavo potesse essere utile. Si prega di controllare i commenti all'interno del codice per una migliore comprensione del flusso complessivo.

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

Lo snippet di codice precedente aggiunge un piè di pagina al file RecyclerView. Puoi controllare questo repository GitHub per verificare l'implementazione dell'aggiunta di intestazione e piè di pagina.


2
Funziona bene. Questo dovrebbe essere contrassegnato come risposta corretta.
Naga Mallesh Maddali

1
Questo è esattamente quello che ho fatto. Ma cosa succede se volessi che il mio RecyclerView si adegui all'elenco sfalsato? Anche il primo elemento (l'intestazione) sarà sfalsato. :(
Neon Warge

Questo è un tutorial su come popolare RecyclerViewdinamicamente il tuo file . Puoi avere il controllo su ciascuno degli elementi del tuo file RecyclerView. Si prega di guardare la sezione del codice per un progetto funzionante. Spero possa aiutare. github.com/comeondude/dynamic-recyclerview/wiki
Reaz Murshed

2
int getItemViewType (int position)- Restituire il tipo di visualizzazione dell'elemento in posizione ai fini del riciclo della visualizzazione. L'implementazione predefinita di questo metodo restituisce 0, presupponendo un unico tipo di visualizzazione per l'adattatore. A differenza degli ListViewadattatori, i tipi non devono essere contigui. Prendi in considerazione l'utilizzo di risorse ID per identificare in modo univoco i tipi di visualizzazione degli elementi. - Dalla documentazione. developer.android.com/reference/android/support/v7/widget/…
Reaz Murshed,

3
Tanto lavoro manuale per qualcosa che le persone vogliono fare sempre. Non posso crederci ...
JohnyTex

28

Molto semplice da risolvere !!

Non mi piace l'idea di avere la logica all'interno dell'adattatore come un tipo di visualizzazione diverso perché ogni volta che controlla il tipo di visualizzazione prima di restituire la visualizzazione. La soluzione sotto riportata evita controlli aggiuntivi.

Basta aggiungere la vista intestazione LinearLayout (verticale) + recyclerview + vista piè di pagina all'interno di android.support.v4.widget.NestedScrollView .

Controllalo:

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

Aggiungi questa riga di codice per uno scorrimento fluido

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

Ciò perderà tutte le prestazioni RV e RV proverà a disporre tutti i possessori di visualizzazione indipendentemente dal layout_heightRV

Uso consigliato per l'elenco di piccole dimensioni come il cassetto di navigazione o le impostazioni, ecc.


1
Ha funzionato per me in modo abbastanza semplice
Hitesh Sahu

1
Questo è un modo molto semplice per aggiungere intestazioni e piè di pagina a una visualizzazione riciclatore quando le intestazioni e i piè di pagina non devono essere ripetuti all'interno di un elenco.
user1841702

34
Questo è un modo molto semplice per perdere tutti i vantaggi che RecyclerViewcomporta: perdi il riciclaggio effettivo e l'ottimizzazione che porta.
Marcin Koziński

1
Ho provato questo codice, lo scorrimento non funziona correttamente ... lo scorrimento è diventato troppo lento .. per favore suggerisco se potessi fare qualcosa per quello
Nibha Jain

2
l'utilizzo di scrollview annidato farà sì che recyclerview memorizzi nella cache tutta la vista e, se la dimensione della vista di riciclatore è maggiore, lo scorrimento e il tempo di caricamento aumenteranno. Suggerisco di non usare questo codice
Tushar Saha

25

Ho avuto lo stesso problema su Lollipop e ho creato due approcci per avvolgere l' Recyclerviewadattatore. Uno è abbastanza facile da usare, ma non sono sicuro di come si comporterà con un set di dati che cambia. Perché avvolge l'adattatore e devi assicurarti di chiamare metodi come notifyDataSetChangedsull'oggetto adattatore giusto.

L'altro non dovrebbe avere questi problemi. Lascia che il tuo normale adattatore estenda la classe, implementa i metodi astratti e dovresti essere pronto. Ed eccole qui:

GIST

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 e forchette apprezzati. useròHeaderRecyclerViewAdapterV2 da solo ed evolverò, testerò e i cambiamenti in futuro.

EDIT : @OvidiuLatcu Sì, ho avuto dei problemi. In realtà ho smesso di compensare implicitamente l'intestazione position - (useHeader() ? 1 : 0)e invece ho creato un metodo pubblico int offsetPosition(int position)per esso. Perché se imposti un OnItemTouchListenersu Recyclerview, puoi intercettare il tocco, ottenere le coordinate x, y del tocco, trovare la vista bambino corrispondente e quindi chiamare recyclerView.getChildPosition(...)e otterrai sempre la posizione non spostata nell'adattatore! Questa è una lacuna nel codice RecyclerView, non vedo un metodo facile per superarla. È per questo che ora compensare le posizioni esplicito quando ho bisogno di mio proprio codice.


sembra buono ! avere problemi con esso? o possiamo tranquillamente usarlo? : D
Ovidiu Latcu

1
@OvidiuLatcu vedi post
seb

In queste implementazioni, sembra che tu abbia assunto che il numero di intestazioni e piè di pagina sia solo 1 ciascuno?
rishabhmhjn

@seb versione 2 funziona come un fascino !! l'unica cosa che dovevo modificare è la condizione per ottenere il piè di pagina su entrambi i metodi onBindViewHolder e getItemViewType. Il problema è che se ottieni la posizione usando position == getBasicItemCount () non restituirà true per l'ultima posizione effettiva, ma l'ultima posizione - 1. Ha finito per posizionare FooterView lì (non in fondo). Lo abbiamo risolto cambiando la condizione in position == getBasicItemCount () + 1 e ha funzionato alla grande!
mmark

La versione 2 di @seb funziona alla grande. grazie mille. lo sto usando. una piccola cosa che suggerisco è di aggiungere la parola chiave "finale" per la funzione di override.
RyanShao

10

Non l'ho provato, ma aggiungerei semplicemente 1 (o 2, se vuoi sia un'intestazione che un piè di pagina) all'intero restituito da getItemCount nel tuo adattatore. È quindi possibile eseguire l'override getItemViewTypenell'adattatore per restituire un numero intero diverso quando i==0: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemViewType(int)

createViewHolderviene quindi passato il numero intero da cui si è restituiti getItemViewType, consentendo di creare o configurare il titolare della visualizzazione in modo diverso per la visualizzazione dell'intestazione: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html# createViewHolder (android.view.ViewGroup , int)

Non dimenticare di sottrarre uno dal numero intero di posizione passato a bindViewHolder.


Non credo che sia il lavoro di Recyclerview. Il compito di Recyclerviews è semplicemente riciclare le visualizzazioni. Scrivere un layoutmanager con un'implementazione di intestazione o piè di pagina è la strada da percorrere.
IZI_Shadow_IZI

Grazie @IanNewson per la tua risposta. Innanzitutto, questa soluzione sembra funzionare, anche solo usando getItemViewType(int position) { return position == 0 ? 0 : 1; }( RecyclerViewnon ha un getViewTypeCount()metodo). D'altra parte, sono d'accordo con @IZI_Shadow_IZI, ho davvero la sensazione che LayoutManager dovrebbe essere quello che gestisce questo genere di cose. Qualche altra idea?
MathieuMaree

@VieuMa probabilmente avete ragione entrambi, ma al momento non so come farlo ed ero abbastanza sicuro che la mia soluzione avrebbe funzionato. Una soluzione non ottimale è meglio di nessuna soluzione, che è quella che avevi prima.
Ian Newson

@VieuMa inoltre, astrarre l'intestazione e il piè di pagina nell'adattatore significa che dovrebbe gestire entrambi i tipi di layout richiesti, scrivere il proprio gestore di layout significa reimplementarlo per entrambi i tipi di layout.
Ian Newson

basta creare un adattatore che avvolga qualsiasi adattatore e aggiunga il supporto per la visualizzazione dell'intestazione all'indice 0. HeaderView in listview crea molti casi limite e il valore aggiunto è minimo dato che è un problema facile da risolvere utilizzando un adattatore wrapper.
yigit

9

È possibile utilizzare questa libreria GitHub che consente di aggiungere intestazione e / o piè di pagina nel tuo RecyclerView nel modo più semplice possibile.

Devi aggiungere la libreria HFRecyclerView nel tuo progetto o puoi anche prenderlo da Gradle:

compile 'com.mikhaellopez:hfrecyclerview:1.0.0'

Questo è un risultato nell'immagine:

Anteprima

MODIFICARE:

Se vuoi solo aggiungere un margine in alto e / o in basso con questa libreria: SimpleItemDecoration :

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

Questa libreria aggiunge la vista correttamente sull'intestazione in LinearLayoutManager ma desidero impostare la vista come intestazione in GridLayoutManager, che si svolge su tutta la larghezza dello schermo. Può è possibile con questa libreria.
ved

No, questa libreria consente di modificare il primo e l'ultimo elemento di un recyclerView durante l'adattamento (RecyclerView.Adapter). Puoi usare questo adattatore per un GridView senza problemi. Quindi penso che questa libreria ti permetta di fare quello che vuoi.
lopez.mikhael

6

Ho finito per implementare il mio adattatore per avvolgere qualsiasi altro adattatore e fornire metodi per aggiungere visualizzazioni di intestazione e piè di pagina.

Creato un riassunto qui: HeaderViewRecyclerAdapter.java

La caratteristica principale che volevo era un'interfaccia simile a ListView, quindi volevo essere in grado di aumentare le visualizzazioni nel mio frammento e aggiungerle al file RecyclerViewin onCreateView. Questo viene fatto creando un HeaderViewRecyclerAdapterpassaggio dell'adattatore da includere nel wrapping e chiamando addHeaderViewe addFooterViewpassando le visualizzazioni gonfiate. Quindi impostare l' HeaderViewRecyclerAdapteristanza come adattatore suRecyclerView .

Un requisito aggiuntivo era che dovevo essere in grado di sostituire facilmente gli adattatori mantenendo le intestazioni e i piè di pagina, non volevo avere più adattatori con più istanze di queste intestazioni e piè di pagina. Quindi puoi chiamare setAdapterper cambiare l'adattatore avvolto lasciando intatti le intestazioni e i piè di pagina, con la RecyclerViewnotifica della modifica.


3

il mio modo "mantienilo semplice stupido" ... spreca alcune risorse, lo so, ma non mi interessa perché il mio codice è semplice quindi ... 1) aggiungi il piè di pagina con visibilità GONE al tuo item_layout

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

2) quindi impostalo visibile sull'ultimo elemento

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

fare il contrario per l'intestazione


1

Sulla base della soluzione di @ seb, ho creato una sottoclasse di RecyclerView.Adapter che supporta un numero arbitrario di intestazioni e piè di pagina.

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

Anche se sembra essere una soluzione, penso anche che questa cosa dovrebbe essere gestita dal LayoutManager. Sfortunatamente, ne ho bisogno ora e non ho tempo per implementare uno StaggeredGridLayoutManager da zero (né estenderlo da esso).

Lo sto ancora provando, ma puoi provarlo se vuoi. Per favore fatemi sapere se trovate problemi con esso.


1

Puoi usare viewtype per risolvere questo problema, ecco la mia demo: https://github.com/yefengfreedom/RecyclerViewWithHeaderFooterLoadingEmptyViewErrorView

  1. puoi definire alcune modalità di visualizzazione della vista riciclatore:

    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.override il mothod getItemViewType

 @Override
public int getItemViewType(int position) {
    if (mMode == RecyclerViewMode.MODE_LOADING) {
        return RecyclerViewMode.MODE_LOADING;
    }
    if (mMode == RecyclerViewMode.MODE_ERROR) {
        return RecyclerViewMode.MODE_ERROR;
    }
    if (mMode == RecyclerViewMode.MODE_EMPTY) {
        return RecyclerViewMode.MODE_EMPTY;
    }
    //check what type our position is, based on the assumption that the order is headers > items > footers
    if (position < mHeaders.size()) {
        return RecyclerViewMode.MODE_HEADER_VIEW;
    } else if (position >= mHeaders.size() + mData.size()) {
        return RecyclerViewMode.MODE_FOOTER_VIEW;
    }
    return RecyclerViewMode.MODE_DATA;
}

3.override il metodo getItemCount

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

4.override il metodo onCreateViewHolder. crea view holder per 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.Override il metodo onBindViewHolder. associare i dati per viewType

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    if (mMode == RecyclerViewMode.MODE_LOADING) {
        onBindLoadingViewHolder(holder, position);
    } else if (mMode == RecyclerViewMode.MODE_ERROR) {
        onBindErrorViewHolder(holder, position);
    } else if (mMode == RecyclerViewMode.MODE_EMPTY) {
        onBindEmptyViewHolder(holder, position);
    } else {
        if (position < mHeaders.size()) {
            if (mHeaders.size() > 0) {
                onBindHeaderViewHolder(holder, position);
            }
        } else if (position >= mHeaders.size() + mData.size()) {
            if (mFooters.size() > 0) {
                onBindFooterViewHolder(holder, position - mHeaders.size() - mData.size());
            }
        } else {
            onBindDataViewHolder(holder, position - mHeaders.size());
        }
    }
}

cosa succede se il tuo collegamento si interrompe in futuro?
Gopal Singh Sirvi


1

Puoi usare la libreria SectionedRecyclerViewAdapter per raggruppare i tuoi articoli in sezioni e aggiungere un'intestazione a ciascuna sezione, come nell'immagine seguente:

inserisci qui la descrizione dell'immagine

Per prima cosa crei la tua classe di sezione:

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

Quindi si configura RecyclerView con le sezioni e si modifica lo SpanSize delle intestazioni con un GridLayoutManager:

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

// Create your sections with the list of data
MySection section1 = new MySection("My Section 1 title", dataList1);
MySection section2 = new MySection("My Section 2 title", dataList2);

// Add your Sections to the adapter
sectionAdapter.addSection(section1);
sectionAdapter.addSection(section2);

// Set up a GridLayoutManager to change the SpanSize of the header
GridLayoutManager glm = new GridLayoutManager(getContext(), 2);
glm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
    @Override
    public int getSpanSize(int position) {
        switch(sectionAdapter.getSectionItemViewType(position)) {
            case SectionedRecyclerViewAdapter.VIEW_TYPE_HEADER:
                return 2;
            default:
                return 1;
        }
    }
});

// Set up your RecyclerView with the SectionedRecyclerViewAdapter
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(glm);
recyclerView.setAdapter(sectionAdapter);

0

So di essere arrivato in ritardo, ma solo di recente sono stato in grado di implementare tale "addHeader" nell'adattatore. Nel mio progetto FlexibleAdapter puoi chiamare setHeaderun elemento sezionabile , quindi chiamare showAllHeaders. Se ti serve solo 1 intestazione, il primo elemento dovrebbe avere l'intestazione. Se elimini questo elemento, l'intestazione viene automaticamente collegata a quella successiva.

Purtroppo i piè di pagina non sono (ancora) coperti.

Il FlexibleAdapter ti consente di fare molto di più che creare intestazioni / sezioni. Dovresti davvero dare un'occhiata: https://github.com/davideas/ForrectAdapter .


0

Vorrei solo aggiungere un'alternativa a tutte quelle implementazioni HeaderRecyclerViewAdapter. CompoundAdapter:

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

È un approccio più flessibile, poiché è possibile creare un AdapterGroup da Adapters. Per l'esempio dell'intestazione, utilizza l'adattatore così com'è, insieme a un adattatore contenente un elemento per l'intestazione:

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

recyclerView.setAdapter(adapterGroup);

È abbastanza semplice e leggibile. È possibile implementare facilmente adattatori più complessi utilizzando lo stesso principio.


0

recyclerview:1.2.0introduce la classe ConcatAdapter che concatena più adattatori in uno solo. Quindi consente di creare adattatori di intestazione / piè di pagina separati e riutilizzarli in più elenchi.

myRecyclerView.adapter = ConcatAdapter(headerAdapter, listAdapter, footerAdapter)

Dai un'occhiata all'articolo dell'annuncio . Contiene un esempio su come visualizzare l'avanzamento del caricamento nell'intestazione e nel piè di pagina utilizzandoConcatAdapter .

Per il momento, quando inserisco questa risposta, la versione 1.2.0della libreria è in fase alpha, quindi l'API potrebbe cambiare. Puoi controllare lo stato qui .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.