135

I spent a moment trying to figure out a way to add a header to a RecyclerView, unsuccessfully.

This is what I got so far:

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

   ...
}

The LayoutManager seems to be the object handling the disposition of the RecyclerView items. As I couldn't find any addHeaderView(View view) method, I decided to go with the LayoutManager's addView(View view, int position) method and to add my header view in the first position to act as a header.

Aaand this is where things get uglier:

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)

After getting several NullPointerExceptions trying to call the addView(View view) at different moments of the Activity creation (also tried adding the view once everything is set up, even the Adapter's data), I realized I have no idea if this is the right way to do it (and it doesn't look to be).

PS: Also, a solution that could handle the GridLayoutManager in addition to the LinearLayoutManager would be really appreciated!

4

14 に答える 14

126

フッターを追加する必要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 リポジトリを確認できます。

于 2015-07-01T06:51:41.553 に答える
30

解決方法はとってもカンタン!!

ビューを返す前にビュータイプをチェックするたびに、アダプター内のロジックを別のビュータイプとして持つという考えは好きではありません。以下のソリューションは、余分なチェックを回避します。

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

layout_heightこれにより、すべての RV パフォーマンスが失われ、RV はRVに関係なくすべてのビュー ホルダーをレイアウトしようとします。

ナビドロワーや設定などの小さなサイズのリストに使用することをお勧めします。

于 2016-07-06T14:13:41.033 に答える
6

最終的に、独自のアダプターを実装して、他のアダプターをラップし、ヘッダー ビューとフッター ビューを追加するメソッドを提供しました。

ここに要点を作成しました: HeaderViewRecyclerAdapter.java

私が欲しかった主な機能は、ListView に似たインターフェイスだったので、Fragment 内のビューをインフレートして in に追加できるようにしたかったのRecyclerViewですonCreateView。これはHeaderViewRecyclerAdapter、ラップされるアダプターを渡す作成を作成し、膨張したビューを呼び出しaddHeaderViewて渡すことによって行われます。addFooterView次に、HeaderViewRecyclerAdapterインスタンスを のアダプタとして設定しますRecyclerView

追加の要件は、ヘッダーとフッターを維持しながらアダプターを簡単に交換できるようにする必要があることでした。これらのヘッダーとフッターの複数のインスタンスを持つ複数のアダプターを持ちたくありませんでした。setAdapterそのため、ヘッダーとフッターをそのままにして、ラップされたアダプターを変更するために呼び出すことができRecyclerView、変更が通知されます。

于 2014-11-10T16:07:28.563 に答える
1

@seb のソリューションに基づいて、任意の数のヘッダーとフッターをサポートする RecyclerView.Adapter のサブクラスを作成しました。

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

解決策のようですが、これもLayoutManagerで管理するべきだと思います。残念ながら、私は今それを必要としており、StaggeredGridLayoutManager を最初から実装する時間はありません (それから拡張することさえありません)。

まだテスト中ですが、よかったら試してみてください。問題が見つかった場合はお知らせください。

于 2014-11-28T21:22:29.033 に答える
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);
于 2016-03-29T15:26:52.703 に答える
1

これらすべての HeaderRecyclerViewAdapter 実装に代替を追加するだけです。複合アダプター:

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

Adapters から AdapterGroup を作成できるため、より柔軟なアプローチです。ヘッダーの例では、アダプターをそのまま使用し、ヘッダー用の 1 つのアイテムを含むアダプターを使用します。

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

recyclerView.setAdapter(adapterGroup);

それはかなり単純で読みやすいです。同じ原則を使用して、より複雑なアダプターを簡単に実装できます。

于 2016-08-17T07:57:42.090 に答える
0

私は遅れていることを知っていますが、最近になって、そのような「addHeader」をアダプターに実装できました。私のFlexibleAdapterプロジェクトsetHeaderでは、Sectionableアイテムを呼び出してから、 を呼び出しますshowAllHeaders。ヘッダーが 1 つだけ必要な場合は、最初の項目にヘッダーが必要です。このアイテムを削除すると、ヘッダーは自動的に次のアイテムにリンクされます。

残念ながら、フッターは (まだ) カバーされていません。

FlexibleAdapter を使用すると、ヘッダー/セクションを作成するだけではありません。https://github.com/davideas/FlexibleAdapterをご覧ください。

于 2016-02-22T18:54:33.100 に答える