是否有一个addHeaderView等同的RecyclerView?

我正在寻找一个相当于addHeaderView回收视图。基本上我想有2个按钮的图像被添加到列表视图的标题。有不同的方法来添加一个头视图到回收视图吗?举个例子作为指导会很有帮助

编辑2(增加片段布局):

在添加日志语句之后,getViewType似乎只接收到0的位置。这导致onCreateView只加载一个布局:

10-26 16:32:53.766    5449-5449/co.testapp I/logger info﹕ Adapter-> getItemCount: 5
10-26 16:32:53.766    5449-5449/co.testapp I/logger info﹕ Adapter-> getItemCount: 5
10-26 16:32:53.766    5449-5449/co.testapp I/logger info﹕ Adapter-> getItemCount: 5
10-26 16:32:53.766    5449-5449/co.testapp I/logger info﹕ Adapter-> getItemCount: 5
10-26 16:32:53.766    5449-5449/co.testapp I/logger info﹕ Adapter-> getItemCount: 5
10-26 16:32:53.766    5449-5449/co.testapp I/logger info﹕ Adapter-> getItemCount: 5
10-26 16:32:53.766    5449-5449/co.testapp I/logger info﹕ Adapter-> getItemCount: 5
10-26 16:32:53.766    5449-5449/co.testapp I/logger info﹕ Adapter-> getItemCount: 5
10-26 16:32:53.766    5449-5449/co.testapp I/logger info﹕ Adapter-> getItemCount: 5
10-26 16:32:53.766    5449-5449/co.testapp I/logger info﹕ Adapter-> getItemCount: 5
10-26 16:32:53.766    5449-5449/co.testapp I/logger info﹕ Adapter-> getItemCount: 5
10-26 16:32:53.766    5449-5449/co.testapp I/logger info﹕ Adapter-> getItemViewType position: 0
10-26 16:32:53.766    5449-5449/co.testapp I/logger info﹕ Adapter-> getItemViewType position: 0
10-26 16:32:53.766    5449-5449/co.testapp I/logger info﹕ Adapter-> getItemViewType position: 0
10-26 16:32:53.766    5449-5449/co.testapp I/logger info﹕ Adapter-> onCreateViewHolder, viewtype: 0
10-26 16:32:53.766    5449-5449/co.testapp I/logger info﹕ Adapter-> onBindViewHolder, viewType: 0

片段转换来加载CommentFragment:

@Override
public void onPhotoFeedItemClick(View view, int position) {
if (fragmentManager == null)
fragmentManager = getSupportFragmentManager();


FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();


if (view.getId() == R.id.button_comment){
CommentFragment commentFragment = CommentFragment.newInstance("","", position);
fragmentTransaction.add(R.id.main_activity, commentFragment,"comment_fragment_tag");
fragmentTransaction.addToBackStack(Constants.TAG_COMMENTS);
fragmentTransaction.commit();
}
}

Fragment的onCreateView:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {


View view = inflater.inflate(R.layout.fragment_comment, container, false);
mRecyclerView = (RecyclerView) view.findViewById(R.id.list_recylclerview);
mRecyclerView.setLayoutManager(new LinearLayoutManager(_context));
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mAdapter = new CommentAdapter(R.layout.row_list_comments, R.layout.row_header_comments, _context, comments);
mRecyclerView.setAdapter(mAdapter);
return view;
}

包含recycleview的片段:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
tools:context="co.testapp.fragments.CommentFragment"
android:background="@color/white">
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/list_recylclerview"
android:layout_width="match_parent"
android:layout_height="200dp" />
</RelativeLayout>
</RelativeLayout>

注释行布局:

<?xml version="1.0" encoding="utf-8"?>


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_margin="10dp"
android:background="@color/white">
<!--Profile Picture-->
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:id="@+id/profile_picture"
android:background="@color/blue_testapp"/>
<!--Name-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="First Name Last Name"
android:textSize="16dp"
android:textColor="@color/blue_testapp"
android:id="@+id/name_of_poster"
android:layout_toRightOf="@id/profile_picture"
/>
<!--Comment-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_marginTop="-5dp"
android:text="This is a test comment"
android:textSize="14dp"
android:textColor="@color/black"
android:id="@+id/comment"
android:layout_below="@id/name_of_poster"
android:layout_toRightOf="@id/profile_picture"/>
</RelativeLayout>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="300dp"
android:id="@+id/header_photo"
android:layout_gravity="center_horizontal"/>
</LinearLayout>

适配器代码(感谢hister让我开始):

public class CommentAdapter extends RecyclerView.Adapter<ViewHolder>{


private final int rowCardLayout;
public static Context mContext;
private final int headerLayout;
private final String [] comments;
private static final int HEADER = 0;
private static final int OTHER = 1;


public CommentAdapter(int rowCardLayout, int headerLayout, Context context, String [] comments) {
this.rowCardLayout = rowCardLayout;
this.mContext = context;
this.comments = comments;
this.headerLayout = headerLayout;
}


@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
logger.i("onCreateViewHolder, viewtype: " + i); //viewtype always returns 0 so OTHER layout is never inflated
if (i == HEADER) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(headerLayout, viewGroup, false);
return new ViewHolderHeader(v);
}
else if (i == OTHER){
View v = LayoutInflater.from(viewGroup.getContext()).inflate(rowCardLayout, viewGroup, false);
return new ViewHolderComments(v);
}
else
throw new RuntimeException("Could not inflate layout");
}


@Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
logger.i("onBindViewHolder, viewType: " + i);


if (viewHolder instanceof ViewHolderComments)
((ViewHolderComments) viewHolder).comment.setText(comments[i].toString());
if (viewHolder instanceof ViewHolderHeader)
((ViewHolderHeader) viewHolder).header.setImageResource(R.drawable.image2);
else {
logger.e("no instance of viewholder found");
}
}


@Override
public int getItemCount() {
int count = comments.length + 1;
logger.i("getItemCount: " + count);
return count;
}


@Override
public int getItemViewType(int position) {
logger.i("getItemViewType position: " + position);
if (position == HEADER)
return HEADER;
else
return OTHER;
}


public static class ViewHolderComments extends RecyclerView.ViewHolder {
public TextView comment;
public ImageView image;


public ViewHolderComments(View itemView) {
super(itemView);
comment = (TextView) itemView.findViewById(R.id.comment);
image = (ImageView) itemView.findViewById(R.id.image);
}
}


public static class ViewHolderHeader extends RecyclerView.ViewHolder {
public final ImageView header;


public ViewHolderHeader(View itemView){
super(itemView);
header = (ImageView) itemView.findViewById(R.id.header_photo);
}
}
}

使用上面的代码,只有头部布局显示为viewType总是0。它看起来像。如果我强制另一种布局,它看起来像:

245732 次浏览

HeaderView依赖于LayoutManager。默认的layoutmanager都不支持这一点,而且可能不会。ListView中的HeaderView创建了很多复杂性,但没有任何显著的好处。

我建议创建一个基本适配器类,如果提供的话,它可以为header添加项。不要忘记覆盖notify*方法来适当地偏移它们,这取决于header是否存在。

没有像listview.addHeaderView()这样简单的方法,但你可以通过在适配器中添加一个类型来实现这一点。

这里有一个例子

public class HeaderAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_HEADER = 0;
private static final int TYPE_ITEM = 1;
String[] data;


public HeaderAdapter(String[] data) {
this.data = data;
}


@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_ITEM) {
//inflate your layout and pass it to view holder
return new VHItem(null);
} else if (viewType == TYPE_HEADER) {
//inflate your layout and pass it to view holder
return new VHHeader(null);
}


throw new RuntimeException("there is no type that matches the type " + viewType + " + make sure your using types correctly");
}


@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof VHItem) {
String dataItem = getItem(position);
//cast holder to VHItem and set data
} else if (holder instanceof VHHeader) {
//cast holder to VHHeader and set data for header.
}
}


@Override
public int getItemCount() {
return data.length + 1;
}


@Override
public int getItemViewType(int position) {
if (isPositionHeader(position))
return TYPE_HEADER;


return TYPE_ITEM;
}


private boolean isPositionHeader(int position) {
return position == 0;
}


private String getItem(int position) {
return data[position - 1];
}


class VHItem extends RecyclerView.ViewHolder {
TextView title;


public VHItem(View itemView) {
super(itemView);
}
}


class VHHeader extends RecyclerView.ViewHolder {
Button button;


public VHHeader(View itemView) {
super(itemView);
}
}
}

链接到gist ->

基于这篇文章,我创建了一个RecyclerView的子类。支持任意数量的页眉和页脚的适配器。

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

虽然这似乎是一个解决方案,但我也认为这个东西应该由LayoutManager管理。不幸的是,我现在需要它,我没有时间从头开始实现StaggeredGridLayoutManager(甚至也没有从它扩展)。

我还在测试,但如果你想试试也可以。如果你发现有任何问题,请告诉我。

请随意使用我的库,可用在这里

它允许你通过一个简单的方法调用,为任何使用LinearLayoutManagerGridLayoutManagerRecyclerView创建头文件View

enter image description here

First - extends RecyclerView.Adapter<RecyclerView.ViewHolder>


public class MenuAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

After - Override方法getItemViewTpe * * * < / p >更重要

@Override
public int getItemViewType(int position) {
return position;
}

方法onCreateViewHolder

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.menu_item, parent, false);
View header = LayoutInflater.from(parent.getContext()).inflate(R.layout.menu_header_item, parent, false);
Log.d("onCreateViewHolder", String.valueOf(viewType));


if (viewType == 0) {
return new MenuLeftHeaderViewHolder(header, onClickListener);
} else {
return new MenuLeftViewHolder(view, onClickListener);
}
}

