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.
<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.
Looking for more like this?
Sign up for our monthly newsletter to receive helpful articles, case studies, and stories from our team.
MichiganLabs’ approach to product design: A strategic, problem-solving process
February 12, 2024Product design, or UX design, is a strategic problem-solving process that leads to a valuable digital product. Learn what to expect when working with product designers for your custom software.
Read moreChicago Roboto 2022 Retrospective
August 11, 2022Scott Schmitz shares some notes of interest from talks at Chicago Roboto 2022, an Android community conference, that took place August 1-2.
Read moreSimplifying the 4 forces of AI
April 9, 2024Artificial Intelligence (AI) is becoming more prevalent, but less understood. Through examples of organizations leading the charge in each of these areas, I hope to empower you to make better decisions for your enterprise and career.
Read more