java - tutorial - Android 5.0: agregar encabezado/pie de página a un RecyclerView
recyclerview android 3 (12)
Pasé un momento tratando de encontrar una manera de agregar un encabezado a un
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
LayoutManager
parece ser el objeto que maneja la disposición de los elementos de
RecyclerView
.
Como no pude encontrar ningún
addHeaderView(View view)
, decidí
LayoutManager
el
LayoutManager
addView(View view, int position)
del
LayoutManager
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 obtener varias
NullPointerExceptions
tratando de llamar a
addView(View view)
en diferentes momentos de la creación de 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 para hacerlo (y no parece ser).
PD: ¡También sería muy apreciada una solución que pudiera manejar el
GridLayoutManager
además del
LinearLayoutManager
!
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 usando el mismo principio.
mi manera 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
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.
Muy simple de resolver !!
No me gusta la idea de tener la 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 la vista, independientemente de la
layout_height
de
layout_height
de 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.
No he intentado esto, 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
getItemViewType
en 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)
createViewHolder
continuación,
createViewHolder
pasa el número entero que devolvió de
getItemViewType
, lo que le permite crear o configurar el titular 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 olvide restar uno del entero de posición pasado a
bindViewHolder
.
Puede usar esta biblioteca GitHub que permite agregar Encabezado y / o Pie de página en su RecyclerView de la manera más simple posible.
HFRecyclerView 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:
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));
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:
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);
Puede usar viewtype para resolver este problema, aquí está mi demo: https://github.com/yefengfreedom/RecyclerViewWithHeaderFooterLoadingEmptyViewErrorView
-
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 getItemViewType mothod
@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());
}
}
}
Sé que llego tarde, pero solo recientemente pude implementar tal "addHeader" en el Adaptador.
En mi proyecto
FlexibleAdapter
puede llamar a
setHeader
en un elemento
Seccionable
, luego llamar a
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 .
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 ListView, por lo que quería poder inflar las vistas en mi Fragment y agregarlas a
RecyclerView
en
onCreateView
.
Esto se hace creando un
HeaderViewRecyclerAdapter
pasa el adaptador que se va a envolver, y llama a
addHeaderView
y
addFooterView
pasando sus vistas infladas.
Luego establezca la instancia
HeaderViewRecyclerAdapter
como el adaptador en
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 a
setAdapter
para cambiar el adaptador envuelto dejando los encabezados y pies de página intactos, y se notificará el cambio a
RecyclerView
.
Tuve el mismo problema en Lollipop y creé dos enfoques para envolver el adaptador
Recyclerview
.
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
notifyDataSetChanged
en 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 ya debería estar listo. Y aquí están:
lo esencial
-
HeaderRecyclerViewAdapterV1.java
uso
new HeaderRecyclerViewAdapterV1(new RegularAdapter());
-
HeaderRecyclerViewAdapterV2.java
uso
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);
}
Comentarios y tenedores apreciados.
HeaderRecyclerViewAdapterV2
por mí mismo y evolucionaré,
HeaderRecyclerViewAdapterV2
y publicaré los cambios en el futuro.
EDITAR
: @OvidiuLatcu Sí, tuve algunos problemas.
En realidad, dejé de compensar el encabezado implícitamente por
position - (useHeader() ? 1 : 0)
y en su lugar creé un método público
int offsetPosition(int position)
para él.
Porque si configura un
OnItemTouchListener
en Recyclerview, puede interceptar el toque, obtener las coordenadas x, y del toque, encontrar la vista secundaria correspondiente
y luego
llamar a
recyclerView.getChildPosition(...)
y siempre obtendrá el no compensado posición 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.
Tuve que agregar un pie de página a mi
RecyclerView
y aquí estoy compartiendo mi fragmento de código, ya que pensé que podría ser útil.
Verifique 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 a
RecyclerView
.
Puede consultar
este repositorio de GitHub
para verificar la implementación de agregar tanto el encabezado como el pie de página.