Android 5.0 – Добавление заголовка / нижнего колонтитула в RecyclerView

Я потратил время, пытаясь найти способ добавить заголовок к RecyclerView , безуспешно. Это то, что я получил до сих пор:

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

Кажется, что LayoutManager является объектом обработки расположения элементов RecyclerView . Поскольку я не мог найти никакого addHeaderView(View view) , я решил пойти с LayoutManager addView(View view, int position) и добавить заголовок в первую позицию, чтобы действовать как заголовок.

Aaand, где все становится более уродливым:

 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) 

После получения нескольких NullPointerExceptions пытающихся вызвать addView(View view) в разные моменты создания Activity (также пытались добавить представление, когда все настроено, даже данные адаптера), я понял, что понятия не имею, правильно ли это происходит Сделать это (и это не выглядит).

PS: Также было бы LinearLayoutManager оценить решение, которое могло бы обрабатывать GridLayoutManager в дополнение к LinearLayoutManager !

Нашли очень хорошую статью относительно этого https://plus.google.com/+WillBlaschko/posts/3MFmgPbQuWx

Мне пришлось добавить нижний колонтитул в мой RecyclerView и здесь я делюсь своим фрагментом кода, поскольку я думал, что это может быть полезно.

 public class RecentCallsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int FOOTER_VIEW = 1; // Define a view holder 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(parent.getContext()).inflate(R.layout.list_item_footer, parent, false); FooterViewHolder vh = new FooterViewHolder(v); return vh; } v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_normal, parent, false); NormalViewHolder vh = new NormalViewHolder(v); return vh; } // Now bind the viewholders 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 } } } 

У меня была такая же проблема на Lollipop, и я создал два подхода для Recyclerview адаптера Recyclerview . Один из них довольно прост в использовании, но я не уверен, как он будет вести себя с изменяющимся набором данных. Поскольку он обертывает ваш адаптер, и вам нужно сделать так, чтобы вы вызывали такие методы, как notifyDataSetChanged на правый объект-адаптер.

У другого не должно быть таких проблем. Просто пусть ваш обычный адаптер расширяет класс, реализует абстрактные методы, и вы должны быть готовы. И вот они:

логи

  • Использование HeaderRecyclerViewAdapterV1.java new HeaderRecyclerViewAdapterV1(new RegularAdapter());
  • Использование 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); } 

Обратная связь и вилки оценили. Я буду использовать HeaderRecyclerViewAdapterV2 самостоятельно и развиваться, тестировать и публиковать изменения в будущем.

EDIT : @OvidiuLatcu Да, у меня были некоторые проблемы. Фактически я прекратил смещение заголовка неявно по position - (useHeader() ? 1 : 0) и вместо этого создал для него открытый метод int offsetPosition(int position) . Потому что если вы установите OnItemTouchListener на Recyclerview, вы можете перехватить касание, получить координаты x, y на сенсорном экране, найти соответствующее дочернее представление, а затем вызвать recyclerView.getChildPosition(...) и вы всегда будете получать без смещения Положение в адаптере! Это недостаток в коде RecyclerView, я не вижу легкого способа преодолеть это. Вот почему я теперь компенсировал позиции, когда мне нужно, по моему собственному коду.

Очень просто решить!

Мне не нравится идея наличия логики внутри адаптера в качестве другого вида вида, потому что каждый раз, когда он проверяет тип вида перед возвратом представления. Ниже решения избегают дополнительных проверок.

Просто добавьте LinearLayout (вертикальный) вид заголовка + recyclerview + footer view внутри файла android.support.v4.widget.NestedScrollView .

Проверь это:

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

Добавьте эту строку кода для плавной прокрутки

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

PS Это полезно, только когда ваш список очень мал (например, навигационный ящик, настройки и т. Д.), Потому что вы можете потерять преимущество утилизации RecyclerView.

Я не пробовал это, но я бы просто добавил 1 (или 2, если вы хотите как верхний, так и нижний колонтитул) целое число, возвращаемое getItemCount в вашем адаптере. Затем вы можете переопределить getItemViewType в своем адаптере, чтобы вернуть другое целое число, когда i==0 : https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemViewType(int)

createViewHolder затем передается целое число, которое вы вернули из getItemViewType , что позволяет вам создавать или настраивать держатель вида по-разному для представления заголовка: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter. Html # createViewHolder (android.view.ViewGroup , int)

