Erweiterbare Liste mit RecyclerView?


95

Ist es möglich, erweiterbare Listenelemente mit dem neuen RecyclerView zu verwenden? Wie ExpandableListView?


1
Sie können die Android-Uhr in Google Source auf Android 6.0 sehen
zys

@zys Wo finde ich diesen Beispielcode für die Android-Uhr?
AppiDevo

1
Sie können verschiedene viewType verwenden, um unterschiedliche Layouts zu laden, wenn Sie auf die Schaltfläche zum Erweitern klicken. Diese Lösung wird von Android Clock verwendet: android.googlesource.com/platform/packages/apps/DeskClock
zys


Antworten:


121

Dies ist mit den Standard-LayoutManagern einfach zu tun. Alles hängt davon ab, wie Sie Ihren Adapter verwalten.

Wenn Sie einen Abschnitt erweitern möchten, fügen Sie Ihrem Adapter nach der Kopfzeile einfach neue Elemente hinzu. Denken Sie daran, notifyItemRangeInserted aufzurufen, wenn Sie dies tun. Um einen Abschnitt zu reduzieren, entfernen Sie einfach die relevanten Elemente und rufen notifyItemRangeRemoved () auf. Bei Datenänderungen, die entsprechend benachrichtigt werden, werden die Ansichten in der Recycler-Ansicht animiert. Beim Hinzufügen von Elementen wird ein Bereich erstellt, der mit den neuen Elementen gefüllt werden soll, wobei die neuen Elemente eingeblendet werden. Das Entfernen ist das Gegenteil. Neben dem Adapter müssen Sie lediglich Ihre Ansichten formatieren, um dem Benutzer die logische Struktur zu vermitteln.

Update: Ryan Brooks hat jetzt einen Artikel darüber geschrieben .


Toller Vorschlag. Ich frage mich, warum sonst niemand diese Antwort positiv bewertet hat.
X-Treme

Ich werde sehen, wie dies als Beispiel zu SuperSLiM als Teil der nächsten Version hinzugefügt wird.
Tonic Artos

Ryan Brooks hat jetzt einen Artikel darüber geschrieben, wie das geht.
Tonic Artos

Toller Vorschlag. Warum ist diese Antwort so weit unten auf der Seite, dass ich nach unten scrollen muss, um sie zu finden? Es sollte ganz oben angezeigt werden, damit mehr Menschen diese schöne Antwort leichter finden können.
RestInPeace

1
Ryan Brooks markierte seine Bibliothek als veraltet. Ich frage mich, ob es nur so ist, dass er aufgehört hat, es zu unterstützen, oder es sich herausstellt, dass dieser Ansatz etwas kaputt macht oder Speicherlecks oder etw erzeugt ...
Varvara Kalinina

6

Holen Sie sich das Beispielcode Implementierung von hier

Legen Sie ValueAnimator in onClick von ViewHolder fest

@Override
public void onClick(final View view) {
    if (mOriginalHeight == 0) {
        mOriginalHeight = view.getHeight();
    }
    ValueAnimator valueAnimator;
    if (!mIsViewExpanded) {
        mIsViewExpanded = true;
        valueAnimator = ValueAnimator.ofInt(mOriginalHeight, mOriginalHeight + (int) (mOriginalHeight * 1.5));
    } else {
        mIsViewExpanded = false;
        valueAnimator = ValueAnimator.ofInt(mOriginalHeight + (int) (mOriginalHeight * 1.5), mOriginalHeight);
    }
    valueAnimator.setDuration(300);
    valueAnimator.setInterpolator(new LinearInterpolator());
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        public void onAnimationUpdate(ValueAnimator animation) {
            Integer value = (Integer) animation.getAnimatedValue();
            view.getLayoutParams().height = value.intValue();
            view.requestLayout();
        }
    });
    valueAnimator.start();

}

Hier ist der endgültige Code

