Expandable list with RecyclerView?
Asked Answered
K

6

98

It's possible to use expandable list items with new RecyclerView? Like ExpandableListView?

Khiva answered 17/10, 2014 at 6:30 Comment(5)
you can see the android clock in google source on android 6.0Fishbolt
@Fishbolt Where can I find this android clock example source code?Calica
You can use different viewType to load different layout, when you click expand button.This solution is used by Android Clock : android.googlesource.com/platform/packages/apps/DeskClockFishbolt
see my simple answer https://mcmap.net/q/136102/-recyclerview-expand-collapse-itemsTiptoe
For two levels: thoughtbot.com/blog/introducing-expandablerecyclerview. For three and more levels: blog.usejournal.com/…, github.com/bmelnychuk/AndroidTreeView, karthicandroid.blogspot.com/2016/08/….Concretize
T
129

This is simple to do with the stock LayoutManagers, it all depends on how you manage your adapter.

When you want to expand a section you just add new items to your adapter after the header. Remember to call notifyItemRangeInserted when you do this. To collapse a section you simply remove the relevant items, and call notifyItemRangeRemoved(). For any data changes that are appropriately notified, the recycler view will animate the views. When adding items, an area to be filled with the new items is made, with the new items fading in. Removal is the opposite. All you need to do besides the adapter stuff is to style your views to convey the logical structure to the user.

Update: Ryan Brooks has now written an article on how to do this.

Thrawn answered 20/3, 2015 at 10:29 Comment(9)
Great suggestion. Wonder why no one else upvoted this answer!!Manvil
I'll see about adding this as an example to SuperSLiM as a part of the next release.Thrawn
Ryan Brooks has now written an article on how to do this.Thrawn
Great suggestion. Why is this answer so far down the page that I have to scroll down to find it? It should be shown at the very top so more people can find this beautiful answer more easily.Freeloader
This is the perfect solution which preserves the recycleness of RecyclerView.Danelledanete
Ryan Brooks marked his library as deprecated. I wonder if it's just that he has stopped supporting it or it turns out that this approach breaks something or creates memory leaks or sth...Alisander
@VarvaraKalinina, yes, Варвара, only github.com/thoughtbot/expandable-recycler-view (a link from his repository) matters. His project and Groopie are not suitable.Concretize
can we children for children in ExpandableRecyclerView? @TonicArtosCarrol
Not my library. @CarrolThrawn
M
4

Get the sample code implementation from here

Set ValueAnimator inside 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();

}

Here is the final 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();

    }
}
Moralez answered 18/10, 2014 at 19:38 Comment(3)
This doesn't work "like ExpandableListView", because the expanded content in that case is a list itself with items coming from the adapter. This is a degenerate solution with only 1 item allowed as children inside the group.Dishearten
nor does it work properly with views that get recycled at allDrove
It doesn't work properly as a view in RecyclerList might be repeated multiple times, so when you expand one item, You see multiple items on the list are expandedTimothy
B
3

https://github.com/gabrielemariotti/cardslib

This library has an implementation of an expandable list with a recyclerview (refer to the demo app under "CardViewNative" --> "List, Grid, and RecyclerView" --> "Expandable cards"). It also has a lot of other cool combinations of cards/lists.

Blackfish answered 1/2, 2015 at 20:52 Comment(1)
This Expandable Cards list isn't a child of RecyclerView (it doesn't extends RecyclerView, it's only extending ExpandableListVIew)Tabby
B
0

Someone complained about that the above mentioned solution is not usable with a listview as expandable content. But there's a simple solution: create a listview and fill this listview manually with your rows.

Solution for the lazy ones: there's a simple solution if you don't want to change your code to much. Just manually use your adapter to create views and add them to the LinearLayout.

Here's the example:

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

helper functions: 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;
    }
}

helper class: ExpandUtils

Kavin Varnan postet already how to animate a layout... But if you want to use my class, feel free to do so, I posted a gist: https://gist.github.com/MichaelFlisar/738dfa03a1579cc7338a

Bond answered 29/1, 2015 at 7:51 Comment(4)
The "Lazy Solution" is a really bad idea. Adding views to a linearlayout inside a scrollable view is so inefficient.Canonicate
I think it is at least more than usable. Works fast an smooth on all devices I tested it. By the way , the views are added when the listview is not visible... only the already filled list view will afterwards be shown...Bond
As @Canonicate mentioned, this is really not a good idea. Having a single large scrollable view w/ LinearLayouts does not scale well. One of the biggest reasons RecyclerView / ListView and other similar views were written was to optimize having large data-backed lists of unknown size. Building a single view w/ a bunch of views added throws out all the optimizations provided. Recycling views is a huge benefit, and makes your app memory efficient. Unless the number of items is tiny, there's a lot of work saved by using lists to handle view binding.Toper
You are comlpetely right, of course this is not perfect and does not optimize anything... For my usecases, I always only had a few items... So this was no problem... Btw, in the meantime I found a way for nested recycler views... Just use a fixed height horizontal recycler view (does not be perfect for every usecase, but still) as a nested recyclerview and you can expand/hide this nested one and use all optimisations of the recyclerviewBond
H
0

You can use ExpandableLayout that like a smooth expand/collapse animation CheckBox, so you can use it as CheckBox in ListView and RecyclerView.

https://github.com/KyoSherlock/ExpandableLayout

Habited answered 29/7, 2015 at 1:20 Comment(0)
P
0

This is the sample code for what is mentioned by @TonicArtos to add and remove Items and to animate it while doing, this is taken from RecyclerView Animations and GitHub sample

1) Add Listener inside your onCreateViewHolder() to register for onClick

2) Create your custom OnClickListener inside your 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) Add your addItem() and 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) If your RecyclerViewAdapter is not in the same Activity as Recycler View, pass instance of recyclerView to the Adapter while creating

5) itemList is a ArrayList of type mObject which helps maintain states of item (Open/Close) , name, type of Item(subItems/mainItem) and set Theme based on values

public class mObject{
    private String label;
    private int type;
    private boolean checked;
} 
Procurable answered 25/10, 2016 at 15:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.