Android 5.0-为回收视图添加页眉/页脚

我花了一点时间试图找到一种方法来添加一个头到 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)方法,我决定使用 LayoutManageraddView(View view, int position)方法,并在第一个位置添加头视图作为头。

这就是事情变得更糟糕的地方:

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)(也尝试在所有东西都设置好后添加视图,甚至是适配器的数据) ,我意识到我不知道这是否是正确的方法(看起来不是)。

另外,一个解决方案,可以处理的 GridLayoutManager除了 LinearLayoutManager将真正受到赞赏!

139489 次浏览

我还没有尝试过这种方法,但是我会简单地向适配器中由 getItemCount 返回的整数添加1(或者2,如果您希望同时添加页眉和页脚)。然后,可以在适配器中重写 getItemViewType,以便在 i==0: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemViewType(int)时返回不同的整数

然后传递从 getItemViewType返回的整数 createViewHolder,允许您为头视图创建或配置不同的视图持有者: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#createViewHolder(android.view.ViewGroup,int)

不要忘记从传递给 bindViewHolder的位置整数中减去1。

我在 Lollipop 上遇到了同样的问题,并创建了两种方法来包装 Recyclerview适配器。一个是相当容易使用,但我不知道它将如何与一个不断变化的数据集行为。因为它包装了您的适配器,您需要确保在正确的适配器对象上调用类似 notifyDataSetChanged的方法。

另一个不应该有这样的问题。只要让您的常规适配器扩展类,实现抽象方法,您就应该准备好了。他们来了:

要点

Header 回收器 ViewAdapterV1

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


}

Header 回收器 ViewAdapterV2

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和进化,测试和张贴的变化在未来。

编辑 :@OvidiuLatcu 是的,我遇到了一些问题。实际上,我停止了用 position - (useHeader() ? 1 : 0)隐式地偏移 Header,而是为它创建了一个公共方法 int offsetPosition(int position)。因为如果你在回收视图上设置一个 OnItemTouchListener,你可以截取触摸,得到触摸的 x,y 坐标,找到相应的子视图 然后调用 recyclerView.getChildPosition(...),你总是会得到适配器中的非偏移位置!这是回收视图代码中的一个缺点,我看不到一个简单的方法来克服这一点。这就是为什么我现在偏移位置显式时,我需要我的 自己的代码。

我最终实现了自己的适配器来封装任何其他适配器,并提供了添加页眉和页脚视图的方法。

创建了一个要点: HeaderViewRecyclerAdapter.java

我想要的主要特性是一个类似于 ListView 的界面,所以我希望能够在我的片段中放大视图,并将它们添加到 onCreateView中的 RecyclerView中。这是通过创建一个 HeaderViewRecyclerAdapter来传递要包装的适配器,并调用 addHeaderViewaddFooterView来传递膨胀的视图来完成的。然后将 HeaderViewRecyclerAdapter实例设置为 RecyclerView上的适配器。

一个额外的要求是,我需要能够轻松地交换适配器,同时保持页眉和页脚,我不希望有多个适配器与这些页眉和页脚的多个实例。因此,您可以调用 setAdapter来更改已包装的适配器,而保留页眉和页脚的完整性,并将更改通知 RecyclerView

在@seb 的解决方案的基础上,我创建了回收视图.Adapter 的一个子类,它支持任意数量的页眉和页脚。

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

虽然它看起来是一个解决方案,但我还是认为这个问题应该由 LayoutManager 来管理。不幸的是,我现在就需要它,而且我没有时间从头开始实现 StaggeredGridLayoutManager (甚至没有时间对它进行扩展)。

我还在测试,不过你可以试试。如果你发现有什么问题,请告诉我。

我不得不在我的 RecyclerView中添加一个页脚,在这里我分享了我的代码片段,因为我认为它可能是有用的。请检查代码中的注释,以便更好地理解整个流程。

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

上面的代码片段将一个页脚添加到 RecyclerView。您可以检查 这个 GitHub 仓库以检查添加页眉和页脚的实现。

