Feature spotlight: Building a horizontal carousel on Android


LinkedIn recently launched a new feature on Profile called Featured Items. It allows members to pin a post, article, or uploaded media to their profiles to showcase their work. Because the range of what members can choose to highlight is so broad, it was important that the feature was built to accommodate them all. We were excited to release Featured Items into the world, but before we did that, we had to make sure that Featured Items would have a consistent viewing experience. As we built out this experience, we came across a problem of how to deliver a horizontal carousel on Android that was flexible to accommodate different types of content our members would want to feature.

Dynamic height cards in a horizontal carousel is a problem that engineers regularly face, but there aren’t many resources or solutions out there. In this blog post, we’ll talk about how we leveraged Android’s RecyclerView and ConstraintLayout to present cards with different content types and height in a horizontal carousel. We’ll also cover how we were able to reuse the card layout for a vertical feed, with some techniques like tweaking view properties at runtime.

Part 1: Laying out the cards

An important part of engineering any type of carousel is to have an indicator that users can scroll further to see more. To achieve this, we needed to make sure the carousel in its default view showed a peek of the second card that would follow. Here are our requirements:

  1. The second card should have a fixed peek of 96dp, for better alignment, with a space of 12dp between items.
  2. Card layout should be as below, any optional fields can be omitted
  3. The height of the carousel should not change as you scroll. 
  4. When a user scrolls, the following card needs to snap to the left when the member lifts their finger and stops scrolling.

For card width, we have the following formula:

card width = (screen width – peeking width) – 2 x padding

For card height, if there is only one card, then we can simply do wrap_content. For multiple cards, however, you don’t want a situation where the second and third cards that follow look shorter than the first card when you scroll—it will look broken. To prevent this, we use the max height that a card could possibly have. Here is the formula:

card height = commentary max height + card width / image aspect ratio + text max height + subtext max height + social footer height

Practically speaking, someone could easily make changes to the view, e.g., add padding or insert a new view into the layout. Instead of doing the math at compile time, we populate the view at runtime by inflating the layout xml, fill it with fake data to maximize all views, and measure it with the card width we know.

In the multi-card scenario, in which all cards use max height, we stretch the image to fill up the potential space when a view has missing fields or if an element is absent. To make sure we didn’t lose the image aspect ratio, we used centerCrop as the ImageView’s ScaleType

  • displaying-a-multicard-scenario-on-the-carousel

With a combination of Space and Barrier tools in ConstraintLayout, we can preserve the height for the ImageView and allow it to stretch if needed to fill the remaining space left in the parent. Here is the xml layout:

Snapping behavior was easy to do with an implementation of RecyclerView’s PagerSnapHelper:

  • displaying-the-horizontal-stretching-of-content-behavior

Horizontal stretching of content behavior

Part 2: Reusing the card layout in a vertical feed view

The carousel is a preview with up to 5 cards and a vertical feed view to display all items. The next step in our journey was to leverage this same card layout for the vertical feed. Instead of a fixed height, the card now uses wrap_content to calculate its height, and the image keeps its aspect ratio. This is achieved by hiding the Space in the xml, changing the ImageView’s layout parameters and scaleType.

  • displaying-the-vertical-card-behavior

Vertical card behavior reusing same item xml layouts

RecyclerView and ConstraintLayout, provided by Android Framework, have a rich set of configurations to help laying out the card in both horizontal and vertical views. We now found ourselves with one card layout that could stretch itself to fill the vertical space given a fixed card height or wrap its own content height to be added to a vertical scrolling list.

Here is a working sample project illustrating the above carousel behavior.

Acknowledgements

Special thanks to Paul Fletcher and Danielle Chandler for all the designs and the calculation formulas; the LinkedIn Eng blog reviewers, Jaren Anderson and Heyun Jeong, for the editorial feedback and support; my managers Rick Ramirez, Bef Ayenew; and coworkers, Mahesh Vishwanath, Eric Liu, Zach Moore, Harshvardhan Aggarwal, Tyler Durden, Tao Ning, Simon Henderson, and the rest of the team who worked on this project.



Source link