Android 5.0 - RecyclerView'a üstbilgi / altbilgi ekle


123

RecyclerViewBaşarısız bir şekilde bir başlık eklemenin bir yolunu bulmaya çalışarak bir an harcadım .

Şu ana kadar elde ettiğim şey bu:

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

   ...
}

LayoutManagerBirinin yapıyı ele nesne gibi görünüyor RecyclerViewöğeler. Herhangi bir addHeaderView(View view)yöntem bulamadığım için, LayoutManager's addView(View view, int position)yöntemine gitmeye ve başlık görünümümü bir başlık görevi görecek ilk konuma eklemeye karar verdim .

Ve işte burada işler çirkinleşiyor:

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)

Aktivite oluşturmanın farklı anlarında birkaç kez NullPointerExceptionsarama yapmaya çalıştıktan sonra addView(View view)(ayrıca her şey kurulduktan sonra görünümü eklemeyi denedim, Bağdaştırıcının verileri bile), bunu yapmanın doğru yolu olup olmadığına dair hiçbir fikrim olmadığını fark ettim (ve görünmüyor).

Not: Ayrıca, buna GridLayoutManagerek olarak üstesinden gelebilecek bir çözüm LinearLayoutManagergerçekten takdir edilecektir!



Sorun Adaptör kodunda. Bu, bir şekilde onCreateViewHolder işlevinde boş görüntü tutucu döndürdüğünüz anlamına gelir.
Neo

Yanıtlar:


121

Altbilgi eklemem gerekiyordu RecyclerViewve burada yararlı olabileceğini düşündüğüm için kod parçacığımı paylaşıyorum. Genel akışı daha iyi anlamak için lütfen kodun içindeki yorumları kontrol edin.

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

Yukarıdaki kod pasajı RecyclerView,. Hem üstbilgi hem de altbilgi eklemenin uygulanmasını kontrol etmek için bu GitHub deposunu kontrol edebilirsiniz .


2
İyi çalışıyor. Bu doğru cevap olarak işaretlenmelidir.
Naga Mallesh Maddali

1
Ben de aynen öyle yaptım. Peki ya RecyclerView'umun kademeli listeyi uyarlamasını istersem? İlk öğe (başlık) da kademeli olacaktır. :(
Neon Warge

Bu, RecyclerViewdinamik olarak nasıl doldurabileceğinizle ilgili bir öğreticidir . Öğenizin her bir öğesi üzerinde kontrol sahibi olabilirsiniz RecyclerView. Lütfen çalışan bir proje için kod bölümüne bakın. Umarım yardımcı olabilir. github.com/comeondude/dynamic-recyclerview/wiki
Reaz Murshed

2
int getItemViewType (int position)- Görünümün geri dönüşümü amacıyla, konumdaki öğenin görünüm tipini iade edin. Bu yöntemin varsayılan uygulaması 0 döndürür ve bağdaştırıcı için tek bir görünüm türü varsayılır. ListViewBağdaştırıcılardan farklı olarak , türlerin bitişik olması gerekmez. Öğe görüntüleme türlerini benzersiz şekilde tanımlamak için kimlik kaynaklarını kullanmayı düşünün. - Belgelerden. developer.android.com/reference/android/support/v7/widget/…
Reaz Murshed

3
İnsanların her zaman yapmak istediği bir şey için çok fazla manuel çalışma. İnanamıyorum ...
JohnyTex

28

Çözmesi çok basit !!

Bağdaştırıcı içinde mantığın farklı bir görünüm türü olarak kullanılması fikrinden hoşlanmıyorum çünkü görünüme dönmeden önce görünüm türünü her kontrol ettiğinde. Aşağıdaki çözüm, ekstra kontrolleri önler.

Sadece içinde LinearLayout (dikey) başlık görünümü + recyclerview + altbilgi görünümü eklemek android.support.v4.widget.NestedScrollView .

Şuna bir bak:

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

Sorunsuz kaydırma için bu kod satırını ekleyin

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

Bu, tüm RV performansını kaybedecek ve RV bakılmaksızın tüm görünüm sahiplerini ortaya koymak için çalışacağız layout_heightrv

Gezinme çekmecesi veya ayarlar vb. Gibi küçük boyutlu liste için kullanılması önerilir.


1
Benim için oldukça basit çalıştı
Hitesh Sahu

1
Bu, üstbilgilerin ve altbilgilerin bir liste içinde yinelenmesi gerekmediğinde geri dönüşümcü görünümüne üstbilgi ve altbilgi eklemenin çok basit bir yoludur.
user1841702

