98

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

Dariusz Rusin
  • 1,105
  • 1
  • 9
  • 7
  • 1
    you can see the android clock in google source on android 6.0 – zys Apr 27 '16 at 13:17
  • @zys Where can I find this android clock example source code? – AppiDevo Dec 15 '16 at 14:05
  • 1
    You can use different viewType to load different layout, when you click expand button.This solution is used by Android Clock : https://android.googlesource.com/platform/packages/apps/DeskClock/ – zys Dec 15 '16 at 14:34
  • see my simple answer https://stackoverflow.com/a/48092441/5962715 – Kiran Benny Joseph Feb 22 '18 at 06:48
  • For two levels: https://thoughtbot.com/blog/introducing-expandablerecyclerview. For three and more levels: https://blog.usejournal.com/multi-level-expandable-recycler-view-e75cf1f4ac4b?gi=8f971378ece0, https://github.com/bmelnychuk/AndroidTreeView, https://karthicandroid.blogspot.com/2016/08/shopping-navigation-list-with-three.html. – CoolMind Apr 11 '19 at 12:58

6 Answers6

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.

Tonic Artos
  • 1,568
  • 1
  • 10
  • 7
  • Great suggestion. Wonder why no one else upvoted this answer!! – x-treme Mar 24 '15 at 00:49
  • I'll see about adding this as an example to SuperSLiM as a part of the next release. – Tonic Artos Jun 05 '15 at 17:14
  • Ryan Brooks has now written an [article](https://www.bignerdranch.com/blog/expand-a-recyclerview-in-four-steps/?utm_source=Android+Weekly&utm_campaign=8f0cc3ff1f-Android_Weekly_165&utm_medium=email&utm_term=0_4eb677ad19-8f0cc3ff1f-337834121) on how to do this. – Tonic Artos Aug 09 '15 at 20:12
  • 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. – RestInPeace May 22 '16 at 02:47
  • This is the perfect solution which preserves the **recycleness** of `RecyclerView`. – Meet Vora Oct 04 '16 at 12:07
  • 2
    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... – Varvara Kalinina Feb 13 '18 at 13:20
  • @VarvaraKalinina, yes, Варвара, only https://github.com/thoughtbot/expandable-recycler-view (a link from his repository) matters. His project and Groopie are not suitable. – CoolMind Apr 10 '19 at 09:17
  • can we children for children in ExpandableRecyclerView? @TonicArtos – VINNUSAURUS Apr 30 '19 at 19:16
  • Not my library. @VINNUSAURUS – Tonic Artos May 03 '19 at 08:14
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();

    }
}
Kavin Varnan
  • 1,989
  • 18
  • 23
  • 14
    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. – TWiStErRob Jan 10 '15 at 12:38
  • nor does it work properly with views that get recycled at all – takecare Jul 31 '15 at 11:39
  • 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 expanded – Hossein Shahdoost Jun 04 '17 at 15:15
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.

Xinzz
  • 2,242
  • 1
  • 13
  • 26
  • 2
    This Expandable Cards list isn't a child of RecyclerView (it doesn't extends RecyclerView, it's only extending ExpandableListVIew) – whizzzkey Jun 05 '15 at 01:25
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

prom85
  • 16,896
  • 17
  • 122
  • 242
  • 9
    The "Lazy Solution" is a really bad idea. Adding views to a linearlayout inside a scrollable view is so inefficient. – Matthew Feb 23 '15 at 20:08
  • 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... – prom85 Feb 24 '15 at 19:24
  • This was neat! Thanks a ton – Pawan Kumar Mar 19 '15 at 22:35
  • 1
    As @Matthew 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. – munkay Jun 14 '15 at 21:05
  • 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 `recyclerview` – prom85 Jun 15 '15 at 06:21
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

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;
} 
Ujju
  • 2,723
  • 3
  • 25
  • 35