方法onBindViewHolder

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (position == 0) {
MenuHeaderViewHolder menuHeaderViewHolder = (MenuHeaderViewHolder) holder;
menuHeaderViewHolder.mTitle.setText(sMenuTitles[position]);
menuHeaderViewHolder.mImage.setImageResource(sMenuImages[position]);
} else {
MenuViewHolder menuLeftViewHolder = (MenuLeftViewHolder) holder;
menuViewHolder.mTitle.setText(sMenuTitles[position]);
menuViewHolder.mImage.setImageResource(sMenuImages[position]);
}
}

在finish中实现了ViewHolders类静态

public static class MenuViewHolder extends RecyclerView.ViewHolder


public static class MenuLeftHeaderViewHolder extends RecyclerView.ViewHolder

容易和可重用的ItemDecoration

静态头文件可以很容易地用ItemDecoration没有任何进一步的更改。添加

// add the decoration. done.
HeaderDecoration headerDecoration = new HeaderDecoration(/* init */);
recyclerView.addItemDecoration(headerDecoration);

装饰也是可重用的,因为根本不需要修改适配器或RecyclerView

下面提供的示例代码将需要在顶部添加一个视图,该视图可以像其他任何东西一样膨胀。它可以是这样的:

HeaderDecoration sample

为什么静态 ?

如果你只是要显示文本和图像,这个解决方案是为你—没有用户交互的可能性,如按钮或视图寻呼机,因为它只会被绘制到你的列表的顶部。

空列表处理

如果没有视图来装饰,装饰就画不出来了。您仍然需要自己处理一个空列表。(一种可行的解决方法是向适配器添加一个虚拟项。)

的代码

你可以找到完整的源代码在GitHub上,包括一个Builder来帮助初始化装饰器,或者只是使用下面的代码并将你自己的值提供给构造函数。

请确保为您的视图设置正确的layout_height。例如:match_parent可能无法正常工作。

public class HeaderDecoration extends RecyclerView.ItemDecoration {


private final View mView;
private final boolean mHorizontal;
private final float mParallax;
private final float mShadowSize;
private final int mColumns;
private final Paint mShadowPaint;


public HeaderDecoration(View view, boolean scrollsHorizontally, float parallax, float shadowSize, int columns) {
mView = view;
mHorizontal = scrollsHorizontally;
mParallax = parallax;
mShadowSize = shadowSize;
mColumns = columns;


if (mShadowSize > 0) {
mShadowPaint = new Paint();
mShadowPaint.setShader(mHorizontal ?
new LinearGradient(mShadowSize, 0, 0, 0,
new int[]{Color.argb(55, 0, 0, 0), Color.argb(55, 0, 0, 0), Color.argb(3, 0, 0, 0)},
new float[]{0f, .5f, 1f},
Shader.TileMode.CLAMP) :
new LinearGradient(0, mShadowSize, 0, 0,
new int[]{Color.argb(55, 0, 0, 0), Color.argb(55, 0, 0, 0), Color.argb(3, 0, 0, 0)},
new float[]{0f, .5f, 1f},
Shader.TileMode.CLAMP));
} else {
mShadowPaint = null;
}
}


@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
// layout basically just gets drawn on the reserved space on top of the first view
mView.layout(parent.getLeft(), 0, parent.getRight(), mView.getMeasuredHeight());


for (int i = 0; i < parent.getChildCount(); i++) {
View view = parent.getChildAt(i);
if (parent.getChildAdapterPosition(view) == 0) {
c.save();
if (mHorizontal) {
c.clipRect(parent.getLeft(), parent.getTop(), view.getLeft(), parent.getBottom());
final int width = mView.getMeasuredWidth();
final float left = (view.getLeft() - width) * mParallax;
c.translate(left, 0);
mView.draw(c);
if (mShadowSize > 0) {
c.translate(view.getLeft() - left - mShadowSize, 0);
c.drawRect(parent.getLeft(), parent.getTop(), mShadowSize, parent.getBottom(), mShadowPaint);
}
} else {
c.clipRect(parent.getLeft(), parent.getTop(), parent.getRight(), view.getTop());
final int height = mView.getMeasuredHeight();
final float top = (view.getTop() - height) * mParallax;
c.translate(0, top);
mView.draw(c);
if (mShadowSize > 0) {
c.translate(0, view.getTop() - top - mShadowSize);
c.drawRect(parent.getLeft(), parent.getTop(), parent.getRight(), mShadowSize, mShadowPaint);
}
}
c.restore();
break;
}
}
}


@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (parent.getChildAdapterPosition(view) < mColumns) {
if (mHorizontal) {
if (mView.getMeasuredWidth() <= 0) {
mView.measure(View.MeasureSpec.makeMeasureSpec(parent.getMeasuredWidth(), View.MeasureSpec.AT_MOST),
View.MeasureSpec.makeMeasureSpec(parent.getMeasuredHeight(), View.MeasureSpec.AT_MOST));
}
outRect.set(mView.getMeasuredWidth(), 0, 0, 0);
} else {
if (mView.getMeasuredHeight() <= 0) {
mView.measure(View.MeasureSpec.makeMeasureSpec(parent.getMeasuredWidth(), View.MeasureSpec.AT_MOST),
View.MeasureSpec.makeMeasureSpec(parent.getMeasuredHeight(), View.MeasureSpec.AT_MOST));
}
outRect.set(0, mView.getMeasuredHeight(), 0, 0);
}
} else {
outRect.setEmpty();
}
}
}