Не забудьте вычесть один из целого числа, переданного в bindViewHolder .

Вы можете использовать эту библиотеку GitHub, позволяющую как можно проще добавить верхний и нижний колонтитулы в RecyclerView .

Вам нужно добавить библиотеку HFRecyclerView в свой проект, или вы также можете взять ее из Gradle:

 compile 'com.mikhaellopez:hfrecyclerview:1.0.0' 

Это результат изображения:

предварительный просмотр

РЕДАКТИРОВАТЬ:

Если вы просто хотите добавить марку вверху и / или внизу этой библиотеки: SimpleItemDecoration :

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

Я завершил реализацию своего собственного адаптера, чтобы обернуть любой другой адаптер и предоставить методы для добавления заголовков и нижних колонтитулов.

Создал сущность: HeaderViewRecyclerAdapter.java

Главной особенностью, которую я хотел, был аналогичный интерфейс с ListView, поэтому я хотел иметь возможность раздувать представления в своем фрагменте и добавлять их в RecyclerView в onCreateView . Это делается путем создания HeaderViewRecyclerAdapter передающего HeaderViewRecyclerAdapter адаптер, и вызова addHeaderView и addFooterView передающего ваши завышенные виды. Затем установите экземпляр HeaderViewRecyclerAdapter в качестве адаптера в RecyclerView .

Дополнительным требованием было то, что мне нужно было легко переключаться между адаптерами, сохраняя верхние и нижние колонтитулы, я не хотел иметь несколько адаптеров с несколькими экземплярами этих верхних и нижних колонтитулов. Таким образом, вы можете вызвать setAdapter чтобы изменить обернутый адаптер, оставив верхние и нижние колонтитулы неповрежденными, а RecyclerView будет уведомлен об изменении.

Основываясь на решении @ seb, я создал подкласс RecyclerView.Adapter, который поддерживает произвольное количество верхних и нижних колонтитулов.

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

Хотя это, кажется, решение, я также думаю, что эта вещь должна управляться LayoutManager. К сожалению, мне это нужно сейчас, и у меня нет времени для реализации StaggeredGridLayoutManager с нуля (и даже не от него).

Я все еще проверяю это, но вы можете попробовать, если хотите. Пожалуйста, дайте мне знать, если вы найдете какие-либо проблемы с ним.

Вы можете использовать viewtype для решения этой проблемы, вот моя демонстрация: https://github.com/yefengfreedom/RecyclerViewWithHeaderFooterLoadingEmptyViewErrorView

  1. Вы можете определить режим отображения вида ресайклера:

    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.Поверните 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.Поверните метод getItemCount

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

4.Поверните метод onCreateViewHolder. Создать держатель вида 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. Переверните метод onBindViewHolder. Привязывать данные по 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()); } } } 

Я знаю, что опоздал, но только недавно я смог реализовать такой «addHeader» для адаптера. В моем проекте FlexibleAdapter вы можете вызвать setHeader для элемента Sectionable , после чего вы вызываете showAllHeaders . Если вам нужен только 1 заголовок, тогда первый элемент должен иметь заголовок. Если вы удалите этот элемент, заголовок автоматически привязан к следующему.

К сожалению, нижние колонтитулы не охвачены (пока).

FlexibleAdapter позволяет делать гораздо больше, чем создавать заголовки / разделы. Вы действительно должны посмотреть: https://github.com/davideas/FlexibleAdapter .

Вы можете использовать библиотеку SectionedRecyclerViewAdapter для группировки ваших элементов в разделах и добавления заголовка в каждый раздел, например, на изображении ниже:

Введите описание изображения здесь

Сначала вы создаете класс раздела:

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

Затем вы настраиваете RecyclerView своими разделами и изменяете SpanSize заголовков с помощью 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); 

Я бы просто добавил альтернативу всем этим реализациям HeaderRecyclerViewAdapter. CompoundAdapter:

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

Это более гибкий подход, поскольку вы можете создать AdapterGroup из адаптеров. Для примера заголовка используйте ваш адаптер, как он есть, вместе с адаптером, содержащим один элемент для заголовка:

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

Это довольно просто и доступно для чтения. Вы можете реализовать более сложный адаптер, используя тот же принцип.