Blog Infos
Author
Published
Topics
Author
Published
Topics

User experience plays a very crucial role in letting users interact with your application more which results in greater user retention. Today we are going to discuss about very often used UI design called overlapping list.

These types of lists are widely used in most applications and have a variety of use cases. And we will build this using both the available design systems, i.e.; using the Recycler View way and the Jetpack Compose way.

First, let’s explore the Recycler View way

As we can see we need to do two steps to achieve the overlapping behaviour,
1. Create a horizontal list
2. Shift each item (starting from the second position) up to a certain percentage to the left.

Creating a list is pretty straightforward, so we will focus on the second point. So we will shift each item by some percentage using RecyclerView Item Decoration. For that, we need to create a custom item decoration class.

class OverlappingDecoration: RecyclerView.ItemDecoration() {

    private val overlapPercentage = 15

    override fun getItemOffsets(
        outRect: Rect,
        view: View,
        parent: RecyclerView,
        state: RecyclerView.State
    ) {
        val width = view.layoutParams.width

        val widthToShift = (overlapPercentage * width * -1) / 100

        val position = parent.getChildAdapterPosition(view)

        val isReversed = (parent.layoutManager as? LinearLayoutManager)?.reverseLayout ?: false

        if (position == 0) {
           return
        } else {
            if (isReversed) {
                outRect.set(0, 0, widthToShift, 0)
            } else {
                outRect.set(widthToShift, 0, 0, 0)
            }
        }
    }

For each view we are first calculating its width and then the widthToShift which is the pixels of offsets that needed to be shifted to overlap on the previous item.
outRect.set(widthToShift, 0, 0, 0) is used to for shifting view to left.
Similarly, in case stacking items is reversed, we are shifting the view to the right side with outRect.set(0, 0, widthToShift, 0). Also, we don’t need to do this for the first item in the list as it doesn’t need to be overlapped.
Now we can add this decoration to our recylerview and it will work like a charm.

Job Offers

Job Offers


    Senior Android Developer

    SumUp
    Berlin
    • Full Time
    apply now

    Senior Android Engineer

    Carly Solutions GmbH
    Munich
    • Full Time
    apply now

OUR VIDEO RECOMMENDATION

No results found.

Jobs

Now let’s see the Compose way

In compose, we have Lazy Row/Column as an alternate for RecyclerView. We play with Arrangement.spacedBy((-1 *x).dp) but this will apply spacing to every item of the list but we want to skip for the first.
Also, we might play with some if-else to achieve the same but here I preferred to go with creating a Custom Composable.

@Composable
fun OverlappingRow(
    modifier: Modifier = Modifier,
    overlappingPercentage: Float,
    content: @Composable () -> Unit
) {

    val factor = (1 - overlappingPercentage)

    Layout(
        modifier = modifier,
        content = content,
        measurePolicy = { measurables, constraints ->
            val placeables = measurables.map { it.measure(constraints) }
            val widthsExceptFirst = placeables.subList(1, placeables.size).sumOf { it.width }
            val firstWidth = placeables.getOrNull(0)?.width ?: 0
            val width = (widthsExceptFirst * factor + firstWidth).toInt()
            val height = placeables.maxOf { it.height }
            layout(width, height) {
                var x = 0
                for (placeable in placeables) {
                    placeable.placeRelative(x, 0, 0f)
                    x += (placeable.width * factor).toInt()
                }
            }
        }
    )
}

Here we have defined a factor which determines the width of the item after the next item will overlap on itself, that’s why we have to subtract it from 1. Now for a custom layout in compose, we need to define a measurePolicy which determines the size and coordinates of the composables.

The measure policy has two properties measurables and constraints. As the name suggests measurables are a list of compositions that can be measured and constraints define the range in pixels in which the measured layout would choose a size to render the composition.

Using measurables and constraints, we get the placeables which are the children’s layouts that can be positioned by the parent layout.

A measurePolicy requires a width and height to determine the size and position of each placeable.

In our case, getting the height is straightforward, i.e.; it will be the height of the tallest placeable.
To find the width, we need to first get the sum of the width of all items except the first item. Then we need to multiply it with the factor that we calculated (since that will affect the overall width of the layout) and then we will add the width of the first item to it.

Now while placing the placeables on the layout we need to make sure that the x-position of each placeable is adjacent and for overlapping we need to adjust the width with factor as well.

We can now use our OverlappingRow composable inside a LazyRow to provide the scroll behaviour to our layout.

LazyRow {
    item {
        Spacer(modifier = modifier.width(16.dp))
    }

    item {
        OverlappingRow(overlappingPercentage = 0.20f) {
            for (i in images) {
                Image(
                    painter = painterResource(id = i),
                    contentDescription = "image_$i",
                    contentScale = ContentScale.Crop,
                    modifier = modifier
                        .size(100.dp)
                        .clip(CircleShape)
                        .border(4.dp, Color(0xFFFFA0A0), CircleShape)
                )
            }
        }
    }

    item {
        Spacer(modifier = modifier.width(16.dp))
    }
}

Let’s see a demo for both ways

Using these approaches we can define much more use cases and enhance the user experience in a better way.

That’s it for this blog. Let’s connect on LinkedIn and Twitter
You can find the source code in the following repository

https://github.com/raystatic/OverlappingLists?source=post_page—–9d0655c5a20e——————————–

This article is previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
This is the second article in an article series that will discuss the dependency…
READ MORE
blog
Let’s suppose that for some reason we are interested in doing some tests with…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu