Reputation: 1276
I'm trying to implement a list with a button on its bottom, like the following:
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:
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
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
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
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
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