RecyclerView 页眉和页脚

Posted

技术标签:

【中文标题】RecyclerView 页眉和页脚【英文标题】:RecyclerView header and footer 【发布时间】:2020-11-30 00:20:28 【问题描述】:

也许以前有人问过这个问题,但我似乎找不到准确的答案或解决方案。我开始使用 RecyclerView,并使用 LinearLayoutManager 实现它。现在我想添加自定义页眉和页脚项目,它们与我的 RecyclerView 中的其余项目不同。页眉和页脚不应该是粘性的,我希望它们与其余项目一起滚动。有人可以指出一些例子如何做到这一点或只是分享想法。我会非常感激。谢谢

【问题讨论】:

见 int RecyclerView.Adapter.getItemViewType(int position) 你可以发布一些代码示例吗,我不知道该怎么做...... 覆盖该方法,对于位置 == 0 返回 0 对于任何其他位置返回 1,现在 onCreateViewHolder 将使用两种不同的视图类型调用:0 用于标题视图,1 用于“正常”视图 看到这个答案它有一个例子***.com/a/26573338/2127203 thx @hister,我来看看例子... 【参考方案1】:

在你的适配器中添加这个类:

private class VIEW_TYPES 
        public static final int Header = 1;
        public static final int Normal = 2;
        public static final int Footer = 3;

然后像这样覆盖以下方法:

@Override
public int getItemViewType(int position) 

    if(items.get(position).isHeader)
        return VIEW_TYPES.Header;
    else if(items.get(position).isFooter)
        return VIEW_TYPES.Footer;
    else
        return VIEW_TYPES.Normal;


现在在 onCreateViewHolder 方法中根据视图类型扩展你的布局::

@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) 

    View rowView;

    switch (i) 

        case VIEW_TYPES.Normal:
            rowView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.normal, viewGroup, false);
            break;
        case VIEW_TYPES.Header:
            rowView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.header, viewGroup, false);
            break;
        case VIEW_TYPES.Footer:
            rowView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.footer, viewGroup, false);
            break;
        default:
            rowView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.normal, viewGroup, false);
            break;
    
    return new ViewHolder (rowView);

现在在 onBindViewHolder 方法中根据视图持有者绑定你的布局:

@Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) 

        int viewType = getItemViewType(position);

        switch(viewType) 

            case VIEW_TYPES.Header: // handle row header
                break;
            case VIEW_TYPES.Footer: // handle row footer
                break;
            case VIEW_TYPES.Normal: // handle row item
                break;

        

    

希望这能有所帮助。

【讨论】:

我可以很好地实现页眉,但是当我添加页脚时,尽管我将页脚属性指定为“layout_alignParentBottom = true”,但它会在 Recycler 视图的第一项之后插入。知道可能是什么原因吗? 嗨,页眉和页脚也是项目,所以你必须先添加页眉(检查它是否在第一个位置)然后添加你的项目,最后在最后一个位置添加页脚跨度> 感谢您提供此解决方案,但您能告诉我方法名称“isHeader”和“isFooter”在哪里吗? @布朗克斯 嗨@RonakJoshi isHeader 和 isFooter 不是方法,它们是您在列表项目中的变量。例如,您有一个名为 MyItem 的类,构造函数是 public MyItem(Object myObject, boolean isHeader, boolean isFooter) @Bronx 或添加完整代码,因为我仍然遇到 santafebound 遇到的同样问题。【参考方案2】:

使用 ItemDecorations 非常简单,无需修改任何其他代码:

recyclerView.addItemDecoration(new HeaderDecoration(this,
                               recyclerView,  R.layout.test_header));

预留一些绘制空间,将你想要绘制的布局膨胀,然后在预留空间绘制。

装饰代码:

public class HeaderDecoration extends RecyclerView.ItemDecoration 

    private View mLayout;

    public HeaderDecoration(final Context context, RecyclerView parent, @LayoutRes int resId) 
        // inflate and measure the layout
        mLayout = LayoutInflater.from(context).inflate(resId, parent, false);
        mLayout.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
    


    @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
        mLayout.layout(parent.getLeft(), 0, parent.getRight(), mLayout.getMeasuredHeight());
        for (int i = 0; i < parent.getChildCount(); i++) 
            View view = parent.getChildAt(i);
            if (parent.getChildAdapterPosition(view) == 0) 
                c.save();
                final int height = mLayout.getMeasuredHeight();
                final int top = view.getTop() - height;
                c.translate(0, top);
                mLayout.draw(c);
                c.restore();
                break;
            
        
    

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) 
        if (parent.getChildAdapterPosition(view) == 0) 
            outRect.set(0, mLayout.getMeasuredHeight(), 0, 0);
         else 
            outRect.setEmpty();
        
    

【讨论】:

如何在项目装饰器中添加点击处理?我必须添加一个按钮作为标题,它可以在您的代码中看到,但我无法在单击时添加 findViewById 返回 null @chin87 ...因为mLayout 是一个视图 - 只需将其添加到视图中?添加 getter 或修改构造函数。 (这取决于你在哪里调用 findViewById...因为视图既不附加到 recyclerView 也不附加到片段) 如何使用它来添加页脚?当 LinearLayoutManager 是水平的时候它也能工作吗? @androiddeveloper 你将不得不修改或调整代码,这个示例是更多的概念证明。要将其用作页脚,您将检查最后一项而不是 position == 0 并在下方而不是上方绘制,对于水平使用它,您还需要对其进行调整,将绘图修改为左/右而不是上方 @DavidMedenjak 太糟糕了。您是否知道拥有所有内容的回购协议?除了水平和垂直的每个页眉和页脚需要额外的代码之外,使用您的解决方案是否有任何缺点【参考方案3】:

如果您只需要一个空白页眉和页脚,这里有一个非常简单的方法来实现这一点(用 Kotlin 编写):

class HeaderFooterDecoration(private val headerHeight: Int, private val footerHeight: Int) : RecyclerView.ItemDecoration() 
    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) 
        val adapter = parent.adapter ?: return
        when (parent.getChildAdapterPosition(view)) 
            0 -> outRect.top = headerHeight
            adapter.itemCount - 1 -> outRect.bottom = footerHeight
            else -> outRect.set(0, 0, 0, 0)
        
    

这样称呼:

recyclerView.addItemDecoration(HeaderFooterDecoration(headerHeightPx, footerHeightPx))

【讨论】:

我并不经常在这里找到最佳解决方案。简明扼要,可读性强,完成工作。满分肖恩:D 一个很好的例子,用源代码创建带有页眉和页脚的 RecyclerView loopwiki.com/ui-ux-design/…【参考方案4】:

您可以使用此 GitHub] 库以最简单的方式将页眉或页脚添加到您的 RecyclerView

您需要在项目中添加HFRecyclerView 库,或者您也可以从 Gradle 中获取它:

compile 'com.mikhaellopez:hfrecyclerview:1.0.0'

这个库是基于@hister的作品

这是图像中的结果:

【讨论】:

【参考方案5】:

recyclerview:1.2.0 引入ConcatAdapter

ConcatAdapter 是一个新的 RecyclerView Adapter,可以线性组合多个适配器。

如何使用ConcatAdapter?

将以下依赖项添加到您的 build.gradle 文件中

androidx.recyclerview:recyclerview:1.2.0-alpha04

然后,如果您有多个适配器,您可以使用轻松合并它们

  MyAdapter adapter1 = ...;
 AnotherAdapter adapter2 = ...;
 ConcatAdapter merged = new ConcatAdapter(adapter1, adapter2);
 recyclerView.setAdapter(merged);

对于上面的示例,ConcatAdapter 将显示来自适配器 1 的项目,然后是适配器 2。

Here你可以找到完整的文档。

找到完整的工作示例here。

阅读this 文章了解更多信息。

Here你可以找到源代码。

【讨论】:

【参考方案6】:

这里是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();
    

      

【讨论】:

您的代码非常方便,但我尝试通过将 parent.getChildAdapterPosition(view) == 0 更改为 parent.getChildAdapterPosition(view) == parent.getAdapter().getItemCount( ) 但它没有工作。嗯,它适用于介于两者之间的任何其他职位,但不是最后一个职位,您能建议任何更改吗? 非常感谢@Trunks!就我而言,我以这种方式添加了一个仅包含 TextView 的简单标题:recyclerView.addItemDecoration(new HeaderItemDecoration(aTextView), 0). @AnuragBhalekar 它是 parent.getChildAdapterPosition(view) == (parent.getAdapter().getItemCount()-1)【参考方案7】:

我建议不要自定义 rv 适配器。

保持原样...在您的 rv 项目布局中,只需添加带有布局的页脚并设置可见性消失。

然后,当您到达适配器中的最后一项时...使其可见。

当您尝试此操作时,请确保将其添加到您的 rv 适配器中。

   @Override
    public void onBindViewHolder(final PersonViewHolder personViewHolder, int i) 
           if(i==List.size()) // Last item in recycle view
           personViewHolder.tv_footer.setVisibility(VISIBLE);// Make footer visible now 

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

对标题做同样的事情。 这里 i==0 // 列表的第一项

对我来说最简单的解决方案。

【讨论】:

好的,你的方法很简单。但是,大部分时间都将视图设置为View.GONE,这不是浪费内存吗? @Kathir...仅当它在屏幕区域可见时才被处理...其他时候它被处理并保存在背景中,就像通常要显示的记录数组一样.. 谢谢@Raktim。我发现了一个相关的问题answering。简而言之,与没有该视图的内容相比,它仍然会占用内存,但它比View.INVISIBLE 更好。另外,请阅读 this 以了解何时改用 View.INVISIBLE【参考方案8】:

点击here。 我做了 RecyclerView.Adapter 的扩展。易于添加页眉和页脚。

class HFAdapter extends HFRecyclerViewAdapter<String, HFAdapter.DataViewHolder>

    public HFAdapter(Context context) 
        super(context);
    

    @Override
    public DataViewHolder onCreateDataItemViewHolder(ViewGroup parent, int viewType) 
        View v = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.data_item, parent, false);
        return new DataViewHolder(v);
    

    @Override
    public void onBindDataItemViewHolder(DataViewHolder holder, int position) 
        holder.itemTv.setText(getData().get(position));
    

    class DataViewHolder extends RecyclerView.ViewHolder
        TextView itemTv;
        public DataViewHolder(View itemView) 
            super(itemView);
            itemTv = (TextView)itemView.findViewById(R.id.itemTv);
        
    


//add header
View headerView = LayoutInflater.from(this).inflate(R.layout.header, recyclerView, false);
hfAdapter.setHeaderView(headerView);
//add footer
View footerView = LayoutInflater.from(this).inflate(R.layout.footer, recyclerView, false);
hfAdapter.setFooterView(footerView);

//remove
hfAdapter.removeHeader();
hfAdapter.removeFooter();

【讨论】:

【参考方案9】:

对于 Recyclerview 中带有 GridView 项目的分段 LinearView 标题:-

查看SectionedGridRecyclerViewAdapter

【讨论】:

【参考方案10】:

另一种方法是将标题和 reyclerview 包装在 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" />

【讨论】:

我还没有找到创建页脚的方法。【参考方案11】:

GroupAdapter 可能是你想要的。

一个专门的 RecyclerView.Adapter,它显示来自 RecyclerView.Adapter 序列的数据。序列是静态的,但每个适配器可以在零个或多个项目视图中呈现。子适配器可以安全地使用 ViewType。另外,我们可以像ListView一样添加HeaderView或者addFooterView。

【讨论】:

【参考方案12】:

您可以使用库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 页眉和页脚的主要内容,如果未能解决你的问题,请参考以下文章

如何将 DiffUtil.Callback 与具有页眉和页脚的 RecyclerView 一起使用?

如何在Excel中设置页眉和页脚

打印网页上的页眉和页脚如何设置

如何使页眉和页脚始终固定?

在 Elementor 中删除页眉和页脚

UICollectionView 页眉和页脚视图