178

I want to expand/collapse the items of my recyclerView in order to show more info. I want to achieve the same effect of the SlideExpandableListView.

Basically in my viewHolder I have a view that is not visible and I want to do a smooth expand/collapse animation rather than set the visibility to VISIBLE/GONE only. I only need an item to be expanded at a time and it would be cool to have some elevation to show that the item is selected.

It is the same effect of the new Android recent calls history list. The options "CALL BACK" and "DETAILS" are visible only when an item is selected.

RecyclerView expandable items

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
stanete
  • 4,062
  • 9
  • 21
  • 30

13 Answers13

215

Please don't use any library for this effect instead use the recommended way of doing it according to Google I/O. In your recyclerView's onBindViewHolder method do this:

final boolean isExpanded = position==mExpandedPosition;
holder.details.setVisibility(isExpanded?View.VISIBLE:View.GONE);
holder.itemView.setActivated(isExpanded);
holder.itemView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        mExpandedPosition = isExpanded ? -1:position;
        TransitionManager.beginDelayedTransition(recyclerView);
        notifyDataSetChanged();
    }
});
  • Where details is my view that will be displayed on touch (call details in your case. Default Visibility.GONE).
  • mExpandedPosition is an int variable initialized to -1

And for the cool effects that you wanted, use these as your list_item attributes:

android:background="@drawable/comment_background"
android:stateListAnimator="@animator/comment_selection"

where comment_background is:

<selector
xmlns:android="http://schemas.android.com/apk/res/android"
android:constantSize="true"
android:enterFadeDuration="@android:integer/config_shortAnimTime"
android:exitFadeDuration="@android:integer/config_shortAnimTime">

<item android:state_activated="true" android:drawable="@color/selected_comment_background" />
<item android:drawable="@color/comment_background" />
</selector>

and comment_selection is:

<selector xmlns:android="http://schemas.android.com/apk/res/android">

<item android:state_activated="true">
    <objectAnimator
        android:propertyName="translationZ"
        android:valueTo="@dimen/z_card"
        android:duration="2000"
        android:interpolator="@android:interpolator/fast_out_slow_in" />
</item>

<item>
    <objectAnimator
        android:propertyName="translationZ"
        android:valueTo="0dp"
        android:duration="2000"
        android:interpolator="@android:interpolator/fast_out_slow_in" />
</item>
</selector>
Heisenberg
  • 5,514
  • 2
  • 32
  • 43
  • 1
    where is the video of this ? – belle tian Aug 10 '16 at 07:17
  • 1
    If one expanded then the other collapses – kgandroid Aug 17 '16 at 08:06
  • 24
    When using this code the expand-animation looks weird. Everytime the top item disappears and everything shifts up one position. Have you ever experienced something similar? – chris-pollux Oct 25 '16 at 10:30
  • @Vacutainer yep. Any fixes? – Ranveer Dec 24 '16 at 08:30
  • 1
    Not an official google project. It was a sample from a google engineer "Nick Butcher" who played around with new animation features for demo purposes. – Heisenberg Jan 11 '17 at 13:56
  • I get exactly the same issue with the whole list looking weird when selecting it. Might have to relook at how I've got my list setup. Also, the stateListAnimator isn't available on Pre-21 devices which isn't that good for anyone using AppCompat :/ So, this doesn't work. – MattBoothDev Jan 22 '17 at 18:18
  • 1
    Just to add that notifyItemChanged(position) works better than notifyDataSetChanged(). Less clunky, but still not entirely right – MattBoothDev Jan 22 '17 at 18:29
  • 7
    I try to do the animation but I implement this on the adapter, but what would be the solution to the `TransitionManager.beginDelayedTransition(recyclerView);` if I cannot hace the recycler view reference. –  Feb 03 '17 at 17:36
  • 2
    @Ranveer It works kind of. I have two quite similar recyclerViews, but it only works correctly for one. No idea why. @MartínDaniel You need to pass the `recyclerView` as a parameter in the constructor for the adapter – chris-pollux Feb 07 '17 at 05:52
  • 39
    This is an awful approach. I can't believe it was recommended at Google I/O. Even after investigating the Plaid project and implementing all the required workarounds from it, the animation still causes some major glitches in RecyclerView, e.g. first/last visible item disappearing prematurely. Also scrolling while the animation runs messes all things badly. In general, I don't think fixing all the issues caused by this approach may be called "just put one line and everything will just work" as advertised. – olshevski May 03 '17 at 15:01
  • I have used this and i have two other list in the child view. This is taking more than 2 seconds to expand and this approach doesn't seems to be useful when you have complex things in your child view. Any other approach for accordion? – Kannan_SJD Aug 04 '17 at 14:10
  • Very helpful but any idea about animation duration respect to material design guideline? – TeachMeJava Aug 08 '17 at 21:27
  • When there are few items in list this approach is working fine. But items exceeds screen, glitch like reloading screen, moving elements starts to happen. Is there solution for this? – Rafael Aug 23 '18 at 05:52
  • 9
    Do we have some more fresh & easy way in 2019 for this issue? – Ponomarenko Oleh May 15 '19 at 14:35
  • Due to first item set as GONE the child button click is not functional and works when any other item is clicked. – Mr X Nov 15 '19 at 08:33
  • 2
    The smoothest recyclerview expand/collapse animation I saw is here: https://proandroiddev.com/complex-ui-animation-on-android-8f7a46f4aec4 – Rafael Apr 15 '20 at 10:39
  • Does anyone have a complete running example code? Please post a link here if you have an example project to solve this issue? – Munazza Jul 04 '20 at 07:00
  • I wonder how this is the most up-voted answer, first of all it's bad practice memory-wise to implement click listener inside `onBindViewHolder` and second calling `notifyDataSetChanged` is way too wrong because that causes whole recyclerview to be refreshed when in reality only one item is changed. – YaMiN Apr 28 '21 at 01:45
