Android 5.0 - 给RecyclerView添加页眉/页脚

141

我花了一些时间尝试为RecyclerView添加一个标题,但失败了。

目前这就是我得到的结果:

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...

    layouManager = new LinearLayoutManager(getActivity());
    recyclerView.setLayoutManager(layouManager);

    LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    headerPlaceHolder = inflater.inflate(R.layout.view_header_holder_medium, null, false);
    layouManager.addView(headerPlaceHolder, 0);

   ...
}

LayoutManager 是处理 RecyclerView 子项排布的对象。由于我找不到任何 addHeaderView(View view) 方法,因此我决定使用 LayoutManageraddView(View view, int position) 方法,并将我的标题视图添加到第一个位置以充当标题。

这就是事情变得更加丑陋的地方:

java.lang.NullPointerException: Attempt to read from field 'android.support.v7.widget.RecyclerView$ViewHolder android.support.v7.widget.RecyclerView$LayoutParams.mViewHolder' on a null object reference
    at android.support.v7.widget.RecyclerView.getChildViewHolderInt(RecyclerView.java:2497)
    at android.support.v7.widget.RecyclerView$LayoutManager.addViewInt(RecyclerView.java:4807)
    at android.support.v7.widget.RecyclerView$LayoutManager.addView(RecyclerView.java:4803)
    at com.mathieumaree.showz.fragments.CategoryFragment.setRecyclerView(CategoryFragment.java:231)
    at com.mathieumaree.showz.fragments.CategoryFragment.access$200(CategoryFragment.java:47)
    at com.mathieumaree.showz.fragments.CategoryFragment$2.success(CategoryFragment.java:201)
    at com.mathieumaree.showz.fragments.CategoryFragment$2.success(CategoryFragment.java:196)
    at retrofit.CallbackRunnable$1.run(CallbackRunnable.java:41)
    at android.os.Handler.handleCallback(Handler.java:739)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:135)
    at android.app.ActivityThread.main(ActivityThread.java:5221)
    at java.lang.reflect.Method.invoke(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:372)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)

在尝试调用addView(View view)时,我收到了多个NullPointerExceptions,这发生在Activity创建的不同阶段(甚至尝试在一切都设置好了,包括适配器数据之后添加视图),我意识到我不知道这是否是正确的方法(并且看起来并不是)。

PS: 另外,如果有一个能够处理GridLayoutManager以及LinearLayoutManager的解决方案,那将不胜感激!


看一下这个:https://dev59.com/3eo6XIcBkEYKwwoYIAgL#26573338 - EC84B4
问题出在适配器代码中。这意味着,在onCreateViewHolder函数中,你以某种方式返回了空的viewholder。 - Neo
有一个很好的方法可以将标题添加到StaggeredGridLayout中:http://stackoverflow.com/questions/42202735/how-to-put-main-cell-on-the-top-of-staggeredgridlayoutrecycler-view/42203237#42203237 - Sirop4ik
请查看此文章 - Amar Yadav
现在我认为最好的解决方案是concatAdapter!看一下这个链接https://developer.android.com/reference/androidx/recyclerview/widget/ConcatAdapter - hamid Mahmoodi
14个回答

129