34
Bu, RecyclerViewgetirdiği tüm avantajları kaybetmenin çok basit bir yoludur - gerçek geri dönüşümü ve getirdiği optimizasyonu kaybedersiniz.
Marcin Koziński

1
Bu kodu denedim, kaydırma düzgün çalışmıyor ... kaydırma çok yavaş oldu .. plz bunun için bir şeyler yapıp yapamayacağınızı önerin
Nibha Jain

2
İç içe kaydırma görünümünün kullanılması, geri dönüşüm görünümünün tüm görünümü önbelleğe almasına neden olur ve geri dönüşümcü görünümünün boyutu daha fazla kaydırılırsa ve yükleme süresi artar. Bu kodu kullanmamanızı öneririm
Tushar Saha

25

Lollipop'ta da aynı sorunu yaşadım ve Recyclerviewadaptörü sarmak için iki yaklaşım yarattım . Birinin kullanımı oldukça kolay, ancak değişen bir veri kümesiyle nasıl davranacağından emin değilim. Çünkü adaptörünüzü sarar notifyDataSetChangedve doğru adaptör nesnesinde olduğu gibi yöntemleri çağırdığınızdan emin olmanız gerekir .

Diğerinin böyle sorunları olmamalı. Normal bağdaştırıcınızın sınıfı genişletmesine izin verin, soyut yöntemleri uygulayın ve hazır olmalısınız. Ve işte buradalar:

esaslar bazı

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

}

Geri bildirim ve çatallar takdir edildi. HeaderRecyclerViewAdapterV2Kendi kendime kullanacağım ve gelecekte değişiklikleri geliştirecek, test edecek ve göndereceğim .

DÜZENLEME : @OvidiuLatcu Evet bazı sorunlar yaşadım. Aslında Üstbilgiyi örtük olarak dengelemeyi bıraktım position - (useHeader() ? 1 : 0)ve bunun yerine bunun için genel bir yöntem oluşturdum int offsetPosition(int position). Çünkü OnItemTouchListenerRecyclerview'u etkinleştirirseniz, dokunuşu kesebilir, dokunmanın x, y koordinatlarını alabilir, uygun çocuk görünümünü bulabilir ve sonra arayabilir recyclerView.getChildPosition(...)ve adaptörde her zaman ofset olmayan konumu elde edersiniz! Bu RecyclerView Kodunda bir eksiklik, bunun üstesinden gelmek için kolay bir yöntem görmüyorum. Bu nedenle, ihtiyacım olduğunda konumları açıkça kendi kodumla dengeliyorum.


iyi görünüyor ! herhangi bir sorun mu yaşıyorsun? yoksa güvenle kullanabilir miyiz? : D
Ovidiu Latcu

1
@OvidiuLatcu postayı görmek
seb

Bu uygulamalarda, üstbilgi ve altbilgi sayısının her biri yalnızca 1 olduğunu varsaymışsınız gibi görünüyor?
rishabhmhjn

@seb Sürüm 2 cazibe gibi çalışıyor !! Değiştirmem gereken tek şey, hem onBindViewHolder hem de getItemViewType yöntemlerinde altbilgiyi alma koşuludur. Sorun şu ki, position == getBasicItemCount () konumunu kullanarak konumu alırsanız, gerçek son konum için doğru olmayacak, ancak son konum - 1. FooterView'ı oraya yerleştirmekle sonuçlandı (en altta değil). Durumu == getBasicItemCount () + 1 olarak değiştirerek düzelttik ve harika çalıştı!
mmark

@seb sürüm 2 çok iyi çalışıyor. çok teşekkürler. onu kullanıyorum. Önerdiğim küçük bir şey, geçersiz kılma işlevi için 'son' anahtar kelime eklemektir.
RyanShao

10

Bunu denemedim, ancak bağdaştırıcınızdaki getItemCount tarafından döndürülen tam sayıya 1 (veya hem üstbilgi hem de altbilgi istiyorsanız 2) eklerim. Ardından getItemViewType, aşağıdaki durumlarda farklı bir tamsayı döndürmek için adaptörünüzde geçersiz kılabilirsiniz i==0: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemViewType(int)

createViewHolderdaha sonra, geri döndüğünüz tamsayı geçirilerek getItemViewTypebaşlık görünümü için görünüm tutucuyu farklı şekilde oluşturmanıza veya yapılandırmanıza olanak tanır: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html# createViewHolder (android.view.ViewGroup , int)

