Android 5.0-将页眉/页脚添加到RecyclerView


122

我花了一点时间试图找出一种将标头添加到的方法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)方法,并将标头视图添加到第一个位置以用作标头。

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

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

NullPointerExceptions尝试addView(View view)创建Activity的不同时刻调用了几次之后(还尝试在设置所有内容(甚至是适配器的数据)后添加视图),我意识到我不知道这是否是正确的方法(并且它看起来不是)。

PS:另外,一个能够处理的解决方案GridLayoutManager,除了LinearLayoutManager将非常感激!



问题出在适配器代码中。这意味着,您将以某种方式在onCreateViewHolder函数中返回空的viewholder。
Neo

有一个很好的办法如何将头部添加到StaggeredGridLayout stackoverflow.com/questions/42202735/...
阿列克谢Timoshchenko

Answers:


120

我必须在其中添加页脚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 2015年

1
这正是我所做的。但是,如果我希望我的RecyclerView适应交错列表怎么办?第一个元素(标头)也将错开。:(
霓虹灯Warge '16

这是一个有关如何RecyclerView动态填充您的教程。您可以控制自己的每个元素RecyclerView。请查看工作项目的代码部分。希望它会有所帮助。github.com/comeondude/dynamic-recyclerview/wiki
Reaz Murshed

2
int getItemViewType (int position)-返回该项目的视图类型以方便视图回收。此方法的默认实现返回0,并假设适配器具有单一视图类型。与ListView适配器不同,类型不必是连续的。考虑使用id资源来唯一标识项目视图类型。-从文档。developer.android.com/reference/android/support/v7/widget/...
雷阿兹穆尔希德

3
人们总是想做的事情要花很多精力。我简直不敢相信...
JohnyTex

28

解决起来很简单!

我不喜欢在适配器内部将逻辑设置为其他视图类型,因为每次它在返回视图之前都会检查视图类型。以下解决方案避免了额外的检查。

只需在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将尝试布局所有视图持有者,而不考虑layout_heightRV的位置

推荐用于较小的列表,例如Nav抽屉或设置等。


1
为我工作非常简单
Hitesh Sahu

1
当页眉和页脚不必在列表中重复时,这是将页眉和页脚添加到回收器视图的一种非常简单的方法。
user1841702 '16

34
这是丧失所有优势的非常简单的方法RecyclerView-您失去了实际的回收利用及其带来的优化。
MarcinKoziński'16

1
我尝试了这段代码,滚动无法正常工作...滚动变得太慢了
..plz

2
使用嵌套的scrollview将导致recyclerview缓存所有视图,如果recycler视图的大小更大,则滚动并且加载时间将增加。我建议不要使用此代码
图沙·萨哈

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是的,我遇到了一些问题。实际上,我不再隐式偏移Header position - (useHeader() ? 1 : 0),而是int offsetPosition(int position)为其创建了一个公共方法。因为如果OnItemTouchListener在Recyclerview上设置了on,则可以拦截触摸,获取触摸的x,y坐标,找到相应的子视图,然后调用recyclerView.getChildPosition(...),您将始终在适配器中获得未偏移的位置!这在RecyclerView代码中是一个缺点,我看不到一种简单的方法可以克服这一问题。这就是为什么现在我需要用自己的代码来抵消明确的位置的原因。


看起来挺好的 !有什么麻烦吗?还是我们可以安全使用它?:D
Ovidiu Latcu 2014年

1
@OvidiuLatcu看帖子
SEB

在这些实现中,您似乎已经假设页眉和页脚的数量仅为1?
rishabhmhjn

@seb 版本2就像魅力一样!!我唯一需要修改的是获取onBindViewHolder和getItemViewType方法上的页脚的条件。问题是,如果您使用position == getBasicItemCount()获得该位置,则对于实际的最后一个位置,它不会返回true,但对于最后一个位置-1,它将返回true。最终将FooterView放置在此处(不在底部)。我们修复了将条件更改为position == getBasicItemCount()+ 1的问题,效果很好!
mmark 2015年

@seb版本2很棒。非常感谢。我正在使用它。我建议的一件事是为覆盖函数添加“ final”关键字。
RyanShao 2015年

10

我没有尝试过,但是我只需在适配器的getItemCount返回的整数中加1(或2,如果同时需要页眉和页脚)。然后getItemViewType,您可以在以下情况下在适配器中重写以返回其他整数i==0https : //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)

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


我认为这不是recyclerview的工作。Recyclerviews的工作是简单地回收视图。编写带有页眉或页脚实现的layoutmanager是方法。
IZI_Shadow_IZI 2014年

感谢@IanNewson的回复。首先,即使仅使用getItemViewType(int position) { return position == 0 ? 0 : 1; }RecyclerView没有getViewTypeCount()方法),该解决方案也似乎可以正常工作。另一方面,我确实同意@IZI_Shadow_IZI,我真的感觉LayoutManager应该是处理这种事情的人。还有其他想法吗?
MathieuMaree 2014年