GitHub项目是我个人的游乐场。它没有经过彻底的测试,这就是为什么没有然而,库的原因。

它能做什么?

ItemDecoration是对列表项的附加绘图。在这种情况下,装饰被绘制到第一项的顶部。

视图被测量和布局,然后被绘制到第一个项目的顶部。如果添加视差效果,它也会被剪辑到正确的边界。

原生API没有这样的“addHeader”特性,但有“addItem”的概念。

我能够在我的FlexibleAdapter项目中包括页眉和页脚扩展的这个特定功能。我称它为可滚动页眉和页脚

以下是它们的工作原理:

可滚动的页眉和页脚是与其他所有项一起滚动的特殊项,但它们不属于主项(业务项),它们总是由适配器在主项旁边处理。这些项持久地位于第一个和最后一个位置。

enter image description here

关于它们有很多要说的,最好阅读详细的wiki页面

此外,FlexibleAdapter允许你创建标题/节,你也可以有他们黏糊糊的和其他几十个功能,如可扩展的项目,无尽的滚动,UI扩展等…都在一个图书馆里!

你可以使用SectionedRecyclerViewAdapter库来实现它,它有“Sections”的概念,其中Section有Header, Footer和Content(项目列表)。在你的情况下,你可能只需要一个Section,但你可以有很多:

enter image description here

1)创建一个自定义Section类:

class MySection extends StatelessSection {


List<String> myList = Arrays.asList(new String[] {"Item1", "Item2", "Item3" });


public MySection() {
// call constructor with layout resources for this Section header, footer and items
super(R.layout.section_header, R.layout.section_footer,  R.layout.section_item);
}


@Override
public int getContentItemsTotal() {
return myList.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(myList.get(position));
}
}

2)为项目创建一个自定义ViewHolder:

class MyItemViewHolder extends RecyclerView.ViewHolder {


private final TextView tvItem;


public MyItemViewHolder(View itemView) {
super(itemView);


tvItem = (TextView) itemView.findViewById(R.id.tvItem);
}
}

3)使用SectionedRecyclerViewAdapter设置ReclyclerView

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


MySection mySection = new MySection();


// Add your Sections
sectionAdapter.addSection(mySection);


// Set up your RecyclerView with the SectionedRecyclerViewAdapter
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerView.setAdapter(sectionAdapter);
还有一个解决方案涵盖了上面所有的用例:CompoundAdapter: https://github.com/negusoft/CompoundAdapter-android < / p >

您可以创建一个AdapterGroup来保存您的适配器,以及一个带有单个项的适配器,以表示头部。代码简单易读:

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


recyclerView.setAdapter(adapterGroup);

AdapterGroup也允许嵌套,因此对于带有节的适配器,您可以为每个节创建一个AdapterGroup。然后将所有部分放在根适配器组中。

可能http://alexzh.com/tutorials/multiple-row-layouts-using-recyclerview/会有帮助。它只使用RecyclerView和CardView。 这是一个适配器:

public class DifferentRowAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<CityEvent> mList;
public DifferentRowAdapter(List<CityEvent> list) {
this.mList = list;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
switch (viewType) {
case CITY_TYPE:
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_city, parent, false);
return new CityViewHolder(view);
case EVENT_TYPE:
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_event, parent, false);
return new EventViewHolder(view);
}
return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
CityEvent object = mList.get(position);
if (object != null) {
switch (object.getType()) {
case CITY_TYPE:
((CityViewHolder) holder).mTitle.setText(object.getName());
break;
case EVENT_TYPE:
((EventViewHolder) holder).mTitle.setText(object.getName());
((EventViewHolder) holder).mDescription.setText(object.getDescription());
break;
}
}
}
@Override
public int getItemCount() {
if (mList == null)
return 0;
return mList.size();
}
@Override
public int getItemViewType(int position) {
if (mList != null) {
CityEvent object = mList.get(position);
if (object != null) {
return object.getType();
}
}
return 0;
}
public static class CityViewHolder extends RecyclerView.ViewHolder {
private TextView mTitle;
public CityViewHolder(View itemView) {
super(itemView);
mTitle = (TextView) itemView.findViewById(R.id.titleTextView);
}
}
public static class EventViewHolder extends RecyclerView.ViewHolder {
private TextView mTitle;
private TextView mDescription;
public EventViewHolder(View itemView) {
super(itemView);
mTitle = (TextView) itemView.findViewById(R.id.titleTextView);
mDescription = (TextView) itemView.findViewById(R.id.descriptionTextView);
}
}
}

这是一个实体:

public class CityEvent {
public static final int CITY_TYPE = 0;
public static final int EVENT_TYPE = 1;
private String mName;
private String mDescription;
private int mType;
public CityEvent(String name, String description, int type) {
this.mName = name;
this.mDescription = description;
this.mType = type;
}
public String getName() {
return mName;
}
public void setName(String name) {
this.mName = name;
}
public String getDescription() {
return mDescription;
}
public void setDescription(String description) {
this.mDescription = description;
}
public int getType() {
return mType;
}
public void setType(int type) {
this.mType = type;
}
}

这里有一些物品装饰

public class HeaderItemDecoration extends RecyclerView.ItemDecoration {


private View customView;


public HeaderItemDecoration(View view) {
this.customView = view;
}


@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
customView.layout(parent.getLeft(), 0, parent.getRight(), customView.getMeasuredHeight());
for (int i = 0; i < parent.getChildCount(); i++) {
View view = parent.getChildAt(i);
if (parent.getChildAdapterPosition(view) == 0) {
c.save();
final int height = customView.getMeasuredHeight();
final int top = view.getTop() - height;
c.translate(0, top);
customView.draw(c);
c.restore();
break;
}
}
}


@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (parent.getChildAdapterPosition(view) == 0) {
customView.measure(View.MeasureSpec.makeMeasureSpec(parent.getMeasuredWidth(), View.MeasureSpec.AT_MOST),
View.MeasureSpec.makeMeasureSpec(parent.getMeasuredHeight(), View.MeasureSpec.AT_MOST));
outRect.set(0, customView.getMeasuredHeight(), 0, 0);
} else {
outRect.setEmpty();
}
}
}

你可以把你的头和你的RecyclerView放在NestedScrollView中:

<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
>


<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>


<include
layout="@layout/your_header"/>


<android.support.v7.widget.RecyclerView
android:id="@+id/list_recylclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>


</LinearLayout>


</android.support.v4.widget.NestedScrollView>

为了让滚动正常工作,你需要在RecyclerView上禁用嵌套滚动:

myRecyclerView.setNestedScrollingEnabled(false);

将向你展示如何在Recycler视图中创建带有项目的header . __abc0

步骤1-在gradle文件中添加依赖项。

compile 'com.android.support:recyclerview-v7:23.2.0'
// CardView
compile 'com.android.support:cardview-v7:23.2.0'

卡片视图用于装饰目的。

Step2-制作三个xml文件。 一个是主要活动。其次是标题布局。第三个用于列表项布局

activity_main.xml

<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/my_recycler_view"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />

header.xml

<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardElevation="2dp">


<TextView
android:id="@+id/txtHeader"
android:gravity="center"
android:textColor="#000000"
android:textSize="@dimen/abc_text_size_large_material"
android:background="#DCDCDC"
android:layout_width="match_parent"
android:layout_height="50dp" />


</android.support.v7.widget.CardView>

list.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:app="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">


<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardElevation="1dp">


<TextView
android:id="@+id/txtName"
android:text="abc"
android:layout_width="match_parent"
android:layout_height="wrap_content" />


</android.support.v7.widget.CardView>


</LinearLayout>

步骤3-创建三个bean类。

Header.java

public class Header extends ListItem {
private String header;


public String getHeader() {
return header;
}
public void setHeader(String header) {
this.header = header;
}
}

ContentItem.java

public class ContentItem extends ListItem {


private String name;
private String rollnumber;


@Override
public String getName() {
return name;
}


@Override
public void setName(String name) {
this.name = name;
}


public String getRollnumber() {
return rollnumber;
}


public void setRollnumber(String rollnumber) {
this.rollnumber = rollnumber;
}
}

ListItem.java

public class ListItem {
private String name;


public String getName() {
return name;
}


public void setName(String name) {
this.name = name;
}


private int id;


public int getId() {
return id;
}


public void setId(int id) {
this.id = id;
}
}

步骤4-创建一个名为MyRecyclerAdapter.java的适配器

public class MyRecyclerAdapter extends  RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_HEADER = 0;
private static final int TYPE_ITEM = 1;


//Header header;
List<ListItem> list;
public MyRecyclerAdapter(List<ListItem> headerItems) {
this.list = headerItems;
}


@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
if (viewType == TYPE_HEADER) {
View v = inflater.inflate(R.layout.header, parent, false);
return  new VHHeader(v);
} else {
View v = inflater.inflate(R.layout.list, parent, false);
return new VHItem(v);
}
throw new IllegalArgumentException();
}


@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof VHHeader) {
// VHHeader VHheader = (VHHeader)holder;
Header  currentItem = (Header) list.get(position);
VHHeader VHheader = (VHHeader)holder;
VHheader.txtTitle.setText(currentItem.getHeader());
} else if (holder instanceof VHItem)
ContentItem currentItem = (ContentItem) list.get(position);
VHItem VHitem = (VHItem)holder;
VHitem.txtName.setText(currentItem.getName());
}
}


@Override
public int getItemViewType(int position) {
if (isPositionHeader(position))
return TYPE_HEADER;
return TYPE_ITEM;
}