Tamsayı geçirilen pozisyondan bir çıkarmayı unutmayın bindViewHolder.


Bunun geri dönüşümcü görüşünün işi olduğunu sanmıyorum. Recyclerviews işi, görünümleri basitçe geri dönüştürmektir. Üstbilgi veya altbilgi uygulamasıyla bir düzen yöneticisi yazmak, gidilecek yoldur.
IZI_Shadow_IZI

Cevabınız için teşekkürler @ IanNewson. Birincisi, bu çözüm sadece kullanılıyor olsa bile çalışıyor gibi görünüyor getItemViewType(int position) { return position == 0 ? 0 : 1; }( RecyclerViewbir getViewTypeCount()yöntemi yok). Öte yandan, @IZI_Shadow_IZI'ye katılıyorum, gerçekten LayoutManager'ın bu tür şeylerle ilgilenen kişi olması gerektiğini hissediyorum. Başka bir fikrin var mı?
MathieuMaree

@VieuMa muhtemelen ikiniz de haklısınız, ancak şu anda bunu nasıl yapacağımı bilmiyorum ve çözümümün işe yarayacağından oldukça emindim. Suboptimal bir çözüm, çözüm olmamasından daha iyidir, ki daha önce sahip olduğunuz buydu.
Ian Newson

@VieuMa ayrıca, üstbilgiyi ve altbilgiyi adaptöre soyutlamak, istenen her iki düzen türünü de işlemesi gerektiği anlamına gelir; kendi düzen yöneticinizi yazmak, her iki düzen türünü de yeniden uygulamak anlamına gelir.
Ian Newson

sadece herhangi bir bağdaştırıcıyı saran ve dizin 0'da başlık görünümü için destek ekleyen bir bağdaştırıcı oluşturun. Listview'daki HeaderView, çok sayıda uç durum oluşturur ve bir sarmalayıcı bağdaştırıcı kullanarak çözmenin kolay bir sorun olduğu göz önüne alındığında, katma değer minimumdur.
yiğit

9

Bu GitHub kitaplığını , RecyclerView'unuza mümkün olan en basit şekilde Üstbilgi ve / veya Altbilgi eklemenize izin vererek kullanabilirsiniz .

Projenize HFRecyclerView kitaplığı eklemeniz gerekir veya bunu Gradle'dan da edinebilirsiniz:

compile 'com.mikhaellopez:hfrecyclerview:1.0.0'

Bu, resmin bir sonucudur:

Ön izleme

DÜZENLE:

Bu kitaplıkla üst ve / veya altta bir kenar boşluğu eklemek istiyorsanız: SimpleItemDecoration :

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

Bu kütüphane, LinearLayoutManager'da üstbilgiye düzgün bir şekilde görünüm ekliyor ancak ekranın tüm genişliğini kapsayan GridLayoutManager'da görünümü başlık olarak ayarlamak istiyorum. Bu kütüphane ile mümkün olabilir mi?
ved

Hayır, bu kitaplık, uyarlama sırasında bir recyclerView'in ilk ve son öğesini değiştirmenize izin verir (RecyclerView.Adapter). Bu adaptörü sorunsuz bir GridView'a kullanabilirsiniz. Bence bu kütüphane istediğinizi yapmanıza izin veriyor.
lopez.mikhael

6

Başka bir bağdaştırıcıyı sarmak ve üstbilgi ve altbilgi görünümleri eklemek için yöntemler sağlamak için kendi bağdaştırıcımı uyguladım.

Burada bir özet oluşturuldu: HeaderViewRecyclerAdapter.java

Benim Fragment görüşlerini şişirmek ve bunları eklemek mümkün istedim bu yüzden istediğim ana özelliği, bir ListView benzer bir arayüz oldu RecyclerViewin onCreateView. Bu, HeaderViewRecyclerAdaptersarılacak adaptörü bir geçiş oluşturarak addHeaderViewve addFooterViewşişirilmiş görünümlerinizi arayarak ve geçirerek yapılır. Ardından HeaderViewRecyclerAdapterörneği RecyclerView.

Ekstra bir gereksinim, üstbilgileri ve altbilgileri tutarken adaptörleri kolayca değiştirebilmem gerektiğiydi, bu üstbilgilerin ve altbilgilerin birden çok örneğine sahip birden çok bağdaştırıcıya sahip olmak istemiyordum. Böylece setAdapter, üstbilgileri ve altbilgileri olduğu gibi bırakarak, değişikliğin bildirilmesiyle birlikte sarılmış bağdaştırıcıyı değiştirmek için çağrı yapabilirsiniz RecyclerView.