我必须给我的RecyclerView添加页脚,这里我分享了我的代码片段,因为我认为它可能有用。请查看代码内部的注释以更好地理解整个流程。

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;

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

    private static final int FOOTER_VIEW = 1;
    private ArrayList<String> data; // Take any list that matches your requirement.
    private Context context;

    // Define a constructor
    public RecyclerViewWithFooterAdapter(Context context, ArrayList<String> data) {
        this.context = context;
        this.data = data;
    }

    // Define a ViewHolder for Footer view
    public class FooterViewHolder extends ViewHolder {
        public FooterViewHolder(View itemView) {
            super(itemView);
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // Do whatever you want on clicking the item
                }
            });
        }
    }

    // Now define the ViewHolder for Normal list item
    public class NormalViewHolder extends ViewHolder {
        public NormalViewHolder(View itemView) {
            super(itemView);

            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // Do whatever you want on clicking the normal items
                }
            });
        }
    }

    // And now in onCreateViewHolder you have to pass the correct view
    // while populating the list item.

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View v;

        if (viewType == FOOTER_VIEW) {
            v = LayoutInflater.from(context).inflate(R.layout.list_item_footer, parent, false);
            FooterViewHolder vh = new FooterViewHolder(v);
            return vh;
        }

        v = LayoutInflater.from(context).inflate(R.layout.list_item_normal, parent, false);

        NormalViewHolder vh = new NormalViewHolder(v);

        return vh;
    }

    // Now bind the ViewHolder in onBindViewHolder
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

        try {
            if (holder instanceof NormalViewHolder) {
                NormalViewHolder vh = (NormalViewHolder) holder;

                vh.bindView(position);
            } else if (holder instanceof FooterViewHolder) {
                FooterViewHolder vh = (FooterViewHolder) holder;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // Now the critical part. You have return the exact item count of your list
    // I've only one footer. So I returned data.size() + 1
    // If you've multiple headers and footers, you've to return total count
    // like, headers.size() + data.size() + footers.size()

    @Override
    public int getItemCount() {
        if (data == null) {
            return 0;
        }

        if (data.size() == 0) {
            //Return 1 here to show nothing
            return 1;
        }

        // Add extra view to show the footer view
        return data.size() + 1;
    }

    // Now define getItemViewType of your own.

    @Override
    public int getItemViewType(int position) {
        if (position == data.size()) {
            // This is where we'll add footer.
            return FOOTER_VIEW;
        }

        return super.getItemViewType(position);
    }

    // So you're done with adding a footer and its action on onClick.
    // Now set the default ViewHolder for NormalViewHolder

    public class ViewHolder extends RecyclerView.ViewHolder {
        // Define elements of a row here
        public ViewHolder(View itemView) {
            super(itemView);
            // Find view by ID and initialize here
        }

        public void bindView(int position) {
            // bindView() method to implement actions
        }
    }
}

上面的代码片段在 RecyclerView 中添加了一个页脚。您可以查看这个GitHub存储库来检查同时添加头和页脚的实现。


2
运行良好。应将其标记为正确答案。 - Naga Mallesh Maddali
1
这正是我所做的。但是如果我想让我的RecyclerView适应交错列表怎么办?第一个元素(标题)也将被交错。 :( - Neon Warge
非常好!只是一个想法,我们如何确信 return super.getItemViewType(position); 不会返回与 FOOTER_VIEW 相同的值? - Couitchy
2
int getItemViewType (int position) - 返回位置为position的item的视图类型,以便进行视图回收。此方法的默认实现返回0,假设适配器只有一个视图类型。与ListView适配器不同,类型不需要连续。考虑使用id资源来唯一标识item视图类型。- 来自文档。https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html - Reaz Murshed
9
许多人总是想做的事情,需要如此多的手工劳动。我简直不敢相信... - JohnyTex
显示剩余4条评论

32

非常简单的解决方案!

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

只需在 android.support.v4.widget.NestedScrollView 内添加LinearLayout(垂直)标题视图 + RecyclerView + 页脚视图即可。

看看这个:

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

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

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

        <android.support.v7.widget.RecyclerView
            android:id="@+id/list"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layoutManager="LinearLayoutManager"/>

        <View
            android:id="@+id/footer"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>
</android.support.v4.widget.NestedScrollView>

添加此代码以实现平滑滚动

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

这将导致所有的RV性能丧失,RV会尝试布局所有视图持有者,而不考虑RV的layout_height

推荐用于小型列表,如导航抽屉或设置等。


56
这是失去RecyclerView带来的所有优势的非常简单的方法——你会失去真正的回收和它所带来的优化。 - Marcin Koziński
1
我尝试了这段代码,但滚动不正常... 它变得太慢了.. 请建议是否可以为此做些什么。 - Nibha Jain
1
@NibhaJain 你可以尝试使用 https://gist.github.com/mheras/0908873267def75dc746(来自Mato的回答)。 显然,Marcin的评论是正确的,这不是长列表的解决方案,因为我们可能会失去RecycerView回收的所有优势。 - Nishant Shah
2
使用嵌套的ScrollView会导致RecyclerView缓存所有视图,如果RecyclerView的大小更大,则滚动和加载时间将增加。我建议不要使用这段代码。 - Tushar Saha
1
在 Twitter 的帖子中,Nick Butcher(他非常优秀且知识渊博)表示,如果列表非常小,则可能不是问题。这很公平,我同意这一点。但这仍然意味着您正在失去优化并且无法进行回收利用。但是,如果只是一个小列表,例如导航抽屉(使用您在答案中添加的相同示例),那么它可能还好,不会导致性能问题。(顺便说一句,对于导航抽屉,您也可以使用 Navigation View) - Marcin Koziński
显示剩余5条评论

25
我在Lollipop上也遇到了同样的问题,并创建了两种方法来包装Recyclerview适配器。其中一种方法很容易使用,但我不确定它在数据集变化时会表现如何。因为它包装了您的适配器,您需要确保在正确的适配器对象上调用像notifyDataSetChanged这样的方法。
另一种方法不应该有这样的问题。只需让您的常规适配器扩展此类,实现抽象方法即可。以下是它们: 代码片段 HeaderRecyclerViewAdapterV1
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;

/**
 * Created by sebnapi on 08.11.14.
 * <p/>
 * This is a Plug-and-Play Approach for adding a Header or Footer to
 * a RecyclerView backed list
 * <p/>
 * Just wrap your regular adapter like this
 * <p/>
 * new HeaderRecyclerViewAdapterV1(new RegularAdapter())
 * <p/>
 * Let RegularAdapter implement HeaderRecyclerView, FooterRecyclerView or both
 * and you are ready to go.
 * <p/>
 * I'm absolutely not sure how this will behave with changes in the dataset.
 * You can always wrap a fresh adapter and make sure to not change the old one or
 * use my other approach.
 * <p/>
 * With the other approach you need to let your Adapter extend HeaderRecyclerViewAdapterV2
 * (and therefore change potentially more code) but possible omit these shortcomings.
 * <p/>
 * TOTALLY UNTESTED - USE WITH CARE - HAVE FUN :)
 */
public class HeaderRecyclerViewAdapterV1 extends RecyclerView.Adapter {
    private static final int TYPE_HEADER = Integer.MIN_VALUE;
    private static final int TYPE_FOOTER = Integer.MIN_VALUE + 1;
    private static final int TYPE_ADAPTEE_OFFSET = 2;

    private final RecyclerView.Adapter mAdaptee;


    public HeaderRecyclerViewAdapterV1(RecyclerView.Adapter adaptee) {
        mAdaptee = adaptee;
    }

    public RecyclerView.Adapter getAdaptee() {
        return mAdaptee;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_HEADER && mAdaptee instanceof HeaderRecyclerView) {
            return ((HeaderRecyclerView) mAdaptee).onCreateHeaderViewHolder(parent, viewType);
        } else if (viewType == TYPE_FOOTER && mAdaptee instanceof FooterRecyclerView) {
            return ((FooterRecyclerView) mAdaptee).onCreateFooterViewHolder(parent, viewType);
        }
        return mAdaptee.onCreateViewHolder(parent, viewType - TYPE_ADAPTEE_OFFSET);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (position == 0 && holder.getItemViewType() == TYPE_HEADER && useHeader()) {
            ((HeaderRecyclerView) mAdaptee).onBindHeaderView(holder, position);
        } else if (position == mAdaptee.getItemCount() && holder.getItemViewType() == TYPE_FOOTER && useFooter()) {
            ((FooterRecyclerView) mAdaptee).onBindFooterView(holder, position);
        } else {
            mAdaptee.onBindViewHolder(holder, position - (useHeader() ? 1 : 0));
        }
    }

    @Override
    public int getItemCount() {
        int itemCount = mAdaptee.getItemCount();
        if (useHeader()) {
            itemCount += 1;
        }
        if (useFooter()) {
            itemCount += 1;
        }
        return itemCount;
    }

    private boolean useHeader() {
        if (mAdaptee instanceof HeaderRecyclerView) {
            return true;
        }
        return false;
    }

    private boolean useFooter() {
        if (mAdaptee instanceof FooterRecyclerView) {
            return true;
        }
        return false;
    }

    @Override
    public int getItemViewType(int position) {
        if (position == 0 && useHeader()) {
            return TYPE_HEADER;
        }
        if (position == mAdaptee.getItemCount() && useFooter()) {
            return TYPE_FOOTER;
        }
        if (mAdaptee.getItemCount() >= Integer.MAX_VALUE - TYPE_ADAPTEE_OFFSET) {
            new IllegalStateException("HeaderRecyclerViewAdapter offsets your BasicItemType by " + TYPE_ADAPTEE_OFFSET + ".");
        }
        return mAdaptee.getItemViewType(position) + TYPE_ADAPTEE_OFFSET;
    }


    public static interface HeaderRecyclerView {
        public RecyclerView.ViewHolder onCreateHeaderViewHolder(ViewGroup parent, int viewType);

        public void onBindHeaderView(RecyclerView.ViewHolder holder, int position);
    }

    public static interface FooterRecyclerView {
        public RecyclerView.ViewHolder onCreateFooterViewHolder(ViewGroup parent, int viewType);

        public void onBindFooterView(RecyclerView.ViewHolder holder, int position);
    }

}

HeaderRecyclerViewAdapterV2

import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;

/**
 * Created by sebnapi on 08.11.14.
 * <p/>
 * If you extend this Adapter you are able to add a Header, a Footer or both
 * by a similar ViewHolder pattern as in RecyclerView.
 * <p/>
 * If you want to omit changes to your class hierarchy you can try the Plug-and-Play
 * approach HeaderRecyclerViewAdapterV1.
 * <p/>
 * Don't override (Be careful while overriding)
 * - onCreateViewHolder
 * - onBindViewHolder
 * - getItemCount
 * - getItemViewType
 * <p/>
 * You need to override the abstract methods introduced by this class. This class
 * is not using generics as RecyclerView.Adapter make yourself sure to cast right.
 * <p/>
 * TOTALLY UNTESTED - USE WITH CARE - HAVE FUN :)
 */
public abstract class HeaderRecyclerViewAdapterV2 extends RecyclerView.Adapter {
    private static final int TYPE_HEADER = Integer.MIN_VALUE;
    private static final int TYPE_FOOTER = Integer.MIN_VALUE + 1;
    private static final int TYPE_ADAPTEE_OFFSET = 2;

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_HEADER) {
            return onCreateHeaderViewHolder(parent, viewType);
        } else if (viewType == TYPE_FOOTER) {
            return onCreateFooterViewHolder(parent, viewType);
        }
        return onCreateBasicItemViewHolder(parent, viewType - TYPE_ADAPTEE_OFFSET);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (position == 0 && holder.getItemViewType() == TYPE_HEADER) {
            onBindHeaderView(holder, position);
        } else if (position == getBasicItemCount() && holder.getItemViewType() == TYPE_FOOTER) {
            onBindFooterView(holder, position);
        } else {
            onBindBasicItemView(holder, position - (useHeader() ? 1 : 0));
        }
    }

    @Override
    public int getItemCount() {
        int itemCount = getBasicItemCount();
        if (useHeader()) {
            itemCount += 1;
        }
        if (useFooter()) {
            itemCount += 1;
        }
        return itemCount;
    }

    @Override
    public int getItemViewType(int position) {
        if (position == 0 && useHeader()) {
            return TYPE_HEADER;
        }
        if (position == getBasicItemCount() && useFooter()) {
            return TYPE_FOOTER;
        }
        if (getBasicItemType(position) >= Integer.MAX_VALUE - TYPE_ADAPTEE_OFFSET) {
            new IllegalStateException("HeaderRecyclerViewAdapter offsets your BasicItemType by " + TYPE_ADAPTEE_OFFSET + ".");
        }
        return getBasicItemType(position) + TYPE_ADAPTEE_OFFSET;
    }

    public abstract boolean useHeader();

    public abstract RecyclerView.ViewHolder onCreateHeaderViewHolder(ViewGroup parent, int viewType);

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

    public abstract boolean useFooter();

    public abstract RecyclerView.ViewHolder onCreateFooterViewHolder(ViewGroup parent, int viewType);

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

    public abstract RecyclerView.ViewHolder onCreateBasicItemViewHolder(ViewGroup parent, int viewType);

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

    public abstract int getBasicItemCount();

    /**
     * make sure you don't use [Integer.MAX_VALUE-1, Integer.MAX_VALUE] as BasicItemViewType
     *
     * @param position
     * @return
     */
    public abstract int getBasicItemType(int position);

}

