Is there a way to align a item on the bottom of a LazyColumn?
Asked Answered
D

4

12

I'm trying to implement a list with a button on its bottom, like the following:

enter image description here

Since I don't know how many items I would have on the list, I'm using a LazyColumn. The button on the bottom, should only be placed there if the list doesn't fill the entire screen, if it does, the button should be moved down and be the last item on the list.

Moving the button inside the LazyColumn like this:

LazyColumn(...) {
    items(items) { item -> ...}
    item { Button() } 
}

Gives the following:

enter image description here

I tried to add a Spacer with fillMaxHeight() modifier as an item between the two but it didn't change.

I also tried to add both the LazyColumn and the Button inside a column:

Column {
   LazyColumn(
        modifier = Modifier.weight(1f)
   ) {
        items(items) { item -> ...}
   }
   Button()
}

But this only anchors the button on the bottom as if the Column was a LinearLayout.

Considering this, would it be possible to align one of the LazyColumn's items so it will always be on the bottom? Or, add some kind of space that fill the available area?

Denisse answered 27/8, 2021 at 22:17 Comment(0)
D
15

As stated in this closed issue it's possible to use LazyColumn's verticalArrangement parameter to align the last item on the bottom by implementing Arrangement.Vertical:

LazyColumn(verticalArrangement = remember {
    object : Arrangement.Vertical {
        override fun Density.arrange(
            totalSize: Int,
            sizes: IntArray,
            outPositions: IntArray
        ) {
            var currentOffset = 0
            sizes.forEachIndexed { index, size -> 
                if (index == sizes.lastIndex) {
                    outPositions[index] = totalSize - size
                } else {
                    outPositions[index] = currentOffset
                    currentOffset += size
                }
            }
        }
    }
})

If you want to use it with spacedBy on the verticalArrangement parameter, as I was, you could use the following:

fun spacedByWithFooter(space: Dp) = object : Arrangement.Vertical {

    override val spacing = space

    override fun Density.arrange(
        totalSize: Int,
        sizes: IntArray,
        outPositions: IntArray,
    ) {
        if (sizes.isEmpty()) return
        val spacePx = space.roundToPx()

        var occupied = 0
        var lastSpace = 0

        sizes.forEachIndexed { index, size ->

            if (index == sizes.lastIndex) {
                outPositions[index] = totalSize - size
            } else {
                outPositions[index] = min(occupied, totalSize - size)
            }
            lastSpace = min(spacePx, totalSize - outPositions[index] - size)
            occupied = outPositions[index] + size + lastSpace
        }
        occupied -= lastSpace
    }
}
Denisse answered 15/9, 2021 at 16:29 Comment(1)
Just a note that LazyColumn should be used with a fillMaxHeight modifier.Milestone
G
2

We can wrap the button and LazyColumn with Column like this and ues verticalArrangement SpaceBetween.

we also can replace SpaceBetween with SpaceAround

   Column(
    modifier = Modifier.fillMaxSize(),
    verticalArrangement = Arrangement.SpaceBetween
) {
    LazyColumn(...) {

        items(items) { item -> ... }
    }
    Button()
}

I hope that has helped you.

Granddad answered 9/6, 2022 at 9:41 Comment(1)
Not sure if this entirely applies to what was need because, if the list is long enough, the button should be pushed "down the screen" and should only be shown as the last element of the list. If you want the button fixed at the bottom, then I think this is the better solution.Denisse
J
2

Arthur Bertemes worked perfectly for my case.

I created a custom Lazy Column with that solution, just pass the lazy content to the content parameter:

/**
 * A [LazyColumn] that aligns the last item at the bottom of the screen
 * using a custom verticalArrangement function.
 */
@Composable
fun LazyColumnBottomAlign(
    contentPadding: PaddingValues,
    modifier: Modifier = Modifier,
    verticalPadding: Dp = 8.dp,
    horizontalPadding: Dp = 16.dp,
    content: LazyListScope.() -> Unit,
) {
    LazyColumn(
        verticalArrangement = remember {
            object : Arrangement.Vertical {
                override fun Density.arrange(
                    totalSize: Int,
                    sizes: IntArray,
                    outPositions: IntArray
                ) {
                    var currentOffset = 0

                    sizes.forEachIndexed { index, size ->
                        if (index == sizes.lastIndex) {
                            outPositions[index] = totalSize - size
                        } else {
                            outPositions[index] = currentOffset
                            currentOffset += size
                        }
                    }
                }
            }
        },
        modifier = modifier
            .padding(
                start = horizontalPadding,
                end = horizontalPadding,
                top = contentPadding.calculateTopPadding() + verticalPadding,
            )
            .fillMaxHeight(),
        content = content,
    )
}
Joyejoyful answered 27/10, 2023 at 13:8 Comment(0)
A
0

This was asked on the discord server and I came up with this:

LazyButtonTheme {
    // Change to a 10 to test the other case.
    val items = (0..100).toList()
    val listState = rememberLazyListState()
    Surface(
        modifier = Modifier.fillMaxSize(),
        color = MaterialTheme.colorScheme.background
    ) {
        if (listState.canScrollForward || listState.canScrollBackward) {
            // First layout (list is scrollable)
            LazyColumn(
                modifier = Modifier.fillMaxSize(),
                state = listState,
                verticalArrangement = Arrangement.SpaceBetween
            ) {
                items(items.size + 1) {
                    if (it != items.lastIndex + 1) {
                        Text(
                            text = "$it"
                        )
                    } else {
                        Button(
                            onClick = { },
                            modifier = Modifier.wrapContentSize()
                        ) {
                            Text(text = "Button")
                        }
                    }
                }
            }
        } else {
            // Second layout (list is not scrollable)
            Column(verticalArrangement = Arrangement.SpaceBetween) {
                LazyColumn(state = listState) {
                    items(items.size) {
                        Text(text = "$it")
                    }
                }

                Button(
                    onClick = { },
                    modifier = Modifier.wrapContentSize()
                ) {
                    Text(text = "Button")
                }
            }
        }
    }
}
Azarcon answered 5/5, 2023 at 5:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.