public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    private TextView mFriendName;
    private int mOriginalHeight = 0;
    private boolean mIsViewExpanded = false;


    public ViewHolder(RelativeLayout v) {
        super(v);
        mFriendName = (TextView) v.findViewById(R.id.friendName);
        v.setOnClickListener(this);
    }

    @Override
    public void onClick(final View view) {
        if (mOriginalHeight == 0) {
            mOriginalHeight = view.getHeight();
        }
        ValueAnimator valueAnimator;
        if (!mIsViewExpanded) {
            mIsViewExpanded = true;
            valueAnimator = ValueAnimator.ofInt(mOriginalHeight, mOriginalHeight + (int) (mOriginalHeight * 1.5));
        } else {
            mIsViewExpanded = false;
            valueAnimator = ValueAnimator.ofInt(mOriginalHeight + (int) (mOriginalHeight * 1.5), mOriginalHeight);
        }
        valueAnimator.setDuration(300);
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            public void onAnimationUpdate(ValueAnimator animation) {
                Integer value = (Integer) animation.getAnimatedValue();
                view.getLayoutParams().height = value.intValue();
                view.requestLayout();
            }
        });
        valueAnimator.start();

    }
}

11
Dies funktioniert nicht "wie ExpandableListView", da der erweiterte Inhalt in diesem Fall eine Liste mit Elementen ist, die vom Adapter stammen. Dies ist eine entartete Lösung, bei der nur 1 Element als Kinder innerhalb der Gruppe zulässig ist.
TWiStErRob

Es funktioniert auch nicht richtig mit Ansichten, die überhaupt recycelt werden
vorsichtig

Es funktioniert nicht richtig, da eine Ansicht in RecyclerList möglicherweise mehrmals wiederholt wird. Wenn Sie also ein Element erweitern, werden mehrere Elemente in der Liste erweitert
Hossein Shahdoost

3

https://github.com/gabrielemariotti/cardslib

Diese Bibliothek verfügt über eine Implementierung einer erweiterbaren Liste mit einer Recycling-Ansicht (siehe die Demo-App unter "CardViewNative" -> "Liste, Raster und RecyclerView" -> "Erweiterbare Karten"). Es hat auch viele andere coole Kombinationen von Karten / Listen.


2
Diese Liste der erweiterbaren Karten ist kein
untergeordnetes Element

0

Jemand hat sich darüber beschwert, dass die oben genannte Lösung mit einer Listenansicht nicht als erweiterbarer Inhalt verwendet werden kann. Es gibt jedoch eine einfache Lösung: Erstellen Sie eine Listenansicht und füllen Sie diese Listenansicht manuell mit Ihren Zeilen .

Lösung für die Faulen: Es gibt eine einfache Lösung, wenn Sie Ihren Code nicht zu stark ändern möchten. Verwenden Sie einfach Ihren Adapter manuell, um Ansichten zu erstellen und sie dem hinzuzufügen LinearLayout.

Hier ist das Beispiel:

if (mIsExpanded)
{
    // llExpandable... is the expandable nested LinearLayout
    llExpandable.removeAllViews();
    final ArrayAdapter<?> adapter = ... // create your adapter as if you would use it for a ListView
    for (int i = 0; i < adapter.getCount(); i++)
    {
        View item = adapter.getView(i, null, null);
        // if you want the item to be selectable as if it would be in a default ListView, then you can add following code as well:
        item.setBackgroundResource(Functions.getThemeReference(context, android.R.attr.selectableItemBackground));
        item.setTag(i);
        item.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // item would be retrieved with: 
                // adapter.getItem((Integer)v.getTag())
            }
        });
        llExpandable.addView(item);
    }
    ExpandUtils.expand(llExpandable, null, 500);
}
else
{
    ExpandUtils.collapse(llExpandable, null, 500);
}

Hilfsfunktionen: getThemeReference

public static int getThemeReference(Context context, int attribute)
{
    TypedValue typeValue = new TypedValue();
    context.getTheme().resolveAttribute(attribute, typeValue, false);
    if (typeValue.type == TypedValue.TYPE_REFERENCE)
    {
        int ref = typeValue.data;
        return ref;
    }
    else
    {
        return -1;
    }
}

Hilfsklasse: ExpandUtils

Kavin Varnan postet bereits, wie man ein Layout animiert ... Aber wenn Sie meine Klasse verwenden möchten, können Sie dies gerne tun: https://gist.github.com/MichaelFlisar/738dfa03a1579cc7338a


9
Die "Lazy Solution" ist eine wirklich schlechte Idee. Das Hinzufügen von Ansichten zu einem linearen Layout in einer scrollbaren Ansicht ist so ineffizient.
Matthew