@VieuMa也许你是对的,但我目前不知道该怎么做,我很确定我的解决方案会起作用。次优的解决方案总比没有解决方案好,这就是您以前的解决方案。
伊恩·纽森

@VieuMa同样,将页眉和页脚抽象到适配器中意味着它应该处理所请求的两种类型的布局,编写自己的布局管理器意味着针对两种类型的布局重新实现。
伊恩·纽森

只需创建一个包装任何适配器并在索引0处添加对标头视图的支持的适配器。listview中的HeaderView会创建很多边缘情况,并且鉴于使用包装适配器很容易解决问题,因此其附加值很小。
yigit 2014年

9

您可以使用此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

6

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

在这里创建了要点:HeaderViewRecyclerAdapter.java

我想要的主要功能是与ListView相似的界面,因此我希望能够在Fragment中添加视图并将它们添加到RecyclerViewin中onCreateView。这是通过创建HeaderViewRecyclerAdapter传递要包装的适配器并调用addHeaderViewaddFooterView传递膨胀的视图来完成的。然后将HeaderViewRecyclerAdapter实例设置为适配器RecyclerView

一个额外的要求是,我需要能够在保留页眉和页脚的同时轻松地换出适配器,而我不想拥有多个带有这些页眉和页脚实例的适配器。因此,您可以调用setAdapter更改包装的适配器,而使页眉和页脚保持不变,并RecyclerView收到更改通知。


3

我的“保持简单愚蠢”的方式...浪费了一些资源,我知道,但是我不在乎,因为我的代码保持简单,所以... 1)将可见性为GONE的页脚添加到您的item_layout

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

2)然后将其设置为在最后一个项目上可见

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

对标题执行相反的操作


1

基于@seb的解决方案,我创建了RecyclerView.Adapter的子类,该子类支持任意数量的页眉和页脚。

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

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

我仍在测试中,但是如果需要,您可以尝试一下。如果您发现任何问题,请告诉我。


1

您可以使用viewtype解决此问题,这是我的演示:https : //github.com/yefengfreedom/RecyclerViewWithHeaderFooterLoadingEmptyViewErrorView

  1. 您可以定义一些回收站视图显示模式:

    public static final int MODE_DATA = 0,MODE_LOADING = 1,MODE_ERROR = 2,MODE_EMPTY = 3,MODE_HEADER_VIEW = 4,MODE_FOOTER_VIEW = 5;

2.重写getItemViewType方法

 @Override
public int getItemViewType(int position) {
    if (mMode == RecyclerViewMode.MODE_LOADING) {
        return RecyclerViewMode.MODE_LOADING;
    }
    if (mMode == RecyclerViewMode.MODE_ERROR) {
        return RecyclerViewMode.MODE_ERROR;
    }
    if (mMode == RecyclerViewMode.MODE_EMPTY) {
        return RecyclerViewMode.MODE_EMPTY;
    }
    //check what type our position is, based on the assumption that the order is headers > items > footers
    if (position < mHeaders.size()) {
        return RecyclerViewMode.MODE_HEADER_VIEW;
    } else if (position >= mHeaders.size() + mData.size()) {
        return RecyclerViewMode.MODE_FOOTER_VIEW;
    }
    return RecyclerViewMode.MODE_DATA;
}

3.重写getItemCount方法

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

4.重写onCreateViewHolder方法。通过viewType创建视图持有者

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (viewType == RecyclerViewMode.MODE_LOADING) {
        RecyclerView.ViewHolder loadingViewHolder = onCreateLoadingViewHolder(parent);
        loadingViewHolder.itemView.setLayoutParams(
                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, parent.getHeight() - mToolBarHeight)
        );
        return loadingViewHolder;
    }
    if (viewType == RecyclerViewMode.MODE_ERROR) {
        RecyclerView.ViewHolder errorViewHolder = onCreateErrorViewHolder(parent);
        errorViewHolder.itemView.setLayoutParams(
                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, parent.getHeight() - mToolBarHeight)
        );
        errorViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View v) {
                if (null != mOnErrorViewClickListener) {
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            mOnErrorViewClickListener.onErrorViewClick(v);
                        }
                    }, 200);
                }
            }
        });
        return errorViewHolder;
    }
    if (viewType == RecyclerViewMode.MODE_EMPTY) {
        RecyclerView.ViewHolder emptyViewHolder = onCreateEmptyViewHolder(parent);
        emptyViewHolder.itemView.setLayoutParams(
                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, parent.getHeight() - mToolBarHeight)
        );
        emptyViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View v) {
                if (null != mOnEmptyViewClickListener) {
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            mOnEmptyViewClickListener.onEmptyViewClick(v);
                        }
                    }, 200);
                }
            }
        });
        return emptyViewHolder;
    }
    if (viewType == RecyclerViewMode.MODE_HEADER_VIEW) {
        RecyclerView.ViewHolder headerViewHolder = onCreateHeaderViewHolder(parent);
        headerViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View v) {
                if (null != mOnHeaderViewClickListener) {
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            mOnHeaderViewClickListener.onHeaderViewClick(v, v.getTag());
                        }
                    }, 200);
                }
            }
        });
        return headerViewHolder;
    }
    if (viewType == RecyclerViewMode.MODE_FOOTER_VIEW) {
        RecyclerView.ViewHolder footerViewHolder = onCreateFooterViewHolder(parent);
        footerViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View v) {
                if (null != mOnFooterViewClickListener) {
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            mOnFooterViewClickListener.onFooterViewClick(v, v.getTag());
                        }
                    }, 200);
                }
            }
        });
        return footerViewHolder;
    }
    RecyclerView.ViewHolder dataViewHolder = onCreateDataViewHolder(parent);
    dataViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(final View v) {
            if (null != mOnItemClickListener) {
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        mOnItemClickListener.onItemClick(v, v.getTag());
                    }
                }, 200);
            }
        }
    });
    dataViewHolder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(final View v) {
            if (null != mOnItemLongClickListener) {
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        mOnItemLongClickListener.onItemLongClick(v, v.getTag());
                    }
                }, 200);
                return true;
            }
            return false;
        }
    });
    return dataViewHolder;
}