欢迎提供反馈和意见。我会自己使用HeaderRecyclerViewAdapterV2并在未来逐步完善、测试和发布更改。

编辑:@OvidiuLatcu 是的,我遇到了一些问题。实际上,我停止了通过position - (useHeader() ? 1 : 0)隐式偏移标题行,并创建了一个公共方法int offsetPosition(int position)替代它。因为如果你在Recyclerview上设置了一个OnItemTouchListener,你可以拦截触摸事件,获取触摸的x,y坐标,找到对应的子视图然后调用recyclerView.getChildPosition(...),你将始终得到适配器中未偏移的位置!这是RecyclerView代码中的一个缺点,我没有看到一个简单的方法来解决它。这就是为什么我现在需要通过自己的代码显式地偏移位置。


1
@OvidiuLatcu 请查看帖子。 - seb
@seb 版本2非常好用!唯一需要修改的是在onBindViewHolder和getItemViewType方法中获取页脚的条件。问题在于,如果使用position == getBasicItemCount()来获取位置,则对于实际的最后一个位置而言,它不会返回true,而是返回倒数第二个位置。这导致页脚视图被放置在那里(而不是在底部)。我们将条件更改为position == getBasicItemCount() + 1,然后它就很好用了! - mmark
@seb版本2非常好用。非常感谢。我正在使用它。我建议添加“final”关键字来覆盖函数。 - RyanShao
如жһњж‚ЁењЁиұ«йЂ‚й…ҚиЂ…е†…йѓЁдҢүз”Ёд»»дҢ•notifyXXXж–№жі•пәЊй‚Әд№€е®ѓе°†ж— жі•ж­Әеёёе·ӨдҢњгЂ‚我е»ғи®®ж‚ЁйЂљиү‡RecyclerView.Adapter.registerAdapterDataObserverењЁжһ„йЂ е‡Ңж•°дё­жіЁе†Њж‰Ђжњ‰иұ«йЂ‚й…ҚиЂ…зљ„йЂљзџӨдғ‹д»¶гЂ‚ - Flo
自从我创建了这篇文章以来,我已经有了一个更复杂的版本(V2)运行,并且我在使用notifyXXX方法时没有遇到任何问题。但是,可能它不会起作用,这取决于你如何使用它。 - seb
显示剩余4条评论