你可以使用 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;

重写 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;
}

重写 getItemCount 方法

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

通过 viewType 覆盖 onCreateViewHolder 方法

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

您可以使用这个 GitHub库,以尽可能简单的方式在 回收视图中添加 和/或 福特

你需要在你的项目中添加 < strong > HF迴 clView 库,或者你也可以从 Gradle 获取:

compile 'com.mikhaellopez:hfrecyclerview:1.0.0'

这是图像上的结果:

Preview

编辑:

如果你只是想用这个库在顶部和/或底部添加一个空白:

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

我知道我来晚了,但是直到最近我才能够在适配器上实现这样的“ addHeader”。在我的 FlexibleAdapter 灵活适配器项目中,你可以在 切片项上调用 setHeader,然后调用 showAllHeaders。如果您只需要1个标题,那么第一个项目应该有标题。如果删除此项,则标题将自动链接到下一个标题。

遗憾的是,页脚尚未包括在内。

FlexibleAdapter 允许您做的不仅仅是创建标题/节。 你真的应该看看: https://github.com/davideas/FlexibleAdapter

您可以使用库 节点回收视图适配器将您的项目分组到各个部分,并在每个部分添加一个标题,如下图所示:

enter image description here

首先创建节类:

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

然后,您可以设置回收视图与您的部分,并更改标题的 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);

非常简单的解决! !

我不喜欢在适配器中使用逻辑作为不同的视图类型,因为每次在返回视图之前都要检查视图类型。下面的解决方案避免了额外的检查。

只需在 V4.widget. NestedScrollView中添加 LinearLayout (垂直)页眉视图 + 回收视图 + 页脚视图。

看看这个:

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

这将失去所有的 RV 性能和 RV 将尝试布局所有的观点持有者,而不管的 layout_height的 RV

建议用于小尺寸列表,如导航抽屉或设置等

我只需要添加一个替代方案到所有这些 Header迴收视角适配器实现中:

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

这是相当简单和可读的。您可以实现更复杂的适配器很容易使用相同的原则。

我的“保持简单愚蠢”的方式... 它浪费了一些资源,我知道,但我不在乎,因为我的代码保持简单 所以..。 首先,将具有可见性 消失了的页脚添加到 item _ layout

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

然后,将其设置为在最后一项上可见

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

对头部做相反的动作

recyclerview:1.2.0引入了 连接适配器类,它将多个适配器连接到一个适配器中。因此,它允许创建单独的页眉/页脚适配器,并跨多个列表重用它们。

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

看看 公告文章。它包含一个示例,说明如何使用 ConcatAdapter在页眉和页脚中显示加载进度。

当我发布这个答案的时候,这个库的 1.2.0版本正处于 alpha 阶段,所以 api 可能会发生变化。您可以检查状态 给你

@ reaz-mursted 的回答非常好。但是我不喜欢的一部分,数据大小添加 + 1,并返回页脚视图,如果结束达到。
它告诉我每一个最后的元素都是一个 Footer 视图,我真的很难删除页脚视图。

而不是为我的案子做这样的事

private List<RealResponse> addEmptyLoaderResponse(List<RealResponse> originalList){
if(originalList == null){
originalList= new ArrayList<>();
}
originalList.add(new EmptyRealResponse());
return originalList;
}
private class EmptyRealResponse extends RealResponse{
/**Just an Empty class as placeholder for loader at Footer View
*
*/
}
public void setItems(List<InconcurPostResponse> items) {
this.items = addEmptyLoaderResponse(items);
}
@Override
public int getItemCount() {
return items.size();
}


@Override
public int getItemViewType(int position){
if(this.items.get(position) instanceof  EmptyRealResponse){
return ViewTypes.FOOTER_VIEW_TYPE.getViewType();
}
return super.getItemViewType(position);
}

这对我来说很干净,它将实际的对象加载到回收视图中。另外,当我不需要或者如果我想添加更多的占位符页脚视图时,删除页脚视图的好处。