104

For this, just needed simple lines not complicated

in your onBindViewHolder method add below code

final boolean isExpanded = position==mExpandedPosition;
holder.details.setVisibility(isExpanded?View.VISIBLE:View.GONE);
holder.itemView.setActivated(isExpanded);
holder.itemView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        mExpandedPosition = isExpanded ? -1:position;
        notifyItemChanged(position);
    }
});

mExpandedPosition is an int global variable initialized to -1

For those who want only one item expanded and others get collapsed. Use this

first declare a global variable with previousExpandedPosition = -1

then

    final boolean isExpanded = position==mExpandedPosition;
    holder.details.setVisibility(isExpanded?View.VISIBLE:View.GONE);
    holder.itemView.setActivated(isExpanded);

    if (isExpanded)
       previousExpandedPosition = position;

    holder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mExpandedPosition = isExpanded ? -1:position;
            notifyItemChanged(previousExpandedPosition);
            notifyItemChanged(position);
        }
    });
Błażej Michalik
  • 4,474
  • 40
  • 55
Kiran Benny Joseph
  • 6,755
  • 4
  • 38
  • 57
  • 2
    great solution. Also I'd recommend to move setting `OnClickListener`s to `onCreateViewHolder` instead of `onBindViewHolder` – YTerle Feb 21 '18 at 15:26
  • 2
    Works very well, about time there was a decent solution that just lets the `RecyclerView` do the work, I mean, whats the point of the `LayoutManager` otherwise if it doesn't manage the layouts! This solution is better than the top voted solution as it has less code, lets the `RecyclerView` handle things and works better! – Mark Mar 09 '18 at 23:23
  • 2
    did not quite understand how to add "details" view. Should it be static for each holder? What if for each list items I need to add different views? – BekaBot Mar 27 '18 at 17:09
  • @BekaBot it is the view that you want to hide during collapsing item – Kiran Benny Joseph Mar 28 '18 at 06:08
  • 1
    @KiranBennyJoseph yes, I understand. In this case the view should be the same for all items? What if I want different views for different items? – BekaBot Mar 28 '18 at 06:18
  • @BekaBot, in this case, I showed only one view. You should make it appropriate for you. – Kiran Benny Joseph Mar 28 '18 at 08:41
  • This works well however if there are any images present in the row then they will flicker. Is there a solution for this? – jL4 Apr 14 '18 at 07:06
  • @KiranBennyJoseph : your solution works perfectly fine but images in my row item flicker as I expand and collapse the view. Is there any solution for this ? – Pragya Mendiratta Jul 16 '18 at 12:43
  • @KiranBennyJoseph I guess the flicker is due to updating the adapter again the second time with `notifyItemChanged(position);`. What exactly does this line of code acheive? I'm a beginner, hope you don't mind. Thanks – Nuhman Oct 01 '18 at 06:59
  • how dynamically loading web api data for expand and collapse please helpme – Harsha Oct 24 '18 at 10:33
  • This works like a charm, but I have an icon to indicate the expand and collapse, I trying to use a rotate animation `mExpandArrow.animate().rotation(rotationAngle).setDuration(500).start(); `, with notifyItemChanged I lose the animation, how can I fix this? – Clamorious Jun 30 '19 at 19:09
  • @Kiran Benny Joseph ... Grt !! Simple and perfect solution. – Fakeeraddi Bhavi Jul 31 '20 at 10:54
  • perfecto, thank you @KiranBennyJoseph , upvoted for saving my day. – Ravi Vaniya Sep 22 '20 at 12:21
  • Is it possible to move this to ViewHolder.bind()? – DaPoox Aug 31 '22 at 09:33
