Android 5.0 - Tambahkan header / footer ke RecyclerView

123

Saya menghabiskan waktu sejenak mencoba mencari cara untuk menambahkan header ke file RecyclerView , tidak berhasil.

Inilah yang saya dapatkan sejauh ini:

@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 LayoutManagertampaknya menjadi objek penanganan disposisi dari RecyclerViewitem. Seperti yang saya tidak bisa menemukan addHeaderView(View view)metode, saya memutuskan untuk pergi dengan LayoutManager's addView(View view, int position)metode dan menambah tampilan judul saya di posisi pertama untuk bertindak sebagai header.

Aa dan di sinilah segalanya menjadi lebih buruk:

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)

Setelah NullPointerExceptionsmencoba beberapa kali untuk memanggil addView(View view)pada momen yang berbeda dari pembuatan Aktivitas (juga mencoba menambahkan tampilan setelah semuanya diatur, bahkan data Adaptor), saya menyadari bahwa saya tidak tahu apakah ini cara yang tepat untuk melakukannya (dan itu tidak terlihat).

PS: Juga, solusi yang bisa menangani GridLayoutManagerselain itu LinearLayoutManagerakan sangat dihargai!

MathieuMaree
sumber
Masalahnya ada pada kode Adaptor. Artinya, entah bagaimana Anda mengembalikan viewholder null dalam fungsi onCreateViewHolder.
Neo
Ada cara yang baik untuk menambahkan header ke StaggeredGridLayout stackoverflow.com/questions/42202735/…
Aleksey Timoshchenko

Jawaban:

121

Saya harus menambahkan footer ke saya RecyclerViewdan di sini saya membagikan cuplikan kode saya karena saya pikir itu mungkin berguna. Silakan periksa komentar di dalam kode untuk pemahaman yang lebih baik tentang aliran keseluruhan.

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

Potongan kode di atas menambahkan footer ke RecyclerView. Anda dapat memeriksa repositori GitHub ini untuk memeriksa implementasi penambahan header dan footer.

Reaz Murshed
sumber
2
Bekerja dengan baik. Ini harus ditandai sebagai jawaban yang benar.
Naga Mallesh Maddali
1
Inilah yang saya lakukan. Tetapi bagaimana jika saya ingin RecyclerView saya menyesuaikan daftar terhuyung-huyung? Elemen pertama (Header) juga akan diguncang. :(
Neon Warge
Ini adalah tutorial tentang bagaimana Anda dapat mengisi data Anda RecyclerViewsecara dinamis. Anda dapat mengontrol setiap elemen Anda RecyclerView. Silakan lihat bagian kode untuk proyek kerja. Semoga bisa membantu. github.com/comeondude/dynamic-recyclerview/wiki
Reaz Murshed
2
int getItemViewType (int position)- Kembalikan jenis tampilan item pada posisinya untuk tujuan daur ulang tampilan. Implementasi default metode ini mengembalikan 0, membuat asumsi tipe tampilan tunggal untuk adaptor. Tidak seperti ListViewadaptor, tipe tidak perlu bersebelahan. Pertimbangkan menggunakan sumber daya id untuk mengidentifikasi tipe tampilan item secara unik. - Dari dokumentasi. developer.android.com/reference/android/support/v7/widget/…
Reaz Murshed
3
Begitu banyak pekerjaan manual untuk sesuatu yang selalu ingin dilakukan orang. Aku tidak percaya ...
JohnyTex
28

Sangat mudah untuk dipecahkan !!

Saya tidak suka gagasan memiliki logika di dalam adaptor sebagai tipe tampilan yang berbeda karena setiap kali memeriksa tipe tampilan sebelum mengembalikan tampilan. Solusi di bawah ini menghindari pemeriksaan ekstra.

Cukup tambahkan tampilan header LinearLayout (vertikal) + tampilan recyclerview + footer di dalam android.support.v4.widget.NestedScrollView .

Lihat ini:

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

Tambahkan baris kode ini untuk pengguliran lancar

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

Ini akan kehilangan semua kinerja RV dan RV akan mencoba untuk menata semua pemegang tampilan terlepas layout_heightdari RV

Disarankan menggunakan untuk daftar ukuran kecil seperti laci Nav atau pengaturan dll.

Nishant Shah
sumber
1
Bekerja untuk saya cukup sederhana
Hitesh Sahu
1
Ini adalah cara yang sangat sederhana untuk menambahkan header dan footer ke tampilan pendaur ulang saat header dan footer tidak harus diulang dalam daftar.
pengguna1841702
34
Ini adalah cara yang sangat sederhana untuk menghilangkan semua keuntungan yang RecyclerViewdihasilkan - Anda kehilangan daur ulang yang sebenarnya dan pengoptimalan yang dihasilkannya.
Marcin Koziński
1
Saya mencoba kode ini, pengguliran tidak berfungsi dengan benar ... pengguliran menjadi terlalu lambat .. mohon saran jika bisa melakukan sesuatu untuk itu
Nibha Jain
2
menggunakan scrollview bersarang akan menyebabkan recyclerview meng-cache semua tampilan dan jika ukuran recycler view lebih banyak, scrolling dan waktu pemuatan akan meningkat. Saya menyarankan untuk tidak menggunakan kode ini
Tushar Saha
25

Saya memiliki masalah yang sama di Lollipop dan membuat dua pendekatan untuk membungkus Recyclerviewadaptor. Salah satunya cukup mudah digunakan, tetapi saya tidak yakin bagaimana perilakunya dengan kumpulan data yang berubah. Karena itu membungkus adaptor Anda dan Anda perlu memastikan diri Anda sendiri untuk memanggil metode seperti notifyDataSetChangedpada objek adaptor yang tepat.

Yang lainnya seharusnya tidak memiliki masalah seperti itu. Biarkan adaptor reguler Anda memperluas kelas, terapkan metode abstrak dan Anda sudah siap. Dan inilah mereka:

inti

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

}

