Rozszerzalna lista z RecyclerView?

95

Czy w nowym RecyclerView można używać rozwijanych elementów listy? Podoba Ci się ExpandableListView?

Dariusz Rusin
źródło
1
zegar Androida widać w google source na Androidzie 6.0
zyski
@zys Gdzie mogę znaleźć ten przykładowy kod źródłowy zegara Androida?
AppiDevo
1
Możesz użyć różnych viewType, aby załadować inny układ po kliknięciu przycisku rozwijania.Tego rozwiązania używa Android Clock: android.googlesource.com/platform/packages/apps/DeskClock
zys
zobacz moją prostą odpowiedź stackoverflow.com/a/48092441/5962715
Kiran Benny Joseph

Odpowiedzi:

121

Jest to łatwe do zrobienia za pomocą standardowych LayoutManagerów, wszystko zależy od tego, jak zarządzasz kartą.

Jeśli chcesz rozwinąć sekcję, po prostu dodaj nowe elementy do adaptera po nagłówku. Pamiętaj, aby w tym celu zadzwonić do notificationItemRangeInserted. Aby zwinąć sekcję, po prostu usuwasz odpowiednie elementy i wywołujesz notifyItemRangeRemoved (). W przypadku wszelkich zmian danych, które zostaną odpowiednio zgłoszone, widok recyklera będzie animował widoki. Podczas dodawania elementów tworzony jest obszar, który ma zostać wypełniony nowymi elementami, a nowe elementy znikają. Usuwanie jest odwrotne. Jedyne, co musisz zrobić, poza elementami adaptera, to stylizować widoki, aby przekazać logiczną strukturę użytkownikowi.

Aktualizacja: Ryan Brooks napisał teraz artykuł o tym, jak to zrobić.

Tonic Artos
źródło
Świetna sugestia. Ciekawe, dlaczego nikt inny nie zagłosował za tą odpowiedzią !!
x-treme
Postaram się dodać to jako przykład do SuperSLiM jako część następnej wersji.
Tonic Artos
Ryan Brooks napisał teraz artykuł o tym, jak to zrobić.
Tonic Artos
Świetna sugestia. Dlaczego ta odpowiedź jest tak daleko w dół strony, że muszę ją przewinąć, aby ją znaleźć? Powinien być pokazany na samej górze, aby więcej osób mogło łatwiej znaleźć tę piękną odpowiedź.
RestInPeace
1
Ryan Brooks oznaczył swoją bibliotekę jako przestarzałą. Zastanawiam się, czy to po prostu to, że przestał to wspierać, czy też okazuje się, że takie podejście coś psuje lub powoduje wycieki pamięci lub coś ...
Varvara Kalinina
6

Pobierz przykładową implementację kodu stąd

Ustaw ValueAnimator wewnątrz onClick of 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();

}

Oto ostateczny kod

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

    }
}
Kavin Varnan
źródło
11
To nie działa „jak ExpandableListView”, ponieważ w tym przypadku rozwinięta zawartość jest listą zawierającą elementy pochodzące z adaptera. To zdegenerowane rozwiązanie, w którym tylko 1 pozycja jest dozwolona jako dzieci w grupie.
TWiStErRob
nie działa też poprawnie z widokami, które w ogóle są przetwarzane
takecare
Nie działa poprawnie, ponieważ widok w RecyclerList może być powtarzany wiele razy, więc po rozwinięciu jednej pozycji zobaczysz, że wiele pozycji na liście jest rozwiniętych
Hossein Shahdoost
3

https://github.com/gabrielemariotti/cardslib

Ta biblioteka zawiera implementację listy rozwijanej z widokiem recyklingu (patrz aplikacja demonstracyjna w sekcji „CardViewNative” -> „List, Grid i RecyclerView” -> „Expandable cards”). Zawiera również wiele innych fajnych kombinacji kart / list.

