Elenco espandibile con RecyclerView?


95

È possibile utilizzare voci di elenco espandibili con il nuovo RecyclerView? Ti piace ExpandableListView?


1
puoi vedere l'orologio di Android in Google Source su Android 6.0
zys

@zys Dove posso trovare questo codice sorgente di esempio di orologio Android?
AppiDevo

1
È possibile utilizzare un diverso tipo di visualizzazione per caricare un layout diverso, quando si fa clic sul pulsante di espansione.Questa soluzione viene utilizzata da Android Clock: android.googlesource.com/platform/packages/apps/DeskClock
zys


Risposte:


121

Questo è semplice da fare con i LayoutManagers di serie, tutto dipende da come gestisci l'adattatore.

Quando si desidera espandere una sezione, è sufficiente aggiungere nuovi elementi all'adattatore dopo l'intestazione. Ricordarsi di chiamare notifyItemRangeInserted quando si esegue questa operazione. Per comprimere una sezione è sufficiente rimuovere gli elementi rilevanti e chiamare notifyItemRangeRemoved (). Per qualsiasi modifica ai dati opportunamente notificata, la vista riciclatore animerà le viste. Quando si aggiungono elementi, viene creata un'area da riempire con i nuovi elementi, con i nuovi elementi che si dissolvono. La rimozione è l'opposto. Tutto quello che devi fare oltre alle cose dell'adattatore è modellare le tue viste per trasmettere la struttura logica all'utente.

Aggiornamento: Ryan Brooks ha ora scritto un articolo su come farlo.


Ottimo suggerimento. Chissà perché nessun altro ha votato positivamente questa risposta !!
x-treme

Vedrò di aggiungere questo come esempio a SuperSLiM come parte della prossima versione.
Tonic Artos

Ryan Brooks ha ora scritto un articolo su come farlo.
Tonic Artos

Ottimo suggerimento. Perché questa risposta è così in basso nella pagina che devo scorrere verso il basso per trovarla? Dovrebbe essere mostrato in alto in modo che più persone possano trovare questa bella risposta più facilmente.
RestInPeace

1
Ryan Brooks ha contrassegnato la sua libreria come deprecata. Mi chiedo se è solo che ha smesso di supportarlo o si scopre che questo approccio rompe qualcosa o crea perdite di memoria o qc ...
Varvara Kalinina

6

Ottieni l'implementazione del codice di esempio da qui

Impostare ValueAnimator all'interno di onClick di ViewHolder

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

}

Ecco il codice finale

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
Questo non funziona "come ExpandableListView", perché il contenuto espanso in quel caso è un elenco stesso con elementi provenienti dall'adattatore. Questa è una soluzione degenerata con solo 1 elemento consentito come bambini all'interno del gruppo.
TWiStErRob

né funziona correttamente con le viste che vengono riciclate affatto
takecare

Non funziona correttamente poiché una visualizzazione in RecyclerList potrebbe essere ripetuta più volte, quindi quando espandi un elemento, vedi che più elementi nell'elenco sono espansi
Hossein Shahdoost

3

https://github.com/gabrielemariotti/cardslib

Questa libreria ha un'implementazione di un elenco espandibile con una visualizzazione di riciclo (fare riferimento all'app demo in "CardViewNative" -> "Elenco, griglia e RecyclerView" -> "Schede espandibili"). Ha anche molte altre fantastiche combinazioni di carte / elenchi.


2
Questo elenco di carte espandibili non è figlio di RecyclerView (non estende RecyclerView, estende solo
ExpandableListVIew

0

Qualcuno si è lamentato del fatto che la soluzione sopra menzionata non è utilizzabile con una visualizzazione elenco come contenuto espandibile. Ma c'è una soluzione semplice: crea una listview e riempila manualmente con le tue righe .

Soluzione per i pigri: c'è una soluzione semplice se non vuoi cambiare molto il tuo codice. Basta usare manualmente l'adattatore per creare visualizzazioni e aggiungerle al file LinearLayout.

Ecco l'esempio:

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

funzioni di supporto: 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;
    }
}

classe di supporto: ExpandUtils

Kavin Varnan ha già postato su come animare un layout ... Ma se vuoi usare la mia classe, sentiti libero di farlo, ho pubblicato una sintesi: https://gist.github.com/MichaelFlisar/738dfa03a1579cc7338a


9
La "Lazy Solution" è davvero una pessima idea. L'aggiunta di viste a un layout lineare all'interno di una vista scorrevole è così inefficiente.
Matteo

Penso che sia almeno più che utilizzabile. Funziona velocemente e senza intoppi su tutti i dispositivi che ho testato. A proposito, le visualizzazioni vengono aggiunte quando la visualizzazione elenco non è visibile ... successivamente verrà mostrata solo la visualizzazione elenco già riempita ...
prom85

Questo è stato pulito! Grazie
mille

1
Come ha detto @Matthew, questa non è davvero una buona idea. Avere un'unica grande vista scorrevole con LinearLayouts non viene ridimensionata bene. Uno dei motivi principali per cui RecyclerView / ListView e altre visualizzazioni simili sono state scritte era l'ottimizzazione con elenchi di grandi dimensioni supportati da dati di dimensioni sconosciute. La creazione di una visualizzazione singola con un gruppo di visualizzazioni aggiunte elimina tutte le ottimizzazioni fornite. Il riciclaggio delle visualizzazioni è un enorme vantaggio e rende efficiente la memoria dell'app. A meno che il numero di elementi non sia esiguo, è possibile risparmiare molto lavoro utilizzando gli elenchi per gestire l'associazione delle viste.
lunedì

Hai perfettamente ragione, ovviamente questo non è perfetto e non ottimizza nulla ... Per i miei casi ho sempre avuto solo pochi articoli ... Quindi questo non era un problema ... A proposito, nel frattempo ho trovato un modo per le viste del riciclatore annidato ... Basta usare una vista del riciclatore orizzontale ad altezza fissa (non è perfetta per ogni caso d'uso, ma comunque) come nidificata recyclerviewe puoi espandere / nascondere questa nidificata e utilizzare tutte le ottimizzazioni delrecyclerview
prom85


0

Questo è il codice di esempio per ciò che è menzionato da @TonicArtos per aggiungere e rimuovere elementi e per animarlo mentre lo fa, questo è preso da RecyclerView Animations e GitHub sample

1) Aggiungi Listener all'interno del tuo onCreateViewHolder () per registrarti a onClick

2) Crea il tuo OnClickListener personalizzato all'interno del tuo 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) Aggiungi il tuo addItem () e deleteItem ()

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) Se RecyclerViewAdapter non è nella stessa attività di Recycler View , passare l'istanza di recyclerView all'adapter durante la creazione

5) itemList è un ArrayList di tipo mObject che aiuta a mantenere gli stati dell'elemento (Open / Close), il nome, il tipo di Item (subItems / mainItem) e imposta il tema in base ai valori

public class mObject{
    private String label;
    private int type;
    private boolean checked;
} 
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.