10

你可以使用这个GitHub库,以最简单的方式向你的RecyclerView中添加Header和/或Footer

你需要在你的项目中添加HFRecyclerView库,或者你也可以通过Gradle获取它:

compile 'com.mikhaellopez:hfrecyclerview:1.0.0'

这是一张图片结果:

预览

编辑:

如果您只想在顶部和/或底部使用此库添加边距:SimpleItemDecoration:

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

这个库可以在LinearLayoutManager中正确添加视图作为标题,但我希望在GridLayoutManager中将视图设置为标题,它应该占据整个屏幕的宽度。使用这个库是否可能实现这个目标? - Ved
不,这个库允许你在适配器(RecyclerView.Adapter)中更改recyclerView的第一个和最后一个元素。你可以将此适配器应用于GridView而没有问题。因此,我认为这个库可以实现你想要的功能。 - lopez.mikhael

10

我没有尝试过这个方法,但你可以在适配器的getItemCount返回的整数基础上加1(如果您想要头部和底部则加2)。然后,您可以在适配器中覆盖 getItemViewType,当i==0时返回不同的整数: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemViewType(int)

接下来,createViewHolder将传递您从 getItemViewType 中返回的整数,使您能够为头部视图创建或配置不同的视图持有者:https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#createViewHolder(android.view.ViewGroup, int)