Xinzz
źródło
2
Ta lista kart rozwijanych nie jest elementem podrzędnym RecyclerView (nie rozszerza RecyclerView, tylko rozszerza ExpandableListVIew)
whizzzkey
0

Ktoś narzekał, że powyższe rozwiązanie nie nadaje się do użytku z widokiem listy jako treścią rozwijaną. Ale jest proste rozwiązanie: utwórz widok listy i wypełnij ten widok listy ręcznie swoimi wierszami .

Rozwiązanie dla leniwych: istnieje proste rozwiązanie, jeśli nie chcesz zbytnio zmieniać kodu. Po prostu ręcznie użyj adaptera, aby utworzyć widoki i dodać je do LinearLayout.

Oto przykład:

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

funkcje pomocnicze: 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;
    }
}

klasa pomocnika: ExpandUtils

Kavin Varnan postet już, jak animować układ ... Ale jeśli chcesz skorzystać z mojej klasy, nie krępuj się, zamieściłem streszczenie: https://gist.github.com/MichaelFlisar/738dfa03a1579cc7338a

prom85
źródło
9
„Leniwe rozwiązanie” to naprawdę zły pomysł. Dodawanie widoków do układu liniowego wewnątrz widoku przewijalnego jest tak nieefektywne.
Matthew
Myślę, że jest co najmniej więcej niż użyteczne. Działa szybko i płynnie na wszystkich testowanych urządzeniach. Nawiasem mówiąc, widoki są dodawane, gdy widok listy nie jest widoczny ... później zostanie wyświetlony tylko już wypełniony widok listy ...
prom85
To było fajne! Wielkie dzięki
Pawan Kumar
1
Jak wspomniał @Matthew, to naprawdę nie jest dobry pomysł. Pojedynczy duży przewijalny widok z układami liniowymi nie jest dobrze skalowany. Jednym z głównych powodów, dla których RecyclerView / ListView i inne podobne widoki zostały napisane, była optymalizacja posiadania dużych list wspieranych danymi o nieznanym rozmiarze. Zbudowanie pojedynczego widoku z kilkoma dodanymi widokami powoduje odrzucenie wszystkich dostępnych optymalizacji. Recykling widoków to ogromna korzyść i zwiększa wydajność pamięci aplikacji. O ile liczba elementów nie jest niewielka, można zaoszczędzić wiele pracy, korzystając z list do obsługi powiązania widoku.
munkay
Masz całkowitą rację, oczywiście to nie jest idealne i niczego nie optymalizuje ... Do moich zastosowań zawsze miałem tylko kilka pozycji ... Więc to nie był problem ... Przy okazji, w międzyczasie znalazłem sposób dla zagnieżdżonych widoków recyklera ... Po prostu użyj poziomego widoku recyklera o stałej wysokości (nie jest idealny dla każdego przypadku, ale nadal) jako zagnieżdżonego recyclerviewi możesz rozwinąć / ukryć ten zagnieżdżony widok i wykorzystać wszystkie optymalizacjerecyclerview
prom85
0

To jest przykładowy kod tego, o czym wspomina @TonicArtos, aby dodawać i usuwać elementy oraz animować go podczas wykonywania, pochodzi z RecyclerView Animations i przykład GitHub

1) Dodaj Listener wewnątrz swojej onCreateViewHolder (), aby zarejestrować się w onClick

2) Utwórz niestandardowy OnClickListener wewnątrz adaptera

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) Dodaj swój addItem () i 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) Jeśli RecyclerViewAdapter nie jest w tym samym działalność jako Recycler View , przechodzą instancję recyclerView do zasilacza podczas tworzenia

5) itemList to ArrayList typu mObject, który pomaga zachować stany pozycji (Open / Close), nazwę, typ Item (subItems / mainItem) i ustawić motyw na podstawie wartości

public class mObject{
    private String label;
    private int type;
    private boolean checked;
} 
Ujju
źródło