private boolean isPositionHeader(int position) {
return list.get(position) instanceof Header;
}


@Override
public int getItemCount() {
return list.size();
}


class VHHeader extends RecyclerView.ViewHolder{
TextView txtTitle;
public VHHeader(View itemView) {
super(itemView);
this.txtTitle = (TextView) itemView.findViewById(R.id.txtHeader);
}
}
class VHItem extends RecyclerView.ViewHolder{
TextView txtName;
public VHItem(View itemView) {
super(itemView);
this.txtName = (TextView) itemView.findViewById(R.id.txtName);
}
}
}

< p >步骤5 - 在MainActivity中添加如下代码:

public class MainActivity extends AppCompatActivity {
RecyclerView recyclerView;
List<List<ListItem>> arraylist;
MyRecyclerAdapter adapter;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = (RecyclerView)findViewById(R.id.my_recycler_view);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
adapter = new MyRecyclerAdapter(getList());
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setAdapter(adapter);
}


private ArrayList<ListItem> getList() {
ArrayList<ListItem> arrayList = new ArrayList<>();
for(int j = 0; j <= 4; j++) {
Header header = new Header();
header.setHeader("header"+j);
arrayList.add(header);
for (int i = 0; i <= 3; i++) {
ContentItem item = new ContentItem();
item.setRollnumber(i + "");
item.setName("A" + i);
arrayList.add(item);
}
}
return arrayList;
}


}

函数getList()动态地为标题和列表项生成数据。

你可以创建addHeaderView并使用

adapter.addHeaderView(View)

此代码为多个头文件构建addHeaderView。 头文件应该有:

android:layout_height="wrap_content"

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_ITEM = -1;
public class MyViewSHolder extends RecyclerView.ViewHolder {
public MyViewSHolder (View view) {
super(view);
}
// put you code. for example:
View mView;
...
}


public class ViewHeader extends RecyclerView.ViewHolder {
public ViewHeader(View view) {
super(view);
}
}


private List<View> mHeaderViews = new ArrayList<>();
public void addHeaderView(View headerView) {
mHeaderViews.add(headerView);
}


@Override
public int getItemCount() {
return ... + mHeaderViews.size();
}


@Override
public int getItemViewType(int position) {
if (mHeaderViews.size() > position) {
return position;
}


return TYPE_ITEM;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType != TYPE_ITEM) {
//inflate your layout and pass it to view holder
return new ViewHeader(mHeaderViews.get(viewType));
}
...
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int basePosition1) {
if (holder instanceof ViewHeader) {
return;
}
int basePosition = basePosition1 -  mHeaderViews.size();
...
}
}

出于个人目的,我基于@hister的做了一个实现,但使用了继承。

我在抽象超类HeadingableRecycleAdapter中隐藏了实现细节机制(如itemCount加1,position减1),通过 实现适配器所需的方法,如onBindViewHoldergetItemViewTypegetItemCount,使这些方法为final,并向客户端提供带有隐藏逻辑的新方法:

  • onAddViewHolder(RecyclerView.ViewHolder holder, int position),
  • onCreateViewHolder(ViewGroup parent),
  • itemCount()

下面是HeadingableRecycleAdapter类和客户端。我留下的标题布局有点硬编码,因为它符合我的需要。

public abstract class HeadingableRecycleAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {


private static final int HEADER_VIEW_TYPE = 0;


@LayoutRes
private int headerLayoutResource;
private String headerTitle;
private Context context;


public HeadingableRecycleAdapter(@LayoutRes int headerLayoutResourceId, String headerTitle, Context context) {
this.headerLayoutResource = headerLayoutResourceId;
this.headerTitle = headerTitle;
this.context = context;
}


public Context context() {
return context;
}


@Override
public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == HEADER_VIEW_TYPE) {
return new HeaderViewHolder(LayoutInflater.from(context).inflate(headerLayoutResource, parent, false));
}
return onCreateViewHolder(parent);
}


@Override
public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
int viewType = getItemViewType(position);
if (viewType == HEADER_VIEW_TYPE) {
HeaderViewHolder vh = (HeaderViewHolder) holder;
vh.bind(headerTitle);
} else {
onAddViewHolder(holder, position - 1);
}
}


@Override
public final int getItemViewType(int position) {
return position == 0 ? 0 : 1;
}


@Override
public final int getItemCount() {
return itemCount() + 1;
}


public abstract int itemCount();


public abstract RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent);


public abstract void onAddViewHolder(RecyclerView.ViewHolder holder, int position);


}






@PerActivity
public class IngredientsAdapter extends HeadingableRecycleAdapter {
public static final String TITLE = "Ingredients";
private List<Ingredient> itemList;




@Inject
public IngredientsAdapter(Context context) {
super(R.layout.layout_generic_recyclerview_cardified_header, TITLE, context);
}


public void setItemList(List<Ingredient> itemList) {
this.itemList = itemList;
}


@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
return new ViewHolder(LayoutInflater.from(context()).inflate(R.layout.item_ingredient, parent, false));
}


