Android Development

Expand/Collapse Recycler View

January 18, 2017

Expand Collapse RecyclerView Cell (Android 4.0+)

Adding animations to expanding/collapsing cells is a simple way to add some high quality polish to your app. Today, the solution we see more frequently is launching a popup window overtop of the current selected content. However, what if the intended outcome is for users to compare items? In this case, launching a popup window actually impedes the users success. Fortunately, this functionality comes standard in Android 4.4+ in the package android.transition.TransitionManager. However, what about apps supporting the rest of the Android ecosystem? For those supporting Android Level 14 - 18, I recommend the Transitions Everywhere library.

API Level 19

As stated above, Android 4.4 provides the android.Transition package which allows for transitions to take place over a short period of time. This can be accomplished by calling the TransitionManager immediately after modifying the contents of the cell. Note that, by default, the TransitionManager will animate a Scene change using an AutoTransition, which in the case of expanding cells works well. For more specific transitions refer to the Android Developer Docs.

import android.transition.TransitionManager;
...
@Override
public void onBindViewHolder(final ItemViewHolder viewHolder, final int position) {
 viewHolder.setup(data.get(position));

 viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
 @Override
 public void onClick(View v) {
 if (shouldExpand) {
 viewHolder.expandedView.setVisibility(View.VISIBLE);
 viewHolder.imageView_toggle.setImageResource(R.drawable.collapse_symbol);
 } else {
 viewHolder.expandedView.setVisibility(View.GONE);
 viewHolder.imageView_toggle.setImageResource(R.drawable.expand_symbol);
 }

 TransitionManager.beginDelayedTransition(recyclerView)
 }
 });
}

API Level 14+ Double Tap Explosion

The Transitions Everywhere library backports this functionality to Android 4.0! Once the library has been imported, the syntax is exactly the same.

import com.transitionseverywhere.TransitionManager;
...
if (shouldExpand) {
 viewHolder.expandedView.setVisibility(View.VISIBLE);
 viewHolder.imageView_toggle.setImageResource(R.drawable.collapse_symbol);
} else {
 viewHolder.expandedView.setVisibility(View.GONE);
 viewHolder.imageView_toggle.setImageResource(R.drawable.expand_symbol);
}

Although this is starting to come together, you may notice some problems when testing. Visually the expansion duration may feel too long or too short for the content being displayed. Tapping too quickly can cause a crash if the cell is trying to expand before successfully collapsing. Both of these issues are fixable and addressed in the following section.

API Level 14+ Functioning Solution

The above solution is prone to crashing when an item is tapped quickly, due to IllegalStateException. Fortunately, the logs are quite helpful in this scenario and point directly to the problem.

java.lang.IllegalStateException: The specified child already has a parent. You must
call removeView() on the child's parent first.

Essentially, the view has not yet been set to GONE before it is being set back to VISIBLE. In this situation, the desired effect is a ChangeBounds transition. By defining the transition up front, the application does not have to make its best guess at determining the transition for us. Also, this sets us down the path for defining the duration of the transition itself. Play around with the timing until you discover a value that feels right for the app, in the example below 125ms is used.

Expand/Collapse Recycler View
<LinearLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:orientation="vertical"
 >

 <TextView
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:id="@+id/title"
 android:text="Title!"
 />

 <LinearLayout
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:id="@+id/expandView"
 android:orientation="vertical"
 android:gravity="center_horizontal"
 android:visibility="gone"
 >

 <TextView
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:id="@+id/description"
 android:text="Description!!"
 />

 <ImageView
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:src="@drawable/coolImage"
 />

 </LinearLayout>

 <ImageView
 android:layout_width="wrap_content"
 android:layout_height="match_parent"
 android:id="@+id/toggle"
 android:src="@drawable/expand_symbol"
 />
 
</LinearLayout>
import com.transitionseverywhere.ChangeBounds;
import com.transitionseverywhere.Transition;
import com.transitionseverywhere.TransitionManager;

...

@Override
public void onBindViewHolder(final ItemViewHolder viewHolder, final int position) {
 viewHolder.setup(data.get(position));

 // Since views are recycled, it must be reset to the collapsed state.
 // Alternatively, the expand/collapse state could be trakced for the
 // position and then set visibility as needed
 viewHolder.expandedView.setVisibility(View.GONE);

 viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
 @Override
 public void onClick(View v) {
 boolean shouldExpand = viewHolder.expandedView.getVisibility() == View.GONE;

 ChangeBounds transition = new ChangeBounds();
 transition.setDuration(125);

 if (shouldExpand) {
 viewHolder.expandedView.setVisibility(View.VISIBLE);
 viewHolder.imageView_toggle.setImageResource(R.drawable.collapse_symbol);
 } else {
 viewHolder.expandedView.setVisibility(View.GONE);
 viewHolder.imageView_toggle.setImageResource(R.drawable.expand_symbol);
 }

 TransitionManager.beginDelayedTransition(recyclerView, transition);
 viewHolder.itemView.setActivated(shouldExpand);
 }
 });
}

Conclusion

Although expanding and collapsing cells may be falling out of favor with an increasing preference for popups, there are still occasions when they can be quite useful. Specifically when a user will want to compare information between two cells. Think about if you were shopping for a TV and you wanted to select the one with highest refresh rate. With popups, you will have to open and close each option while remembering the values associated with each television. Not only does this feel clunky but it requires the user to remember their previous choices. However, with an expanding cell interface, multiple options may fit on the page at the same time and allow for direct comparison. When expanding cells are a requirement, there is no reason not to style them with smooth animations.

Scott Schmitz
Scott Schmitz
Software Developer

Looking for more like this?

Sign up for our monthly newsletter to receive helpful articles, case studies, and stories from our team.

UX Writing Tips
Design Process

UX Writing Tips

February 3, 2023

Kai shares a few tips he's collected on how to write for user interfaces.

Read more
Application Architecture with SwiftUI
Development iOS

Application Architecture with SwiftUI

June 15, 2022

An overview of mobile application system architecture using SwiftUI

Read more
Make an AI Art Generating Slack Bot in 10 Minutes
Development

Make an AI Art Generating Slack Bot in 10 Minutes

February 3, 2023

David shares how easy it is to create your own AI art generating Slack bot using Typescript and Stable Diffusion.

Read more
View more articles