Umpan balik dan garpu dihargai. Saya akan menggunakan HeaderRecyclerViewAdapterV2sendiri dan mengembangkan, menguji, dan memposting perubahan di masa mendatang.

EDIT : @OvidiuLatcu Ya, saya punya beberapa masalah. Sebenarnya saya berhenti mengimbangi Header secara implisit dengan position - (useHeader() ? 1 : 0)dan sebagai gantinya membuat metode publik int offsetPosition(int position)untuk itu. Karena jika Anda menyetel OnItemTouchListenerpada Recyclerview, Anda dapat mencegat sentuhan, mendapatkan koordinat x, y dari sentuhan tersebut, menemukan tampilan anak yang sesuai, lalu memanggil recyclerView.getChildPosition(...)dan Anda akan selalu mendapatkan posisi non-offset di adaptor! Ini adalah shortcomming di RecyclerView Code, saya tidak melihat metode yang mudah untuk mengatasinya. Inilah sebabnya mengapa saya sekarang mengimbangi posisi eksplisit ketika saya perlu dengan kode saya sendiri .

seb
sumber
kelihatan bagus ! mengalami masalah dengan itu? atau kita bisa menggunakannya dengan aman? : D
Ovidiu Latcu
1
@OvidiuLatcu lihat postingan
seb
Dalam implementasi ini, tampaknya Anda mengasumsikan jumlah header dan footer masing-masing hanya 1?
rishabhmhjn
@seb Versi 2 bekerja seperti pesona !! satu-satunya hal yang perlu saya ubah adalah kondisi untuk mendapatkan footer pada metode onBindViewHolder dan getItemViewType. Masalahnya adalah jika Anda mendapatkan posisi menggunakan position == getBasicItemCount () itu tidak akan mengembalikan nilai true untuk posisi terakhir sebenarnya, tetapi posisi terakhir - 1. Itu akhirnya menempatkan FooterView di sana (bukan di bagian bawah). Kami memperbaikinya dengan mengubah kondisi ke posisi == getBasicItemCount () + 1 dan berhasil dengan baik!
mmark
@seb versi 2 bekerja dengan sangat baik. Terima kasih banyak. saya menggunakannya. satu hal kecil yang saya sarankan adalah menambahkan kata kunci 'final' untuk fungsi override.
RyanShao
10

Saya belum mencoba ini, tetapi saya hanya akan menambahkan 1 (atau 2, jika Anda menginginkan header dan footer) ke integer yang dikembalikan oleh getItemCount di adaptor Anda. Anda kemudian dapat mengganti getItemViewTypeadaptor Anda untuk mengembalikan bilangan bulat yang berbeda ketika i==0: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemViewType(int)

createViewHolderkemudian diteruskan bilangan bulat yang Anda kembalikan getItemViewType, memungkinkan Anda membuat atau mengonfigurasi view holder secara berbeda untuk tampilan header: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html# createViewHolder (android.view.ViewGroup , int)

