Android 5.0: agregar encabezado / pie de página a un RecyclerView

122

Pasé un momento tratando de encontrar una manera de agregar un encabezado a RecyclerView, sin éxito.

Esto es lo que obtuve hasta ahora:

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

   ...
}

El LayoutManagerparece ser el objeto que maneja la disposición de los RecyclerViewartículos. Como no pude encontrar ningún addHeaderView(View view)método, decidí seguir el método de LayoutManager's addView(View view, int position)y agregar mi vista de encabezado en la primera posición para actuar como un encabezado.

Y aquí es donde las cosas se ponen más feas:

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)

Después de que varios NullPointerExceptionsintentaran llamar addView(View view)a los diferentes momentos de la creación de la Actividad (también intenté agregar la vista una vez que todo está configurado, incluso los datos del Adaptador), me di cuenta de que no tengo idea si esta es la forma correcta de hacerlo (y no parece ser)

PD: ¡También se agradecería una solución que pudiera manejar GridLayoutManagerademás del LinearLayoutManager!

MathieuMaree
fuente
eche un vistazo a este stackoverflow.com/a/26573338/2127203
EC84B4
El problema está en el código del adaptador. Significa que, de alguna manera, está devolviendo un visor nulo en la función onCreateViewHolder.
Neo
Hay una buena manera de agregar encabezado a StaggeredGridLayout stackoverflow.com/questions/42202735/…
Aleksey Timoshchenko

Respuestas:

120

Tuve que agregar un pie de página a mi RecyclerViewy aquí estoy compartiendo mi fragmento de código, ya que pensé que podría ser útil. Consulte los comentarios dentro del código para comprender mejor el flujo general.

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

El fragmento de código anterior agrega un pie de página al RecyclerView. Puede consultar este repositorio de GitHub para verificar la implementación de agregar tanto el encabezado como el pie de página.

Reaz Murshed
fuente
2
Funciona bien. Esto debe marcarse como respuesta correcta.
Naga Mallesh Maddali
1
Esto es exactamente lo que hice. Pero, ¿y si quisiera que mi RecyclerView adaptara la lista escalonada? El primer elemento (el encabezado) también se escalonará. :(
Neon Warge
Este es un tutorial sobre cómo puede completar su RecyclerViewdinámica. Puede tener control sobre cada uno de los elementos de su RecyclerView. Mire la sección de código para un proyecto de trabajo. Espero que pueda ayudar. github.com/comeondude/dynamic-recyclerview/wiki
Reaz Murshed
2
int getItemViewType (int position)- Devuelva el tipo de vista del artículo en la posición para fines de reciclaje de vista. La implementación predeterminada de este método devuelve 0, asumiendo un solo tipo de vista para el adaptador. A diferencia de los ListViewadaptadores, los tipos no necesitan ser contiguos. Considere el uso de recursos de identificación para identificar de forma exclusiva los tipos de vista de elementos. - De la documentación. developer.android.com/reference/android/support/v7/widget/…
Reaz Murshed
3
Tanto trabajo manual para algo que la gente quiere hacer siempre. No puedo creerlo ...
JohnyTex
28

Muy simple de resolver !!

No me gusta la idea de tener lógica dentro del adaptador como un tipo de vista diferente porque cada vez que verifica el tipo de vista antes de devolver la vista. La solución a continuación evita controles adicionales.

Simplemente agregue la vista de encabezado LinearLayout (vertical) + vista de reciclaje + vista de pie de página dentro de android.support.v4.widget.NestedScrollView .

Mira esto:

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

Agregue esta línea de código para un desplazamiento suave

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

Esto perderá todo el rendimiento de RV y RV intentará diseñar todos los titulares de vista independientemente de la layout_heightde RV

Se recomienda su uso para la lista de tamaño pequeño como el cajón de navegación o la configuración, etc.

Nishant Shah
fuente
1
Me funcionó bastante simple
Hitesh Sahu
1
Esta es una manera muy simple de agregar encabezados y pies de página a una vista de reciclador cuando los encabezados y pies de página no tienen que repetirse dentro de una lista.
user1841702
34
Esta es una manera muy simple de perder todas las ventajas que RecyclerViewtrae: pierde el reciclaje real y la optimización que brinda.
Marcin Koziński el
1
Probé este código, el desplazamiento no funciona correctamente ... se volvió demasiado lento ... por favor, sugiera si podría hacer algo para eso
Nibha Jain
2
el uso de la vista de desplazamiento anidada hará que la vista de reciclado almacene en caché toda la vista y, si el tamaño de la vista de reciclador es más de desplazamiento, el tiempo de carga aumentará. Sugiero no usar este código
Tushar Saha
25

Tuve el mismo problema en Lollipop y creé dos enfoques para envolver el Recyclerviewadaptador. Uno es bastante fácil de usar, pero no estoy seguro de cómo se comportará con un conjunto de datos cambiante. Debido a que envuelve su adaptador y necesita asegurarse de llamar a métodos como notifyDataSetChangeden el objeto adaptador adecuado.

El otro no debería tener tales problemas. Simplemente deje que su adaptador regular extienda la clase, implemente los métodos abstractos y debería estar listo. Y aquí están:

lo esencial

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

}