@Override
public void onAddViewHolder(RecyclerView.ViewHolder holder, int position) {
ViewHolder vh = (ViewHolder) holder;
vh.bind(itemList.get(position));
}


@Override
public int itemCount() {
return itemList == null ? 0 : itemList.size();
}


private String getQuantityFormated(double quantity, String measure) {
if (quantity == (long) quantity) {
return String.format(Locale.US, "%s %s", String.valueOf(quantity), measure);
} else {
return String.format(Locale.US, "%.1f %s", quantity, measure);
}
}




class ViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.text_ingredient)
TextView txtIngredient;


ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}


void bind(Ingredient ingredient) {
String ingredientText = ingredient.getIngredient();
txtIngredient.setText(String.format(Locale.US, "%s %s ", getQuantityFormated(ingredient.getQuantity(),
ingredient.getMeasure()), Character.toUpperCase(ingredientText.charAt(0)) +
ingredientText
.substring(1)));
}
}
}

也许将header和recyclerview包装为coordinatorlayout:

<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">


<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:elevation="0dp">


<View
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll" />


</android.support.design.widget.AppBarLayout>


<android.support.v7.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />

已经过去几年了,但以防有人以后读到这篇文章……

使用上面的代码,只有头部布局显示为viewType总是0。

问题在于常量声明:

private static final int HEADER = 0;
private static final int OTHER = 0;  <== bug

如果您将它们都声明为零,那么您将始终得到零!

我已经实现了EC84B4 answer提出的相同方法,但我抽象了RecycleViewAdapter,并通过接口使其易于重用。

所以为了使用我的方法,你应该添加以下基类和接口到你的项目:

1)为适配器提供数据的接口(泛型类型T的集合,以及泛型类型P的附加参数(如果需要))

public interface IRecycleViewListHolder<T,P>{
P getAdapterParameters();
T getItem(int position);
int getSize();
}

2)用于绑定您的项目(标题/项目)的工厂:

public interface IViewHolderBinderFactory<T,P> {
void bindView(RecyclerView.ViewHolder holder, int position,IRecycleViewListHolder<T,P> dataHolder);
}

3) viewHolders的工厂(头/项):

public interface IViewHolderFactory {
RecyclerView.ViewHolder provideInflatedViewHolder(int viewType, LayoutInflater layoutInflater,@NonNull ViewGroup parent);
}

4) Adapter with Header的基类:

public class RecycleViewHeaderBased<T,P> extends RecyclerView.Adapter<RecyclerView.ViewHolder>{


public final static int HEADER_TYPE = 1;
public final static int ITEM_TYPE = 0;
private final IRecycleViewListHolder<T, P> dataHolder;
private final IViewHolderBinderFactory<T,P> binderFactory;
private final IViewHolderFactory viewHolderFactory;


public RecycleViewHeaderBased(IRecycleViewListHolder<T,P> dataHolder, IViewHolderBinderFactory<T,P> binderFactory, IViewHolderFactory viewHolderFactory) {
this.dataHolder = dataHolder;
this.binderFactory = binderFactory;
this.viewHolderFactory = viewHolderFactory;
}


@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
return viewHolderFactory.provideInflatedViewHolder(viewType,layoutInflater,parent);
}


@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
binderFactory.bindView(holder, position,dataHolder);
}


@Override
public int getItemViewType(int position) {
if(position == 0)
return HEADER_TYPE;
return ITEM_TYPE;
}


@Override
public int getItemCount() {
return dataHolder.getSize()+1;
}
}

使用的例子:

1) irecycleviewlisholder实现:

public class AssetTaskListData implements IRecycleViewListHolder<Map.Entry<Integer, Integer>, GroupedRecord> {
private List<Map.Entry<Integer, Integer>> assetCountList;
private GroupedRecord record;


public AssetTaskListData(Map<Integer, Integer> assetCountListSrc, GroupedRecord record) {
this.assetCountList =  new ArrayList<>();
for(Object  entry: assetCountListSrc.entrySet().toArray()){
Map.Entry<Integer,Integer> entryTyped = (Map.Entry<Integer,Integer>)entry;
assetCountList.add(entryTyped);
}
this.record = record;
}


@Override
public GroupedRecord getAdapterParameters() {
return record;
}


@Override
public Map.Entry<Integer, Integer> getItem(int position) {
return assetCountList.get(position-1);
}


@Override
public int getSize() {
return assetCountList.size();
}
}

2) IViewHolderBinderFactory实现:

public class AssetTaskListBinderFactory implements IViewHolderBinderFactory<Map.Entry<Integer, Integer>, GroupedRecord> {
@Override
public void bindView(RecyclerView.ViewHolder holder, int position, IRecycleViewListHolder<Map.Entry<Integer, Integer>, GroupedRecord> dataHolder) {
if (holder instanceof AssetItemViewHolder) {
Integer assetId = dataHolder.getItem(position).getKey();
Integer assetCount = dataHolder.getItem(position).getValue();
((AssetItemViewHolder) holder).bindItem(dataHolder.getAdapterParameters().getRecordId(), assetId, assetCount);
} else {
((AssetHeaderViewHolder) holder).bindItem(dataHolder.getAdapterParameters());
}
}
}