Jangan lupa untuk mengurangi satu dari bilangan bulat posisi yang diteruskan bindViewHolder.

Ian Newson
sumber
Saya tidak berpikir itu adalah pekerjaan pendaur ulang. Tugas Recyclerviews adalah mendaur ulang tampilan. Menulis layoutmanager dengan implementasi header atau footer adalah caranya.
IZI_Shadow_IZI
Terima kasih @IanNewson atas balasan Anda. Pertama, solusi ini sepertinya berhasil, bahkan hanya menggunakan getItemViewType(int position) { return position == 0 ? 0 : 1; }( RecyclerViewtidak memiliki getViewTypeCount()metode). Di sisi lain, saya setuju dengan @IZI_Shadow_IZI, saya merasa LayoutManagerlah yang harus menangani hal-hal semacam ini. Ada ide lain?
MathieuMaree
@VieuMa mungkin Anda berdua benar, tetapi saya tidak tahu cara melakukannya saat ini dan saya cukup yakin solusi saya akan berhasil. Solusi suboptimal lebih baik daripada tidak ada solusi, seperti yang Anda miliki sebelumnya.
Ian Newson
@VieuMa juga, mengabstraksi header dan footer ke dalam adaptor berarti ia harus menangani kedua jenis tata letak yang diminta, menulis pengelola tata letak Anda sendiri berarti menerapkan ulang untuk kedua jenis tata letak.
Ian Newson
cukup buat adaptor yang membungkus adaptor apa pun dan tambahkan dukungan untuk tampilan header pada indeks 0. HeaderView dalam listview membuat banyak kasus tepi dan nilai tambahnya minimal karena ini adalah masalah yang mudah dipecahkan dengan menggunakan adaptor pembungkus.
yigit
9

Anda dapat menggunakan pustaka GitHub ini yang memungkinkan untuk menambahkan Header dan / atau Footer di RecyclerView Anda dengan cara yang sesederhana mungkin.

Anda perlu menambahkan pustaka HFRecyclerView di proyek Anda atau Anda juga bisa mengambilnya dari Gradle:

compile 'com.mikhaellopez:hfrecyclerview:1.0.0'

Ini adalah hasil gambar:

Pratinjau

EDIT:

Jika Anda hanya ingin menambahkan margin di bagian atas dan / atau bawah dengan library ini: SimpleItemDecoration :

int offsetPx = 10;
recyclerView.addItemDecoration(new StartOffsetItemDecoration(offsetPx));
recyclerView.addItemDecoration(new EndOffsetItemDecoration(offsetPx));
lopez.mikhael
sumber
Library ini menambahkan view dengan benar pada header di LinearLayoutManager tetapi saya ingin mengatur view sebagai header di GridLayoutManager, yang mengambil tempat di seluruh lebar layar. Bisakah itu dimungkinkan dengan perpustakaan ini.
ved
Tidak, pustaka ini memungkinkan Anda mengubah elemen pertama dan terakhir dari recyclerView saat mengadaptasi (RecyclerView.Adapter). Anda bisa menggunakan adaptor ini ke GridView tanpa masalah. Jadi menurut saya perpustakaan ini memungkinkan untuk melakukan apa yang Anda inginkan.
lopez.mikhael
6

Saya akhirnya menerapkan adaptor saya sendiri untuk membungkus adaptor lain dan menyediakan metode untuk menambahkan tampilan header dan footer.

Membuat intinya di sini: HeaderViewRecyclerAdapter.java

Fitur utama yang saya inginkan adalah antarmuka yang mirip dengan ListView, jadi saya ingin dapat memperbesar tampilan di Fragmen saya dan menambahkannya ke RecyclerViewdalam onCreateView. Ini dilakukan dengan membuat HeaderViewRecyclerAdapterpenerusan adaptor untuk dibungkus, dan memanggil addHeaderViewserta addFooterViewmeneruskan tampilan yang diluaskan. Kemudian setel HeaderViewRecyclerAdapterinstance sebagai adaptor pada RecyclerView.

Persyaratan tambahan adalah bahwa saya harus dapat dengan mudah menukar adaptor sambil mempertahankan header dan footer, saya tidak ingin memiliki banyak adaptor dengan banyak contoh header dan footer ini. Jadi, Anda dapat memanggil setAdapteruntuk mengubah adaptor yang dibungkus dengan membiarkan header dan footer tetap utuh, dengan RecyclerViewpemberitahuan tentang perubahan tersebut.