Comentarios y tenedores apreciados. Voy a utilizar HeaderRecyclerViewAdapterV2por mi mismo y evolucionar, probar y publicar los cambios en el futuro.

EDITAR : @OvidiuLatcu Sí, tuve algunos problemas. En realidad, dejé de compensar el encabezado implícitamente position - (useHeader() ? 1 : 0)y en su lugar creé un método público int offsetPosition(int position)para ello. Porque si configura una vista OnItemTouchListenerde Recycler, puede interceptar el toque, obtener las coordenadas x, y del toque, encontrar la vista secundaria correspondiente y luego llamar recyclerView.getChildPosition(...)y siempre obtendrá la posición no compensada en el adaptador. Esta es una deficiencia en el Código RecyclerView, no veo un método fácil para superar esto. Es por eso que ahora compenso las posiciones explícitas cuando lo necesito con mi propio código.

seb
fuente
se ve bien ! ¿Tienes algún problema con eso? o podemos usarlo con seguridad? : D
Ovidiu Latcu
1
@OvidiuLatcu ver publicación
seb
En estas implementaciones, ¿parece que ha asumido que el número de encabezados y pies de página es solo 1 cada uno?
rishabhmhjn
@seb La versión 2 funciona como encanto !! lo único que necesitaba modificar es la condición para obtener el pie de página en los métodos onBindViewHolder y getItemViewType. El problema es que si obtiene la posición usando position == getBasicItemCount (), no se devolverá verdadero para la última posición real, sino para la última posición - 1. Terminó colocando FooterView allí (no en la parte inferior). Lo arreglamos cambiando la condición a position == getBasicItemCount () + 1 y funcionó muy bien.
mmark
@seb versión 2 funciona muy bien. muchas gracias. lo estoy usando. Una pequeña cosa que sugiero es agregar la palabra clave 'final' para la función de anulación.
RyanShao
10

No lo he intentado, pero simplemente agregaría 1 (o 2, si desea un encabezado y un pie de página) al entero devuelto por getItemCount en su adaptador. Luego puede anular getItemViewTypeen su adaptador para devolver un número entero diferente cuando i==0: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemViewType(int)

createViewHolderluego se le pasa el entero del que regresó getItemViewType, lo que le permite crear o configurar el soporte de la vista de manera diferente para la vista del encabezado: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html# createViewHolder (android.view.ViewGroup , int)

No olvides restar uno de la posición al número entero pasado bindViewHolder.

Ian Newson
fuente
No creo que ese sea el trabajo de la vista del reciclador. El trabajo de Recyclerviews es simplemente reciclar vistas. Escribir un administrador de diseño con una implementación de encabezado o pie de página es el camino a seguir.
IZI_Shadow_IZI
Gracias @IanNewson por tu respuesta. Primero, esta solución parece estar funcionando, incluso usando solo getItemViewType(int position) { return position == 0 ? 0 : 1; }( RecyclerViewno tiene un getViewTypeCount()método). Por otro lado, estoy de acuerdo con @IZI_Shadow_IZI, realmente tengo la sensación de que LayoutManager debería ser el que maneje este tipo de cosas. Alguna otra idea?
MathieuMaree
@VieuMa probablemente ambos tengan razón, pero no sé cómo hacerlo actualmente y estaba bastante seguro de que mi solución funcionaría. Una solución subóptima es mejor que ninguna solución, que es lo que tenía antes.
Ian Newson
@VieuMa también, abstraer el encabezado y el pie de página en el adaptador significa que debe manejar ambos tipos de diseño solicitados, escribir su propio administrador de diseño significa volver a implementar ambos tipos de diseño.
Ian Newson
simplemente cree un adaptador que envuelva cualquier adaptador y agregue soporte para la vista de encabezado en el índice 0. HeaderView en la vista de lista crea muchos casos extremos y el valor agregado es mínimo dado que es un problema fácil de resolver usando un adaptador de envoltura.
yigit
9

Puede usar esta biblioteca de GitHub que permite agregar Encabezado y / o Pie de página en su RecyclerView de la manera más simple posible.

Debe agregar la biblioteca HFRecyclerView en su proyecto o también puede obtenerla de Gradle:

compile 'com.mikhaellopez:hfrecyclerview:1.0.0'

Este es un resultado en la imagen:

Avance

EDITAR:

Si solo desea agregar un margen en la parte superior o inferior con esta biblioteca: SimpleItemDecoration :

