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

Stay in the loop with our latest content!

Select the topics you’re interested to receive our new relevant content in your inbox. Don’t worry, we won’t spam you.

6 Tips for Facilitating Effective Roadmapping and Story Mapping Sessions

6 Tips for Facilitating Effective Roadmapping and Story Mapping Sessions

June 28, 2019

Roadmapping and story mapping are effective ways to align teams around a set direction. It can even help facilitate future conversations of prioritization and backlog grooming.

Read more
Product Strategy: Turning your App Ideas Into Reality
Process

Product Strategy: Turning your App Ideas Into Reality

April 16, 2020

Anyone who has been there knows, it can be incredibly frustrating having an app idea and not being able to bring it to life. And with organizational, technical, or financial roadblocks in the way, it can be equally frustrating to simply get a project off the ground.

Read more
Press Release: Michigan Software Labs named Best and Brightest Companies to Work For - Top 101 In the Nation
Press Release

Press Release: Michigan Software Labs named Best and Brightest Companies to Work For - Top 101 In the Nation

February 3, 2020

Michigan Software Labs was named Best and Brightest Companies to Work For - Top 101 In the Nation

Read more
View more articles