darnmason
sumber
3

cara saya "tetap sederhana, bodoh" ... itu menyia-nyiakan beberapa sumber daya, saya tahu, tetapi saya tidak peduli karena kode saya tetap sederhana jadi ... 1) tambahkan footer dengan visibilitas PERGI ke item_layout Anda

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

2) lalu atur agar terlihat di item terakhir

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

lakukan sebaliknya untuk header

Luca Rocchi
sumber
1

Berdasarkan solusi @ seb, saya membuat subclass RecyclerView.Adapter yang mendukung sejumlah header dan footer.

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

Meskipun tampaknya menjadi solusi, menurut saya hal ini juga harus dikelola oleh LayoutManager. Sayangnya, saya membutuhkannya sekarang dan saya tidak punya waktu untuk mengimplementasikan StaggeredGridLayoutManager dari awal (atau bahkan memperpanjangnya).

Saya masih mengujinya, tapi Anda bisa mencobanya jika mau. Beri tahu saya jika Anda menemukan masalah apa pun dengannya.

mato
sumber
1

Anda dapat menggunakan viewtype untuk mengatasi masalah ini, berikut demo saya: https://github.com/yefengfreedom/RecyclerViewWithHeaderFooterLoadingEmptyViewErrorView

  1. Anda dapat menentukan beberapa mode tampilan tampilan daur ulang:

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

2. ganti mothod 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. ganti metode getItemCount

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

4. ganti metode onCreateViewHolder. buat view holder dengan 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. Ganti metode onBindViewHolder. ikat data dengan 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());
        }
    }
}
yefeng
sumber
bagaimana jika tautan Anda akan rusak di masa mendatang?
Gopal Singh Sirvi
Pertanyaan bagus. Saya akan mengedit jawaban saya dan memposting kode saya di sini
yefeng
1

Anda dapat menggunakan pustaka SectionedRecyclerViewAdapter untuk mengelompokkan item Anda dalam beberapa bagian dan menambahkan header ke setiap bagian, seperti pada gambar di bawah ini:

masukkan deskripsi gambar di sini

Pertama Anda membuat kelas bagian Anda:

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

Kemudian Anda menyiapkan RecyclerView dengan bagian Anda dan mengubah SpanSize header dengan GridLayoutManager:

// 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);
Gustavo
sumber
0

Saya tahu saya datang terlambat, tetapi baru belakangan ini saya bisa menerapkan "addHeader" seperti itu ke Adaptor. Dalam saya FlexibleAdapter proyek Anda dapat memanggil setHeaderpada Sectionable item, maka Anda menelepon showAllHeaders. Jika Anda hanya membutuhkan 1 header maka item pertama harus memiliki header. Jika Anda menghapus item ini, maka header secara otomatis ditautkan ke item berikutnya.

Sayangnya footer belum tercakup (belum).

FlexibleAdapter memungkinkan Anda melakukan lebih dari sekadar membuat header / bagian. Anda benar-benar harus melihat: https://github.com/davideas/FlexibleAdapter .

Davideas
sumber
0

Saya hanya akan menambahkan alternatif untuk semua implementasi HeaderRecyclerViewAdapter tersebut. CompoundAdapter:

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

Ini adalah pendekatan yang lebih fleksibel, karena Anda dapat membuat AdapterGroup dari Adapters. Untuk contoh header, gunakan adaptor Anda apa adanya, bersama dengan adaptor yang berisi satu item untuk header:

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

recyclerView.setAdapter(adapterGroup);

Ini cukup sederhana dan mudah dibaca. Anda dapat menerapkan adaptor yang lebih kompleks dengan mudah menggunakan prinsip yang sama.

blurkidi
sumber
0

recyclerview:1.2.0memperkenalkan kelas ConcatAdapter yang menggabungkan beberapa adaptor menjadi satu. Jadi memungkinkan untuk membuat adaptor header / footer terpisah dan menggunakannya kembali di beberapa daftar.

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

Lihat artikel pengumuman . Ini berisi contoh bagaimana menampilkan kemajuan pemuatan di header dan footer menggunakan ConcatAdapter.

Untuk saat saya memposting jawaban ini versi 1.2.0library berada dalam tahap alpha, jadi api mungkin berubah. Anda dapat memeriksa statusnya di sini .

Valeriy Katkov
sumber