int offsetPx = 10;
recyclerView.addItemDecoration(new StartOffsetItemDecoration(offsetPx));
recyclerView.addItemDecoration(new EndOffsetItemDecoration(offsetPx));
lopez.mikhael
fuente
Esta biblioteca agrega la vista correctamente en el encabezado en LinearLayoutManager pero quiero establecer la vista como encabezado en GridLayoutManager, que tiene lugar en todo el ancho de la pantalla. ¿Es posible con esta biblioteca?
Ved
No, esta biblioteca le permite cambiar el primer y el último elemento de un recyclerView en la adaptación (RecyclerView.Adapter). Puede usar este adaptador a un GridView sin problemas. Así que creo que esta biblioteca te permite hacer lo que quieras.
lopez.mikhael
6

Terminé implementando mi propio adaptador para envolver cualquier otro adaptador y proporcionar métodos para agregar vistas de encabezado y pie de página.

Creé una esencia aquí: HeaderViewRecyclerAdapter.java

La característica principal que quería era una interfaz similar a un ListView, así que quería ser capaz de inflar las vistas en mi Fragmento y añadirlos a la RecyclerViewde onCreateView. Esto se realiza creando un HeaderViewRecyclerAdapterpase para adaptar el adaptador, y llamando addHeaderViewy addFooterViewpasando sus vistas infladas. Luego configure la HeaderViewRecyclerAdapterinstancia como el adaptador en el RecyclerView.

Un requisito adicional era que necesitaba poder cambiar fácilmente los adaptadores manteniendo los encabezados y pies de página, no quería tener múltiples adaptadores con múltiples instancias de estos encabezados y pies de página. Por lo tanto, puede llamar setAdapterpara cambiar el adaptador envuelto dejando los encabezados y pies de página intactos, con la RecyclerViewnotificación del cambio.

Darnmason
fuente
3

mi forma de "mantenerlo simple y estúpido" ... desperdicia algunos recursos, lo sé, pero no me importa ya que mi código es simple, así que ... 1) agregue pie de página con visibilidad GONE a su item_layout

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

2) luego configúrelo visible en el último 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);
        }
    }

hacer lo contrario para el encabezado

Luca Rocchi
fuente
1

Basado en la solución de @ seb, creé una subclase de RecyclerView.Adapter que admite un número arbitrario de encabezados y pies de página.

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

Aunque parece ser una solución, también creo que esto debería ser administrado por LayoutManager. Desafortunadamente, lo necesito ahora y no tengo tiempo para implementar un StaggeredGridLayoutManager desde cero (ni siquiera extenderlo).

Todavía lo estoy probando, pero puedes probarlo si quieres. Avíseme si encuentra algún problema con él.

mato
fuente
1

Puede usar viewtype para resolver este problema, aquí está mi demo: https://github.com/yefengfreedom/RecyclerViewWithHeaderFooterLoadingEmptyViewErrorView

  1. puede definir algunos modos de visualización de la vista del reciclador:

    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.sobre el método 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.sobre el método getItemCount

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

4.sobre el método onCreateViewHolder. crear titular de vista por 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. Anule el método onBindViewHolder. enlazar datos por 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());
        }
    }
}
yefeng
fuente
¿Qué pasa si su enlace se romperá en el futuro?
Gopal Singh Sirvi
Buena pregunta
Editaré
1

Puede usar la biblioteca SectionedRecyclerViewAdapter para agrupar sus elementos en secciones y agregar un encabezado a cada sección, como en la imagen a continuación:

ingrese la descripción de la imagen aquí

Primero creas tu clase de sección:

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

Luego configura RecyclerView con sus secciones y cambia el SpanSize de los encabezados 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);
Gustavo
fuente
0

Sé que llego tarde, pero solo recientemente pude implementar ese "addHeader" en el Adaptador. En mi proyecto FlexibleAdapter , puede llamar setHeadera un elemento seccionable y luego llamar showAllHeaders. Si solo necesita 1 encabezado, el primer elemento debe tener el encabezado. Si elimina este elemento, el encabezado se vincula automáticamente con el siguiente.

Lamentablemente, los pies de página no están cubiertos (todavía).

El Adaptador flexible le permite hacer mucho más que crear encabezados / secciones. Realmente deberías echar un vistazo: https://github.com/davideas/FlexibleAdapter .

Davideas
fuente
0

Simplemente agregaría una alternativa a todas esas implementaciones de HeaderRecyclerViewAdapter. Adaptador compuesto:

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

Es un enfoque más flexible, ya que puede crear un grupo de adaptadores a partir de adaptadores. Para el ejemplo de encabezado, use su adaptador tal como está, junto con un adaptador que contenga un elemento para el encabezado:

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

recyclerView.setAdapter(adapterGroup);

Es bastante simple y legible. Puede implementar un adaptador más complejo fácilmente utilizando el mismo principio.

blurkidi
fuente