76

Not saying this is the best approach, but it seems to work for me.

The full code may be found at: Example code at: https://github.com/dbleicher/recyclerview-grid-quickreturn

First off, add the expanded area to your cell/item layout, and make the enclosing cell layout animateLayoutChanges="true". This will ensure that the expand/collapse is animated:

<LinearLayout
    android:id="@+id/llCardBack"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:background="@android:color/white"
    android:animateLayoutChanges="true"
    android:padding="4dp"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tvTitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center|fill_horizontal"
        android:padding="10dp"
        android:gravity="center"
        android:background="@android:color/holo_green_dark"
        android:text="This is a long title to show wrapping of text in the view."
        android:textColor="@android:color/white"
        android:textSize="16sp" />

    <TextView
        android:id="@+id/tvSubTitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center|fill_horizontal"
        android:background="@android:color/holo_purple"
        android:padding="6dp"
        android:text="My subtitle..."
        android:textColor="@android:color/white"
        android:textSize="12sp" />

    <LinearLayout
        android:id="@+id/llExpandArea"
        android:visibility="gone"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="6dp"
            android:text="Item One" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="6dp"
            android:text="Item Two" />

    </LinearLayout>
</LinearLayout>

Then, make your RV Adapter class implement View.OnClickListener so that you can act on the item being clicked. Add an int field to hold the position of the one expanded view, and initialize it to a negative value:

private int expandedPosition = -1;

Finally, implement your ViewHolder, onBindViewHolder() methods and override the onClick() method. You will expand the view in onBindViewHolder if it's position is equal to "expandedPosition", and hide it if not. You set the value of expandedPosition in the onClick listener:

@Override
public void onBindViewHolder(RVAdapter.ViewHolder holder, int position) {

    int colorIndex = randy.nextInt(bgColors.length);
    holder.tvTitle.setText(mDataset.get(position));
    holder.tvTitle.setBackgroundColor(bgColors[colorIndex]);
    holder.tvSubTitle.setBackgroundColor(sbgColors[colorIndex]);

    if (position == expandedPosition) {
        holder.llExpandArea.setVisibility(View.VISIBLE);
    } else {
        holder.llExpandArea.setVisibility(View.GONE);
    }
}

@Override
public void onClick(View view) {
    ViewHolder holder = (ViewHolder) view.getTag();
    String theString = mDataset.get(holder.getPosition());

    // Check for an expanded view, collapse if you find one
    notifyDataSetChanged();

    // Set the current position to "expanded"
    expandedPosition = holder.getPosition();
    notifyItemChanged(expandedPosition);

    Toast.makeText(mContext, "Clicked: "+theString, Toast.LENGTH_SHORT).show();
}

/**
 * Create a ViewHolder to represent your cell layout
 * and data element structure
 */
public static class ViewHolder extends RecyclerView.ViewHolder {
    TextView tvTitle;
    TextView tvSubTitle;
    LinearLayout llExpandArea;

    public ViewHolder(View itemView) {
        super(itemView);

        tvTitle = (TextView) itemView.findViewById(R.id.tvTitle);
        tvSubTitle = (TextView) itemView.findViewById(R.id.tvSubTitle);
        llExpandArea = (LinearLayout) itemView.findViewById(R.id.llExpandArea);
    }
}

This should expand only one item at a time, using the system-default animation for the layout change. At least it works for me. Hope it helps.

Manideep
  • 353
  • 3
  • 13
MojoTosh
  • 1,886
  • 1
  • 18
  • 23
  • I know how to keep only one item selected. I don't really know how to do the animation on that item. The expand/collapse animation with the default system animation doesn't look very smooth. – stanete Dec 01 '14 at 22:10
  • 1
    I haven't tried the following yet, but it sure looks like it will work. Check out the answer at: http://stackoverflow.com/a/26443762/2259418 – MojoTosh Dec 06 '14 at 23:28
  • I have a related question. In the Clock app, alarm set activity has a similar feature. Is it done the same way? – user2731584 May 02 '15 at 13:38
  • 2
    @user2731584 - I don't think so. Looking at (what I believe is) their source, it appears that the Lollipop clock is using a ListView to display the list of alarms. Layout: https://android.googlesource.com/platform/packages/apps/DeskClock/+/android-5.0.1_r1/res/layout/alarm_clock.xml They appear to have custom code to animate the expand/collapse items in this list. See the following source: https://android.googlesource.com/platform/packages/apps/DeskClock/+/android-5.0.1_r1/src/com/android/deskclock/AlarmClockFragment.java – MojoTosh May 02 '15 at 14:20
  • Thanks for the links. I didn't know the app sources were available. – user2731584 May 02 '15 at 14:28
  • 1
    @MojoTosh you are right. They are using ListView and doing the animation using code. `expandAlarm` function in `AlarmClockFragment.java` files executes the expansion part. – user2731584 May 02 '15 at 17:42
  • I would recommend all people to peep the example at least once if you face any issue as this code is too little to make everything working. Anyways, well done. – Haresh Chaudhary Nov 12 '15 at 17:03
  • Works like a charm. Is there a way to change the duration (/speed) of the animation? – Timmiej93 Jun 26 '16 at 12:32
  • 4
    Which `onClick()` should I override? There's no `onClick` in the Adapter. – Vicky Leong Jul 29 '16 at 19:55
  • when i click item expanded, i want click on this item again for collapse how can i do that? – Ruslan_K Jul 20 '17 at 09:08
  • @vleong Please go through [this file](https://github.com/dbleicher/recyclerview-grid-quickreturn/blob/master/app/src/main/java/com/example/examplersqr/RVAdapter.java) for understanding the implementation of `onClickListener` – Kathir Jun 08 '19 at 16:50
17

There is a very simple to use library with gradle support: https://github.com/cachapa/ExpandableLayout.

Right from the library docs:

<net.cachapa.expandablelayout.ExpandableLinearLayout
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:el_duration="1000"
    app:el_expanded="true">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Click here to toggle expansion" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="Fixed height"
        app:layout_expandable="true" />

 </net.cachapa.expandablelayout.ExpandableLinearLayout>

After you mark your expandable views, just call any of these methods on the container: expand(), collapse() or toggle()

Laurențiu Onac
  • 471
  • 1
  • 7
  • 17
  • Is this still the best way to do it? I am trying to follow what you wrote. I have some recycle views that I want to expand to show a graph when clicked on – sourlemonaid Dec 28 '18 at 22:16
12

I know it has been a long time since the original question was posted. But i think for slow ones like me a bit of explanation of @Heisenberg's answer would help.

Declare two variable in the adapter class as

private int mExpandedPosition= -1;
private RecyclerView recyclerView = null;

Then in onBindViewHolder following as given in the original answer.

      // This line checks if the item displayed on screen 
      // was expanded or not (Remembering the fact that Recycler View )
      // reuses views so onBindViewHolder will be called for all
      // items visible on screen.
    final boolean isExpanded = position==mExpandedPosition;

        //This line hides or shows the layout in question
        holder.details.setVisibility(isExpanded?View.VISIBLE:View.GONE);

        // I do not know what the heck this is :)
        holder.itemView.setActivated(isExpanded);

        // Click event for each item (itemView is an in-built variable of holder class)
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

 // if the clicked item is already expaned then return -1 
//else return the position (this works with notifyDatasetchanged )
                mExpandedPosition = isExpanded ? -1:position;
    // fancy animations can skip if like
                TransitionManager.beginDelayedTransition(recyclerView);
    //This will call the onBindViewHolder for all the itemViews on Screen
                notifyDataSetChanged();
            }
        });

And lastly to get the recyclerView object in the adapter override

@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
    super.onAttachedToRecyclerView(recyclerView);

    this.recyclerView = recyclerView;
}

Hope this Helps.

shahroz butt
  • 168
  • 1
  • 7
10

There is simply no need of using third party libraries. A little tweak in the method demonstrated in Google I/O 2016 and Heisenberg on this topic, does the trick.

Since notifyDataSetChanged() redraws the complete RecyclerView, notifyDataItemChanged() is a better option (not the best) because we have the position and the ViewHolder at our disposal, and notifyDataItemChanged() only redraws the particular ViewHolder at a given position.

But the problem is that the premature disappearence of the ViewHolder upon clicking and it's emergence is not eliminated even if notifyDataItemChanged() is used.

The following code does not resort to notifyDataSetChanged() or notifyDataItemChanged() and is Tested on API 23 and works like a charm when used on a RecyclerView where each ViewHolder has a CardView as it's root element:

holder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            final boolean visibility = holder.details.getVisibility()==View.VISIBLE;

            if (!visibility)
            {
                holder.itemView.setActivated(true);
                holder.details.setVisibility(View.VISIBLE);
                if (prev_expanded!=-1 && prev_expanded!=position)
                {
                    recycler.findViewHolderForLayoutPosition(prev_expanded).itemView.setActivated(false);
                    recycler.findViewHolderForLayoutPosition(prev_expanded).itemView.findViewById(R.id.cpl_details).setVisibility(View.GONE);
                }
                prev_expanded = position;
            }
            else
            {
                holder.itemView.setActivated(false);
                holder.details.setVisibility(View.GONE);
            }
            TransitionManager.beginDelayedTransition(recycler);              
        }
});

prev_position is an global integer initialized to -1. details is the complete view which is shown when expanded and cloaked when collapsed.

As said, the root element of ViewHolder is a CardView with foreground and stateListAnimator attributes defined exactly as said by Heisenberg on this topic.

UPDATE: The above demonstration will collapse previosuly expanded item if one of them in expanded. To modify this behaviour and keep the an expanded item as it is even when another item is expanded, you'll need the following code.

if (row.details.getVisibility()!=View.VISIBLE)
    {
        row.details.setVisibility(View.VISIBLE);
        row.root.setActivated(true);
        row.details.animate().alpha(1).setStartDelay(500);
    }
    else
    {
        row.root.setActivated(false);
        row.details.setVisibility(View.GONE);
        row.details.setAlpha(0);
    }
    TransitionManager.beginDelayedTransition(recycler);

UPDATE: When expanding the last items on the list, it may not be brought into full visibility because the expanded portion goes below the screen. To get the full item within screen use the following code.

LinearLayoutManager manager = (LinearLayoutManager) recycler.getLayoutManager();
    int distance;
    View first = recycler.getChildAt(0);
    int height = first.getHeight();
    int current = recycler.getChildAdapterPosition(first);
    int p = Math.abs(position - current);
    if (p > 5) distance = (p - (p - 5)) * height;
    else       distance = p * height;
    manager.scrollToPositionWithOffset(position, distance);

IMPORTANT: For the above demonstrations to work, one must keep in their code an instance of the RecyclerView & it's LayoutManager (the later for flexibility)

  • Thanks this solution works for me. I try to avoid using `notifyItemChanged` because I have an `ImageView` which load image from an url. `notifyItemChanged` reload my image makes it looks weird – Tam Huynh Sep 19 '18 at 13:52
  • The line that nails it is `TransitionManager.beginDelayedTransition(recycler);`, great find. But I found out that by using this method, if I spam-click several rows sequentially, it eventually triggers an internal crash `The specified child already has a parent. You must call removeView() on the child's parent first`. I imagine this is due to the view recycling on the recycler view bounds, but I can't tell for sure. Did anyone have the same issue? – Roger Oba Nov 08 '18 at 22:39
  • Is this still the best way to do it? I am trying to follow what you wrote. I have some recycle views that I want to expand to show a graph when clicked on. I am having a hard time understanding exactly what to do – sourlemonaid Dec 28 '18 at 22:15
  • Does anyone have the complete example of this implementation? I mean a working code example. Please help. I am unable to understand how to do it? – Munazza Jul 04 '20 at 06:53
8

After using the recommended way of implementing expandable/collapsible items residing in a RecyclerView on RecyclerView expand/collapse items answered by HeisenBerg, I've seen some noticeable artifacts whenever the RecyclerView is refreshed by invoking TransitionManager.beginDelayedTransition(ViewGroup) and subsequently notifyDatasetChanged().

His original answer:

final boolean isExpanded = position==mExpandedPosition;
holder.details.setVisibility(isExpanded?View.VISIBLE:View.GONE);
holder.itemView.setActivated(isExpanded);
holder.itemView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        mExpandedPosition = isExpanded ? -1 : position;
        TransitionManager.beginDelayedTransition(recyclerView);
        notifyDataSetChanged();
    }
});

Modified:

final boolean isExpanded = position == mExpandedPosition;
holder.details.setVisibility(isExpanded ? View.VISIBLE : View.GONE);
holder.view.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if (mExpandedHolder != null) {
            mExpandedHolder.details.setVisibility(View.GONE);
            notifyItemChanged(mExpandedPosition);
        }
        mExpandedPosition = isExpanded ? -1 : holder.getAdapterPosition();
        mExpandedHolder = isExpanded ? null : holder;
        notifyItemChanged(holder.getAdapterPosition());
    }
}
  • details is view that you want to show/hide during item expand/collapse
  • mExpandedPosition is an int that keeps track of expanded item
  • mExpandedHolder is a ViewHolder used during item collapse

Notice that the method TransitionManager.beginDelayedTransition(ViewGroup) and notifyDataSetChanged() are replaced by notifyItemChanged(int) to target specific item and some little tweaks.

After the modification, the previous unwanted effects should be gone. However, this may not be the perfect solution. It only did what I wanted, eliminating the eyesores.

::EDIT::

For clarification, both mExpandedPosition and mExpandedHolder are globals.

Wei
  • 1,028
  • 8
  • 27
  • Please explain how and where mExpandedHolder is declared and used? – Werner Feb 19 '18 at 07:24
  • 1
    @Werner I've edited the answer to show where it is declared. The usage is within the snippet itself as well as the purpose. It is used to animate collapsing, expanded item. – Wei Feb 19 '18 at 09:12
  • I love this approach the most as far as the animations is concerned, but I'm having this small "fade" when clicking the item. Do you have that as well or differently put, is this due to using notifyItemChanged()? https://drive.google.com/file/d/1GaU1fcjHEfSErYF50otQGLGsIWkSyQSe/view?usp=sharing – kazume Mar 15 '18 at 12:21
  • Yes. Like you have guessed, it is caused by `notifyItemChanged()`. You may manually animate and change the item height and add views into it. – Wei Mar 15 '18 at 12:29
  • @ChanTeckWei: your solution is working fine but row image flicker whenever I collapse or expand the recyclerview item row..Is there any solution for this ? – Pragya Mendiratta Jul 16 '18 at 12:53
4

I am surprised that there's no concise answer yet, although such an expand/collapse animation is very easy to achieve with just 2 lines of code:

(recycler.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false // called once

together with

notifyItemChanged(position) // in adapter, whenever a child view in item's recycler gets hidden/shown

So for me, the explanations in the link below were really useful: https://medium.com/@nikola.jakshic/how-to-expand-collapse-items-in-recyclerview-49a648a403a6

Bianca Daniciuc
  • 920
  • 1
  • 13
  • 22
3

Do the following after you set the onClick listener to the ViewHolder class:

@Override
    public void onClick(View v) {
        final int originalHeight = yourLinearLayout.getHeight();
        animationDown(YourLinearLayout, originalHeight);//here put the name of you layout that have the options to expand.
    }

    //Animation for devices with kitkat and below
    public void animationDown(LinearLayout billChoices, int originalHeight){

        // Declare a ValueAnimator object
        ValueAnimator valueAnimator;
        if (!billChoices.isShown()) {
            billChoices.setVisibility(View.VISIBLE);
            billChoices.setEnabled(true);
            valueAnimator = ValueAnimator.ofInt(0, originalHeight+originalHeight); // These values in this method can be changed to expand however much you like
        } else {
            valueAnimator = ValueAnimator.ofInt(originalHeight+originalHeight, 0);

            Animation a = new AlphaAnimation(1.00f, 0.00f); // Fade out

            a.setDuration(200);
            // Set a listener to the animation and configure onAnimationEnd
            a.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {

                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    billChoices.setVisibility(View.INVISIBLE);
                    billChoices.setEnabled(false);
                }

                @Override
                public void onAnimationRepeat(Animation animation) {

                }
            });
            // Set the animation on the custom view
            billChoices.startAnimation(a);
        }
        valueAnimator.setDuration(200);
        valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            public void onAnimationUpdate(ValueAnimator animation) {
                Integer value = (Integer) animation.getAnimatedValue();
                billChoices.getLayoutParams().height = value.intValue();
                billChoices.requestLayout();
            }
        });


        valueAnimator.start();
    }
}

I think that should help, that's how I implemented and does the same google does in the recent call view.

Rensodarwin
  • 286
  • 4
  • 12
1
//Global Variable

private int selectedPosition = -1;

 @Override
    public void onBindViewHolder(final CustomViewHolder customViewHolder, final int i) {

        final int position = i;
        final GetProductCatalouge.details feedItem = this.postBeanses.get(i);
        customViewHolder.lly_main.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                selectedPosition = i;
                notifyDataSetChanged();
            }
        });
        if (selectedPosition == i) {

          if (customViewHolder.lly_hsn_code.getVisibility() == View.VISIBLE) {

            customViewHolder.lly_hsn_code.setVisibility(View.GONE);
            customViewHolder.lly_sole.setVisibility(View.GONE);
            customViewHolder.lly_sole_material.setVisibility(View.GONE);

        } else {

            customViewHolder.lly_hsn_code.setVisibility(View.VISIBLE);
            customViewHolder.lly_sole.setVisibility(View.VISIBLE);
            customViewHolder.lly_sole_material.setVisibility(View.VISIBLE);
        }


        } else {
            customViewHolder.lly_hsn_code.setVisibility(View.GONE);
            customViewHolder.lly_sole.setVisibility(View.GONE);
            customViewHolder.lly_sole_material.setVisibility(View.GONE);
        }
}

enter image description here

Keshav Gera
  • 10,807
  • 1
  • 75
  • 53
0

Use two view types in the your RVAdapter. One for expanded layout and other for collapsed. And the magic happens with setting android:animateLayoutChanges="true" for RecyclerView Checkout the effect achieved using this at 0:42 in this video

penduDev
  • 4,743
  • 35
  • 37
  • mind explaining this better? I can't seem to get it working – sourlemonaid Dec 29 '18 at 19:41
  • @sourlemonaid basically you create multiple view types for the list as explained in https://stackoverflow.com/a/26245463/3731795 ... You keep a track of index for which the item is expanded, and the rest of the items are collapsed at that time. Now if(current_item_index == expanded_item_index) { return VIEW_TYPE_EXPANDED } else { reutrn VIEW_TYPE_COLLAPSED } – penduDev Dec 30 '18 at 15:35
-1

Create your own Custom View

  • If you wanted to you could create your own reusable custom View. Here is a quick and dirty example blog post I made on the subject. References are noted on the blog post
Tristan Elliott
  • 594
  • 10
  • 8
-3

This is easy, simple and work perfect for me, hope it help :

First : add this to viewgroup of item recyclerview

android:animateLayoutChanges="true"

Second : add this to your onclick method that expand or collapse current item :

val changeBounds = ChangeBounds()
// binder.contraintLayout is viewGroup that have animateLayoutChange = true (iam using databinding)
changeBounds.duration = binder.contrainLayout.layoutTransition.getDuration(LayoutTransition.CHANGING)
changeBounds.interpolator = binder.contrainLayout.layoutTransition.getInterpolator(LayoutTransition.CHANGING)
binder.expandView.Visibility = binder.expandView.Visibility != View.Visible
TransitionManager.beginDelayedTransition(binder.root.parent as ViewGroup, changeBounds)
Nguyen Hoà
  • 327
  • 3
  • 9