3) IViewHolderFactory实现:

public class AssetTaskListViewHolderFactory implements IViewHolderFactory {
private IPropertyTypeIconMapper iconMapper;
private ITypeCaster caster;


public AssetTaskListViewHolderFactory(IPropertyTypeIconMapper iconMapper, ITypeCaster caster) {
this.iconMapper = iconMapper;
this.caster = caster;
}


@Override
public RecyclerView.ViewHolder provideInflatedViewHolder(int viewType, LayoutInflater layoutInflater, @NonNull ViewGroup parent) {
if (viewType == RecycleViewHeaderBased.HEADER_TYPE) {
AssetBasedHeaderItemBinding item = DataBindingUtil.inflate(layoutInflater, R.layout.asset_based_header_item, parent, false);
return new AssetHeaderViewHolder(item.getRoot(), item, caster);
}
AssetBasedListItemBinding item = DataBindingUtil.inflate(layoutInflater, R.layout.asset_based_list_item, parent, false);
return new AssetItemViewHolder(item.getRoot(), item, iconMapper, parent.getContext());
}
}

4)派生适配器

public class AssetHeaderTaskListAdapter extends RecycleViewHeaderBased<Map.Entry<Integer, Integer>, GroupedRecord> {
public AssetHeaderTaskListAdapter(IRecycleViewListHolder<Map.Entry<Integer, Integer>, GroupedRecord> dataHolder,
IViewHolderBinderFactory binderFactory,
IViewHolderFactory viewHolderFactory) {
super(dataHolder, binderFactory, viewHolderFactory);
}
}

5)实例化适配器类:

private void setUpAdapter() {
Map<Integer, Integer> objectTypesCountForGroupedTask = groupedTaskRepository.getObjectTypesCountForGroupedTask(this.groupedRecordId);
AssetTaskListData assetTaskListData = new AssetTaskListData(objectTypesCountForGroupedTask, getGroupedRecord());
adapter = new AssetHeaderTaskListAdapter(assetTaskListData,new AssetTaskListBinderFactory(),new AssetTaskListViewHolderFactory(iconMapper,caster));
assetTaskListRecycler.setAdapter(adapter);
}

注:: AssetItemViewHolder, AssetBasedListItemBinding等,我的应用程序自己的结构应该由你自己交换,为你自己的目的。

如果你想在多个列表中容易地重用头文件,请查看recyclerview库的1.2.0版本。它引入了ConcatAdapter类,它将多个适配器连接成一个适配器。因此,你可以创建一个头适配器,并轻松地将它与任何其他适配器结合起来,比如:

myRecyclerView.adapter = ConcatAdapter(headerAdapter, listAdapter)

发布的文章包含一个如何使用ConcatAdapter在页眉和页脚显示加载进度的示例。

在我发布这个答案的时候,库的1.2.0版本处于alpha阶段,所以api可能会改变。你可以检查状态在这里

我知道这是一个老问题,但我还是想提供一个答案。

有一个ConcatAdapter (这里的文档)可以帮你解决这个问题。 你所要做的就是定义你的头视图布局,并用一个项目创建一个虚拟适配器 在Kotlin中,它只需要几行

class HeaderAdapter(private val inflater: LayoutInflater) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = object : RecyclerView.ViewHolder(inflater.inflate(yourViewLayoutId, parent, false)) {}


override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {}


override fun getItemCount() = 1
}

之后,你所要做的就是将你的头适配器和原始适配器添加到ConcatAdapter

recyclerView.adapter = ConcatAdapter(headerAdapter, adapter)

参见文档了解更多细节和高级情况。

这就是我的解决方案,它使用了一个抽象类来扩展适配器

abstract class HeaderAdapter<T : RecyclerView.ViewHolder>(headerView: View)
: RecyclerView.Adapter<RecyclerView.ViewHolder>() {


var headerView: View = headerView
set(value) {
notifyItemChanged(HEADER_POSITION)
field = value
}
    

companion object {
const val TYPE_HEADER = -1
const val HEADER_POSITION = 0
}


abstract val itemCountWithoutHeader: Int
abstract fun getViewHolder(parent: ViewGroup, viewType: Int): T
abstract fun onBindItemViewHolder(holder: T, position: Int)


open fun getItemsItemViewType(position: Int): Int {
return super.getItemViewType(position)
}


final override fun getItemCount(): Int = itemCountWithoutHeader + 1


final override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == TYPE_HEADER) HeaderHolder(headerView) else getViewHolder(parent, viewType)
}


final override fun getItemViewType(position: Int): Int {
return if (position == HEADER_POSITION) TYPE_HEADER else getItemsItemViewType(position)
}


final override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (position != HEADER_POSITION) onBindItemViewHolder(holder as T, position-1)
}


class HeaderHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
}