别忘了从传递给bindViewHolder的位置整数中减去1。


@VieuMa 同时,将页眉和页脚抽象为适配器意味着它应该处理所请求的两种布局类型,编写自己的布局管理器意味着需要重新实现这两种布局类型。 - Ian Newson
只需创建一个适配器,包装任何适配器并添加对索引0处的标题视图的支持。在ListView中使用HeaderView会创建许多边缘情况,并且由于使用包装适配器很容易解决这个问题,因此增加的价值很小。 - yigit
@VieuMa 是的,包装适配器是一个很好的想法。这样可以让你轻松地在任何现有的RecyclerView中使用这个解决方案。因此,我现在认为这个解决方案比实现自己的布局管理器更优越。 - Ian Newson
如果我理解你所说的正确,那么思路就是创建一个HeaderBaseAdapter,让所有需要具有HeaderView的其他适配器都继承它?并添加必要的方法来实现头部? - MathieuMaree
那是一个选项,更简洁。请确保相应地偏移通知方法。根据你的代码库,你可能还喜欢创建一个HeaderAdapter,将普通适配器作为构造函数参数,并将API调用转发给它。在这种情况下,你可以将你的HeaderAdapter添加为包装适配器的监听器,以便你可以重写和分发通知事件。这一切取决于你的使用情况。两种解决方案都可以。 - yigit
显示剩余6条评论