5.重写onBindViewHolder方法。通过viewType绑定数据

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    if (mMode == RecyclerViewMode.MODE_LOADING) {
        onBindLoadingViewHolder(holder, position);
    } else if (mMode == RecyclerViewMode.MODE_ERROR) {
        onBindErrorViewHolder(holder, position);
    } else if (mMode == RecyclerViewMode.MODE_EMPTY) {
        onBindEmptyViewHolder(holder, position);
    } else {
        if (position < mHeaders.size()) {
            if (mHeaders.size() > 0) {
                onBindHeaderViewHolder(holder, position);
            }
        } else if (position >= mHeaders.size() + mData.size()) {
            if (mFooters.size() > 0) {
                onBindFooterViewHolder(holder, position - mHeaders.size() - mData.size());
            }
        } else {
            onBindDataViewHolder(holder, position - mHeaders.size());
        }
    }
}

如果您的链接将来会断开怎么办?
Gopal Singh Sirvi

好问题。我将编辑答案并将代码发布在这里
yefeng

1

您可以使用库SectionedRecyclerViewAdapter将您的项目分组,并为每个部分添加标题,如下图所示:

在此处输入图片说明

首先,创建您的节类:

class MySection extends StatelessSection {

    String title;
    List<String> list;

    public MySection(String title, List<String> list) {
        // call constructor with layout resources for this Section header, footer and items 
        super(R.layout.section_header, R.layout.section_item);

        this.title = title;
        this.list = list;
    }

    @Override
    public int getContentItemsTotal() {
        return list.size(); // number of items of this section
    }

    @Override
    public RecyclerView.ViewHolder getItemViewHolder(View view) {
        // return a custom instance of ViewHolder for the items of this section
        return new MyItemViewHolder(view);
    }

    @Override
    public void onBindItemViewHolder(RecyclerView.ViewHolder holder, int position) {
        MyItemViewHolder itemHolder = (MyItemViewHolder) holder;

        // bind your view here
        itemHolder.tvItem.setText(list.get(position));
    }

    @Override
    public RecyclerView.ViewHolder getHeaderViewHolder(View view) {
        return new SimpleHeaderViewHolder(view);
    }

    @Override
    public void onBindHeaderViewHolder(RecyclerView.ViewHolder holder) {
        MyHeaderViewHolder headerHolder = (MyHeaderViewHolder) holder;

        // bind your header view here
        headerHolder.tvItem.setText(title);
    }
}

然后,使用各节设置RecyclerView并使用GridLayoutManager更改标题的SpanSize:

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

// Create your sections with the list of data
MySection section1 = new MySection("My Section 1 title", dataList1);
MySection section2 = new MySection("My Section 2 title", dataList2);

// Add your Sections to the adapter
sectionAdapter.addSection(section1);
sectionAdapter.addSection(section2);

// Set up a GridLayoutManager to change the SpanSize of the header
GridLayoutManager glm = new GridLayoutManager(getContext(), 2);
glm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
    @Override
    public int getSpanSize(int position) {
        switch(sectionAdapter.getSectionItemViewType(position)) {
            case SectionedRecyclerViewAdapter.VIEW_TYPE_HEADER:
                return 2;
            default:
                return 1;
        }
    }
});

// Set up your RecyclerView with the SectionedRecyclerViewAdapter
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(glm);
recyclerView.setAdapter(sectionAdapter);

0

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

不幸的是,页脚尚未覆盖。

除了创建标题/节以外,FlexibleAdapter还可以做更多的事情。您确实应该看看:https : //github.com/davideas/FlexibleAdapter


0

我只是为所有这些HeaderRecyclerViewAdapter实现添加一个替代方案。CompoundAdapter:

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

这是一种更灵活的方法,因为您可以在Adapters中创建AdapterGroup。对于标题示例,请按原样使用适配器,以及包含一个用于标题的项目的适配器:

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

recyclerView.setAdapter(adapterGroup);

它相当简单而且可读。您可以使用相同的原理轻松实现更复杂的适配器。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.