3

benim "basit aptalca tut" yöntemim ... bazı kaynakları boşa harcıyor, biliyorum, ama kodum basit olduğu için umrumda değil ... 1) görünürlüklü altbilgi ekle GİTTİ öğenize

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

2) sonra onu son öğede görünür hale getirin

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

başlık için tersini yap


1

@ Seb'in çözümüne dayanarak, isteğe bağlı sayıda üstbilgi ve altbilgiyi destekleyen RecyclerView.Adapter alt sınıfını oluşturdum.

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

Bir çözüm gibi görünse de bu şeyin LayoutManager tarafından yönetilmesi gerektiğini düşünüyorum. Ne yazık ki, şimdi ona ihtiyacım var ve sıfırdan bir StaggeredGridLayoutManager uygulamak için zamanım yok (hatta ondan uzatma).

Hala test ediyorum ama isterseniz deneyebilirsiniz. Bununla ilgili herhangi bir sorun bulursanız lütfen bize bildirin.


1

Bu sorunu çözmek için görünüm tipini kullanabilirsiniz, işte benim demom: https://github.com/yefengfreedom/RecyclerViewWithHeaderFooterLoadingEmptyViewErrorView

  1. bazı geri dönüşümcü görünümü görüntüleme modu tanımlayabilirsiniz:

    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 yöntemini geçersiz kılın

 @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 yöntemini geçersiz kılın

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

4. onCreateViewHolder yöntemini geçersiz kılın. viewType ile görünüm sahibi oluştur

@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 yöntemini geçersiz kılın. veriyi viewType ile bağla

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

Ya gelecekte bağlantınız kopacaksa?
Gopal Singh Sirvi

Güzel soru. Cevabımı düzenleyeceğim ve kodumu buraya
göndereceğim

1

Öğelerinizi bölümler halinde gruplamak ve aşağıdaki görüntüdeki gibi her bölüme bir başlık eklemek için SectionedRecyclerViewAdapter kitaplığını kullanabilirsiniz :

görüntü açıklamasını buraya girin

Önce bölüm sınıfınızı oluşturursunuz:

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

Ardından RecyclerView'ı bölümlerinizle kurarsınız ve GridLayoutManager ile başlıkların SpanSize değerini değiştirirsiniz:

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

Geç kaldığımı biliyorum, ancak ancak yakın zamanda Adaptöre böyle bir "addHeader" ekleyebildim. Benim içinde FlexibleAdapter projesi Arayabileceğin setHeaderbir üzerinde kesitsel bir öğe, o zaman çağrıshowAllHeaders . Yalnızca 1 başlığa ihtiyacınız varsa, ilk öğenin başlığı olmalıdır. Bu öğeyi silerseniz, başlık otomatik olarak bir sonrakine bağlanır.

Maalesef altbilgiler henüz kapsanmadı.

FlexibleAdapter, başlıklar / bölümler oluşturmaktan çok daha fazlasını yapmanızı sağlar. Gerçekten bir göz atmalısınız: https://github.com/davideas/F FlexibleAdapter .


0

Tüm bu HeaderRecyclerViewAdapter uygulamasına bir alternatif eklerim. CompoundAdapter:

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

Adaptörlerden bir AdapterGroup oluşturabileceğiniz için daha esnek bir yaklaşımdır. Başlık örneği için, adaptörünüzü olduğu gibi, başlık için bir öğe içeren bir adaptörle birlikte kullanın:

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

recyclerView.setAdapter(adapterGroup);

Oldukça basit ve okunabilir. Aynı prensibi kullanarak daha karmaşık adaptörü kolayca uygulayabilirsiniz.


0

recyclerview:1.2.0tanıtır ConcatAdapter bir tek içine birden fazla bağdaştırıcı birleştirir sınıfı. Böylece, ayrı üstbilgi / altbilgi bağdaştırıcıları oluşturmaya ve bunları birden çok listede yeniden kullanmaya izin verir.

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

Duyuru makalesine bir göz atın . Üstbilgi ve altbilgi kullanarak yükleme ilerlemesinin nasıl görüntüleneceği konusunda bir örnek içerir ConcatAdapter.

Bu cevabı gönderdiğim an 1.2.0için kütüphanenin sürümü alfa aşamasındadır, bu nedenle api değişebilir. Durumu buradan kontrol edebilirsiniz .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.