Ich denke, es ist zumindest mehr als brauchbar. Funktioniert schnell und reibungslos auf allen Geräten, die ich getestet habe. Übrigens werden die Ansichten hinzugefügt, wenn die Listenansicht nicht sichtbar ist ... danach wird nur die bereits ausgefüllte
Listenansicht

Das war ordentlich! Vielen Dank
Pawan Kumar

1
Wie @Matthew erwähnt hat, ist dies wirklich keine gute Idee. Eine einzelne große scrollbare Ansicht mit LinearLayouts lässt sich nicht gut skalieren. Einer der Hauptgründe, warum RecyclerView / ListView und andere ähnliche Ansichten geschrieben wurden, war die Optimierung großer datengestützter Listen unbekannter Größe. Durch das Erstellen einer einzelnen Ansicht mit einer Reihe von hinzugefügten Ansichten werden alle bereitgestellten Optimierungen verworfen. Das Recycling von Ansichten ist ein großer Vorteil und macht Ihren App-Speicher effizient. Wenn die Anzahl der Elemente nicht gering ist, wird durch die Verwendung von Listen für die Ansichtsbindung viel Arbeit gespart.
Munkay

Sie haben vollkommen recht, natürlich ist dies nicht perfekt und optimiert nichts ... Für meine Fälle hatte ich immer nur ein paar Dinge ... Das war also kein Problem ... Übrigens, in der Zwischenzeit habe ich einen Weg gefunden für verschachtelte Recycler-Ansichten ... Verwenden Sie einfach eine horizontale Recycler-Ansicht mit fester Höhe (nicht für jeden Anwendungsfall perfekt, aber dennoch) als verschachtelt, recyclerviewund Sie können diese verschachtelte Ansicht erweitern / ausblenden und alle Optimierungen desrecyclerview
prom85


0

Dies ist der Beispielcode für das, was von @TonicArtos erwähnt wird, um Elemente hinzuzufügen und zu entfernen und es dabei zu animieren. Dies stammt aus dem RecyclerView Animations- und GitHub-Beispiel

1) Fügen Sie Listener in Ihren onCreateViewHolder () ein, um sich für onClick zu registrieren

2) Erstellen Sie Ihren benutzerdefinierten OnClickListener in Ihrem Adapter

private View.OnClickListener mItemListener = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        TextView tv = (TextView) v.findViewById(R.id.tvItems);
        String selected = tv.getText().toString();
        boolean checked = itemsList.get(recyclerView.getChildAdapterPosition(v)).isChecked();

        switch (selected){
            case "Item1":
                if(checked){
                    deleteItem(v);
                    itemsList.get(recyclerView.getChildAdapterPosition(v)).setChecked(false);
                }else {
                    addItem(v);
                    itemsList.get(recyclerView.getChildAdapterPosition(v)).setChecked(true);
                }
                break;
            case "Item2":
                if(checked){
                    deleteItem(v);
                    itemsList.get(recyclerView.getChildAdapterPosition(v)).setChecked(false);
                }else {
                    addItem(v);
                    itemsList.get(recyclerView.getChildAdapterPosition(v)).setChecked(true);
                }
                break;                 
            default:
                //In my case I have checkList in subItems,
                //checkItem(v);
                break;
        }
    }
};

3) Fügen Sie Ihr addItem () und deleteItem () hinzu

private void addItem(View view){
    int position = recyclerView.getChildLayoutPosition(view);
    if (position != RecyclerView.NO_POSITION){
        navDrawItems.add(position+1,new mObject());
        navDrawItems.add(position+2,new mObject());
        notifyItemRangeInserted(position+1,2);
    }
}


private void deleteItem(View view) {
    int position = recyclerView.getChildLayoutPosition(view);
    if (position != RecyclerView.NO_POSITION) {
        navDrawItems.remove(position+2);
        navDrawItems.remove(position+1);
        notifyItemRangeRemoved(position+1,2);
    }
}

4) Wenn sich Ihr RecyclerViewAdapter nicht in derselben Aktivität wie die Recycler-Ansicht befindet , übergeben Sie beim Erstellen die Instanz von recyclerView an den Adapter

5) itemList ist eine ArrayList vom Typ mObject, mit deren Hilfe der Status von Element (Öffnen / Schließen), Name, Elementtyp (subItems / mainItem) und das Thema basierend auf Werten festgelegt werden können

public class mObject{
    private String label;
    private int type;
    private boolean checked;
} 
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.