Arthur Bertemes
Arthur Bertemes

Reputation: 1276

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

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?

Upvotes: 12

Views: 6602

Answers (4)

jubarim
jubarim

Reputation: 21

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,
    )
}

Upvotes: 2

imashnake_
imashnake_

Reputation: 179

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")
                }
            }
        }
    }
}

Upvotes: 0

Hamdy Abd El Fattah
Hamdy Abd El Fattah

Reputation: 1827

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.

Upvotes: 2

Arthur Bertemes
Arthur Bertemes

Reputation: 1276

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
    }
}

Upvotes: 15

Related Questions