是否有与 RecyclerView 等效的 addHeaderView?
Posted
技术标签:
【中文标题】是否有与 RecyclerView 等效的 addHeaderView?【英文标题】:Is there an addHeaderView equivalent for RecyclerView? 【发布时间】:2014-12-19 06:35:01 【问题描述】:我正在寻找与 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_
android:layout_
tools:context="co.testapp.fragments.CommentFragment"
android:background="@color/white">
<RelativeLayout
android:layout_
android:layout_
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/list_recylclerview"
android:layout_
android:layout_ />
</RelativeLayout>
</RelativeLayout>
cmets 行布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_
android:layout_ android:layout_margin="10dp"
android:background="@color/white">
<!--Profile Picture-->
<ImageView
android:layout_
android:layout_
android:id="@+id/profile_picture"
android:background="@color/blue_testapp"/>
<!--Name-->
<TextView
android:layout_
android:layout_
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_
android:layout_
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_
android:layout_>
<ImageView
android:layout_
android:layout_
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。看起来像this。如果我强制使用其他布局,它看起来像 this:
【问题讨论】:
Android 5.0 - Add header/footer to a RecyclerView的可能重复 由于这是问题here的重复,我发布了我的答案there: 一个优雅的解决方案:***.com/questions/33579800/… 我迟到了,但你的代码有一些更正。更改 private static final int HEADER = 0; to private static final int HEADER = 1; 【参考方案1】:没有像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);
link to gist -> here
【讨论】:
一切似乎都很好,应该可以工作,但是让回收站视图 MATCH_PARENT 看看是否有任何变化。如果可能的话,也可以让回收者查看该布局的根(它有利于性能)。 枪的太阳,清理recyclerview并添加匹配父工作......非常感谢您的帮助!我会尽快给你投票。private String getItem(int position) return data[position + 1];
导致 NPE。由于我们的位置已经增加了 1,由于标题,我们必须减去 1 才能从我们的 data[] 中获得正确的项目。 private String getItem(int position) return data[position - 1];
如果您的网格只是一个简单的网格,只需使用 LinearLayoutManager 并在一行中显示项目。我看到谷歌在他们的一些应用程序中使用它。
@nsL 您可以使用此方法并另外为GridLayoutManager
添加setSpanSizeLookup
,因此您的标题将占据所有列【参考方案2】:
简单且可重复使用ItemDecoration
静态标头可以很容易地添加ItemDecoration
和而无需任何进一步的更改。
// add the decoration. done.
HeaderDecoration headerDecoration = new HeaderDecoration(/* init */);
recyclerView.addItemDecoration(headerDecoration);
装饰也是可重复使用的,因为根本不需要修改适配器或RecyclerView
。
下面提供的示例代码需要将视图添加到顶部,该视图可以像其他所有内容一样膨胀。它可能看起来像这样:
为什么是静态?
如果您只需要显示文本和图像,此解决方案适合您 - 不可能进行按钮或查看寻呼机等用户交互,因为它只会被绘制到您的列表顶部。
空列表处理
如果没有要装饰的视图,则不会绘制装饰。您仍然需要自己处理一个空列表。 (一种可能的解决方法是向适配器添加一个虚拟项目。)
代码
您可以找到完整的源代码 here on 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
是对列表项的附加绘图。在这种情况下,装饰被绘制到第一个项目的顶部。
视图被测量和布局,然后被绘制到第一个项目的顶部。如果添加了视差效果,它也会被裁剪到正确的范围内。
【讨论】:
看起来是一个优雅的解决方案,但是,我有一个问题:我应该什么时候调用 recyclerView.addItemDecoration ? 谢谢,效果很好!使用此方法的唯一缺点是您需要在 RecyclerView 中至少有 1 行。 如何为header设置onClickListener? 我收到以下异常:java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.support.v7.widget.RecyclerView$ViewHolder.shouldIgnore()' on an null object reference 有什么方法可以访问装饰中的视图吗?我想动态设置文本。【参考方案3】:请随意使用我的库,here。
它让您只需一个简单的方法调用即可为使用LinearLayoutManager
或GridLayoutManager
的任何RecyclerView
创建标头View
。
【讨论】:
如何改变页眉高度? 我用它在底部显示页眉,或者你可以说它作为页脚,但我遇到一个问题,当我的视图第一次加载时,它显示最后一个位置,所有列表项都反向显示命令。 @blipinsk 您认为您所描述的问题可能与此有关:github.com/blipinsk/RecyclerViewHeader/issues/16? Lipinski 已停用此库并建议改用此库:github.com/Karumi/HeaderRecyclerView 确切地说,我正在退休这个库。我仍在为它提供温和的支持,但实际上我建议使用专用适配器(例如来自 Karumi 的适配器)。【参考方案4】:将向您展示如何在 Recycler 视图中创建包含项目的标题。
第 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_
android:layout_ />
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_
android:layout_
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_
android:layout_ />
</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_
android:layout_>
<android.support.v7.widget.CardView
android:layout_
android:layout_
app:cardElevation="1dp">
<TextView
android:id="@+id/txtName"
android:text="abc"
android:layout_
android:layout_ />
</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);
步骤 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() 函数为标题和列表项动态生成数据。
【讨论】:
最佳答案之一。我在 navigationView 中使用它作为导航菜单 这是一个很好的简单答案 - 我想不出一个技巧来轻松地将 2 个数据项组合成一个 ListItem - 继承使它变得容易且可实现 - 呵呵! @Anshul Aggarwal 感谢您的回答。我有个问题。你有一个像 ContentItem.java 这样的类,它扩展了 ListItem。然后将 ListItem 数据模型传递给 recyclerAdapter。当我想访问 ContentItem 中的某些属性时,它无法识别。我该怎么办 ?例如,在 ContentItem 类中,我必须在适配器中为我的 textview 设置像 Price 这样的属性。【参考方案5】:如果您希望标题在多个列表中轻松重用,请查看 recyclerview
库的版本 1.2.0。它引入了ConcatAdapter 类,它将多个适配器连接成一个。因此,您可以创建一个标头适配器并轻松地将其与任何其他适配器组合,例如:
myRecyclerView.adapter = ConcatAdapter(headerAdapter, listAdapter)
announcement article 包含一个示例,如何使用 ConcatAdapter
在页眉和页脚中显示加载进度。
目前,当我发布此答案时,库的版本 1.2.0
处于 alpha 阶段,因此 api 可能会更改。您可以查看状态here。
【讨论】:
我知道一直滚动到这里是值得的。所有过时的答案都让我头疼【参考方案6】:您可以使用库SectionedRecyclerViewAdapter 来实现它,它具有“Sections”的概念,其中Section 具有页眉、页脚和内容(项目列表)。在您的情况下,您可能只需要一个部分,但您可以有很多:
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);
【讨论】:
这很容易使用,但是如何将它用于水平滚动.. 每当我将方向更改为水平时,整个 recyclerview 都会更改为水平滚动,但我希望项目部分仅是水平滚动.. 请帮助我出去做【参考方案7】:您可以将标题和 RecyclerView 放在 NestedScrollView 中:
<android.support.v4.widget.NestedScrollView
android:layout_
android:layout_
>
<LinearLayout
android:layout_
android:layout_
android:orientation="vertical"
>
<include
layout="@layout/your_header"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/list_recylclerview"
android:layout_
android:layout_
/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
为了让滚动正常工作,您需要在 RecyclerView 上禁用嵌套滚动:
myRecyclerView.setNestedScrollingEnabled(false);
【讨论】:
或使用 android:nestedScrollingEnabled="false" android:nestedScrollingEnabled="false" 需要 api 级别 21,这很糟糕。无论如何,伟大的解决方案!同样,您必须添加另一个页脚甚至可能是第二个 recyclerview? 永远不要这样做。这似乎工作正常。但它实际上所做的是将滚动传递给 srollView。所以 recyclerView 不会自己做任何事情。没有视图回收。如果你的 recyclerView 中有太多项目,应用程序将会崩溃,因为现在它就像一个简单的滚动视图。 @VipulKumar 的评论完全正确。当您在另一个滚动视图中使用 recyclerview 时,不会有任何回收,它会创建所有项目。 如果你在recyclerView中的物品少于10个,你可以这样做。?【参考方案8】:基于this post,我创建了一个RecyclerView.Adapter 的子类,它支持任意数量的页眉和页脚。
https://gist.github.com/mheras/0908873267def75dc746
虽然看起来是个解决办法,但我也觉得这东西应该由LayoutManager来管理。不幸的是,我现在需要它,而且我没有时间从头开始实现 StaggeredGridLayoutManager(甚至从它扩展)。
我仍在测试它,但如果您愿意,可以尝试一下。如果您发现任何问题,请告诉我。
【讨论】:
【参考方案9】:Native API 没有这样的“addHeader”特性,但有“addItem”的概念。
我能够在我的 FlexibleAdapter 项目中包含页眉的这一特定功能和页脚扩展。我称之为可滚动的页眉和页脚。
它们的工作原理如下:
可滚动的页眉和页脚是与所有其他项目一起滚动的特殊项目,但它们不属于主要项目(业务项目),它们始终由主要项目旁边的适配器处理。这些项目始终位于第一个和最后一个位置。
关于它们有很多话要说,最好阅读详细的wiki page。
此外,FlexibleAdapter 允许您创建标题/部分,您还可以将它们粘住和其他数十种功能,如可扩展项目、无限滚动、UI 扩展等......全部在一个库中!
【讨论】:
【参考方案10】:还有一种解决方案可以涵盖上述所有用例:CompoundAdapter: https://github.com/negusoft/CompoundAdapter-android
您可以创建一个 AdapterGroup 来保存您的适配器,以及一个带有单个项目的适配器来表示标题。代码简单易读:
AdapterGroup adapterGroup = new AdapterGroup();
adapterGroup.addAdapter(SingleAdapter.create(R.layout.header));
adapterGroup.addAdapter(new CommentAdapter(...));
recyclerView.setAdapter(adapterGroup);
AdapterGroup 也允许嵌套,因此对于带有部分的适配器,您可以为每个部分创建一个 AdapterGroup。然后将所有部分放在根 AdapterGroup 中。
【讨论】:
【参考方案11】:也许将 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_
android:layout_>
<android.support.design.widget.AppBarLayout
android:layout_
android:layout_
app:elevation="0dp">
<View
android:id="@+id/header"
android:layout_
android:layout_
app:layout_scrollFlags="scroll" />
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/list"
android:layout_
android:layout_
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
【讨论】:
这样做的问题是它总是允许滚动 AppBarLayout 的高度,即使列表比屏幕短。要解决此问题,您可以按照here 的说明进行操作【参考方案12】:HeaderView 依赖于 LayoutManager。默认的 LayoutManager 都不支持这一点,并且可能不会。 ListView 中的 HeaderView 会产生很多复杂性,但没有任何显着的好处。
我建议创建一个基础适配器类,如果提供的话,它会为 Headers 添加项目。不要忘记覆盖 notify* 方法以根据是否存在标头正确偏移它们。
【讨论】:
您是否可以指出一个使用带有基本适配器的回收器视图的示例?谢谢! 列表中的页眉/页脚有一个非常重要的好处:您可以将其滚动到视图之外。请参阅this example where the amount of visible rows almost doubles as soon as the pinguins are out,我不知道有任何其他方法可以做到这一点,但ListView.addHeaderView
或这个问题的答案。
我不认为我做对了。如果它是适配器中的第一项,为什么不能像示例中的那样滚动?
您不能覆盖 notify* 方法,因为它们被标记为 final。
嗯,抱歉,我没有检查。相反,您可以创建一个适配器包装器,它将一个可观察对象添加到包装的适配器并从其自身调度转移的事件。【参考方案13】:
First - extends RecyclerView.Adapter<RecyclerView.ViewHolder>
public class MenuAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
After - 覆盖方法 getItemViewTpe ***更重要
@Override
public int getItemViewType(int position)
return position;
CreateViewHolder 方法
@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);
BindViewHolder 方法
@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]);
infinish 实现 ViewHolders 类静态
public static class MenuViewHolder extends RecyclerView.ViewHolder
public static class MenuLeftHeaderViewHolder extends RecyclerView.ViewHolder
【讨论】:
【参考方案14】:这里是recyclerview的一些物品装饰
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();
【讨论】:
【参考方案15】:出于个人目的,我基于@hister's 做了一个实现,但使用了继承。
我在一个抽象超类HeadingableRecycleAdapter
中隐藏了实现细节机制(如itemCount
加1,position
减1),通过
从适配器实现所需的方法,如 onBindViewHolder
、getItemViewType
和 getItemCount
,使这些方法成为最终方法,并向客户端提供具有隐藏逻辑的新方法:
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)));
【讨论】:
【参考方案16】:我知道这是一个老问题,但还是想提供一个答案。
有一个ConcatAdapter
(docs here) 可以为您解决问题。
您所要做的就是定义您的标题视图布局并创建一个带有单个项目的虚拟适配器
在 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)
查看documentation了解更多详情和高级案例。
【讨论】:
【参考方案17】: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;
【讨论】:
【参考方案18】:您可以创建 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();
...
【讨论】:
【参考方案19】:已经有几年了,但以防万一以后有人读到这篇文章......
使用上面的代码,只显示标题布局,因为viewType总是0。
问题出在常量声明中:
private static final int HEADER = 0;
private static final int OTHER = 0; <== bug
如果您将它们都声明为零,那么您将永远为零!
【讨论】:
【参考方案20】:我已经实现了 EC84B4 答案提出的相同方法,但是我抽象了 RecycleViewAdapter 并通过接口使其易于重用。
因此,为了使用我的方法,您应该在项目中添加以下基类和接口:
1) 为 Adapter 提供数据的接口(泛型 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) 带Header的Adapter基类:
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) IRecycleViewListHolder 实现:
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);
P.S.:AssetItemViewHolder、AssetBasedListItemBinding 等我的应用程序拥有应该由您自己交换的结构,以供您自己使用。
【讨论】:
以上是关于是否有与 RecyclerView 等效的 addHeaderView?的主要内容,如果未能解决你的问题,请参考以下文章
是否有与 FontUtilities.getCompositeFontUIResource(Font) 等效的非专有等效项?