7

recyclerview:1.2.0 引入了 ConcatAdapter 类,它可以将多个适配器连接成一个单一的适配器。因此,它允许创建单独的头部/底部适配器,并在多个列表中重用它们。

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

请查看发布文章,其中包含一个示例,演示如何使用ConcatAdapter在标题和页脚中显示加载进度条。
截至我回答此问题时,该库的版本1.2.0处于alpha阶段,因此API可能会改变。您可以在此处检查状态

这个解决方案存在一个问题,如果使用ListAdapter的submitList方法,它会滚动到底部。不建议这样做。 - mtsahakis

6
我最终实现了自己的适配器来包装任何其他适配器,并提供添加头部和尾部视图的方法。
在这里创建了一个代码片段:HeaderViewRecyclerAdapter.java 我想要的主要功能是与ListView类似的接口,因此我想要能够在Fragment中填充视图并将它们添加到RecyclerView中的onCreateView中。通过创建一个HeaderViewRecyclerAdapter传递要包装的适配器,并调用addHeaderViewaddFooterView传递您填充的视图来完成这一点。然后将HeaderViewRecyclerAdapter实例设置为RecyclerView上的适配器。
另一个要求是需要能够轻松更换适配器,同时保留头部和尾部,我不想有多个适配器和这些头部和尾部的多个实例。因此,您可以调用setAdapter来更改包装的适配器,保留头部和尾部,RecyclerView会被通知更改。

4

我的“保持简单傻瓜化”的方式...虽然会浪费一些资源,但我不在意,因为我保持代码简单易懂。所以...首先,在你的item_layout中添加一个visibility属性为GONE的footer。

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

然后,在最后一项上设置它的可见性。
public void onBindViewHolder(ChannelAdapter.MyViewHolder holder, int position) {
        boolean last = position==data.size()-1;
        //....
        holder.footer.setVisibility(View.GONE);
        if (last && showFooter){
            holder.footer.setVisibility(View.VISIBLE);
        }
    }

对于标题,请执行相反操作。

除了这个解决方案浪费资源之外,我还有一个功能问题。当使用ListAdapter和submitList()方法与一个数据列表,其中少了一项数据,并且那个少的一项是显示页脚的最后一项时,页脚永远不会显示。我认为这是因为itemCount(与DiffUtil结合使用)保持不变。请注意。我认为最好的解决方案是这个 https://medium.com/androiddevelopers/merge-adapters-sequentially-with-mergeadapter-294d2942127a - mtsahakis

1

我只想添加一个替代所有HeaderRecyclerViewAdapter实现的解决方案。CompoundAdapter:

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

这是一种更加灵活的方法,因为您可以将多个Adapter创建成一个AdapterGroup。对于标题示例,使用现有的adapter,并添加一个包含标题项目的adapter:

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

recyclerView.setAdapter(adapterGroup);

这很简单易懂。您可以使用相同的原理轻松实现更复杂的适配器。

这是我找到的最简单和最强大的答案。 - Noor Hossain

1
优秀的回答,由@reaz-murshed分享在这里。但我不喜欢添加+1的数据大小部分,并且如果已经到达末尾就返回页脚视图的部分。
它告诉我们每个最后一个元素都是页脚视图,我很难删除页脚视图。

对于我的情况,相反地做了这样的事情-

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

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

这对我来说更加简洁,并将实际对象加载到回收视图中。此外,当我不需要页脚视图或想要添加更多占位符页脚视图时,我也可以删除页脚视图。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接