Reputation: 432
In xml you can use GridLayoutManager.SpanSizeLookup in GridLayoutManager to set the span size on single items (How many columns the item will use in the row, like for example, in a grid of 3 columns I can set the first item to be span size 3 so it will use all the width of the grid), but in Compose I can't find a way to do it, the vertical grid only have a way set the global span count and add items, but not set the span size of an individual item, is there a way to do it?
Upvotes: 4
Views: 4945
Reputation: 1291
I don't know about back then but I still faced this issue in 2025 and spent a lot of time trying to provide dynamic span lookup like we did in GridLayout in Views. I was able to call span for all items but not for individual items. Long story short. I came to see this answer
The documentation on this is a bit scarce, that is true. What you might have missed is: while you can only provide one span lambda, it will be called for each item, with that item as argument. This means you are free to return different span sizes depending on the argument provided. So the implementation path is almost identical to the classic SpanSizeLookup, with the advantage that you don't have to look up items by their index (but can still opt to do it):
// Just for visualization purposes
@Composable
fun GridItem(label: String) {
Box(
Modifier
.fillMaxWidth()
.height(56.dp)
.border(1.dp, Color.Gray, RoundedCornerShape(16.dp)),
contentAlignment = Alignment.Center
) {
Text(text = label)
}
}
@Preview
@Composable
fun GridSpansSample() {
LazyVerticalGrid(columns = GridCells.Fixed(3), modifier =
Modifier.fillMaxSize()) {
// based on index
items(3, span = { index ->
val spanCount = if (index == 0) 3 else 1
GridItemSpan(spanCount)
}) { index ->
GridItem("Item #$index")
}
// based on list content
items(listOf("Foo", "Bar", "Baz"), span = { item ->
val spanCount = if (item == "Foo") 3 else 1
GridItemSpan(spanCount)
}) { item ->
GridItem(item)
}
// based on either content or index
itemsIndexed(listOf("Foo", "Bar", "Baz"), span = { index, item ->
val spanCount = if (item == "Foo" || index == 1) 3 else 1
GridItemSpan(spanCount)
}) { index, item ->
GridItem(item)
}
// Bonus: The span lambda receives additional information as "this"
context, which allows for further customization
items(10 , span = {
// occupy the available remaining width in the current row, but at most
2 cells wide
GridItemSpan(this.maxCurrentLineSpan.coerceAtMost(2))
}) { index ->
GridItem("Item #$index")
}
}
}
But still Android studio was not able to resolve the parameter span lambda because in my specific case I was missing the following dependency.
implementation("androidx.compose.foundation:foundation:1.5.1")
Hope this helps those having a similar issue.
Upvotes: 0
Reputation: 11
The first answer seems to work but i think it wasn't concise. Here's how i solved this problem:
LazyVerticalGrid(columns = GridCells.Fixed(count = columns)) {
myItemsList.forEach { myItem ->
item(
key = myItem.hashCode(),
span = {
GridItemSpan(myItem.spanSize)
}
) {
Text(text=myItem.title)
}
}
}
I was building a dynamic app where i was loading list from the firebase. So in these type of scenarios iterating over the list is the best
Upvotes: 0
Reputation: 29260
There is no support for this out of the box at present. The way I have solved this for now is to use a LazyColumn
then the items are Row
s and in each Row
you can decide how wide an item is, using weight
.
I have implemented and in my case I have headers (full width), and cells of items of equal width (based on how wide the screen is, there could be 1, 2 or 3 cells per row). It's a workaround, but until there is native support from VerticalGrid
this is an option.
My solution is here - look for the LazyListScope
extensions.
Edit: this is no longer necessary as LazyVerticalGrid
supports spans now, here's an example
LazyVerticalGrid(
columns = GridCells.Adaptive(
minSize = WeatherCardWidth,
),
modifier = modifier,
contentPadding = PaddingValues(all = MarginDouble),
horizontalArrangement = Arrangement.spacedBy(MarginDouble),
verticalArrangement = Arrangement.spacedBy(MarginDouble),
) {
state.forecastItems.forEach { dayForecast ->
item(
key = dayForecast.header.id,
span = { GridItemSpan(maxLineSpan) }
) {
ForecastHeader(
state = dayForecast.header,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = MarginDouble),
)
}
items(
items = dayForecast.forecast,
key = { hourForecast -> hourForecast.id }
) { hourForecast ->
ForecastWeatherCard(
state = hourForecast,
modifier = Modifier.fillMaxWidth(),
)
}
}
}
Upvotes: 2
Reputation: 3131
Jetpack Compose version 1.1.0-beta03 introduced horizontal spans to LazyVerticalGrid
.
Example code:
val list by remember { mutableStateOf(listOf("A", "E", "I", "O", "U")) }
LazyVerticalGrid(
cells = GridCells.Fixed(2)
) {
// Spanned Item:
item(
span = {
// Replace "maxCurrentLineSpan" with the number of spans this item should take.
// Use "maxCurrentLineSpan" if you want to take full width.
GridItemSpan(maxCurrentLineSpan)
}
) {
Text("Vowels")
}
// Other items:
items(list) { item ->
Text(item)
}
}
Upvotes: 14
Reputation: 432
Adapting the code from the answer, I created a more "general" purpose method, It can be used with Adaptive and Fixed, I'm very new with Compose so I accept suggestions
@Composable
fun HeaderGrid(cells: GridCells, content: HeaderGridScope.() -> Unit) {
var columns = 1
var minColumnWidth = 0.dp
when (cells) {
is GridCells.Fixed -> {
columns = cells.count
minColumnWidth = cells.minSize
}
is GridCells.Adaptive -> {
val width = LocalContext.current.resources.displayMetrics.widthPixels
val columnWidthPx = with(LocalDensity.current) { cells.minSize.toPx() }
minColumnWidth = cells.minSize
columns = ((width / columnWidthPx).toInt()).coerceAtLeast(1)
}
}
LazyColumn(modifier = Modifier.fillMaxWidth()){
content(HeaderGridScope(columns, minColumnWidth, this))
}
}
fun <T>HeaderGridScope.gridItems(items: List<T>, content: @Composable (T) -> Unit) {
items.chunked(numColumn).forEach {
listScope.item {
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
it.forEach {
content(it)
}
if (it.size < numColumn) {
repeat(numColumn - it.size) {
Spacer(modifier = Modifier.width(columnWidth))
}
}
}
}
}
}
fun HeaderGridScope.header(content: @Composable BoxScope.() -> Unit) {
listScope.item {
Box(
modifier = Modifier
.fillMaxWidth(),
content = content
)
}
}
data class HeaderGridScope(val numColumn: Int, val columnWidth: Dp, val listScope: LazyListScope)
sealed class GridCells {
class Fixed(val count: Int, val minSize: Dp) : GridCells()
class Adaptive(val minSize: Dp) : GridCells()
}
Upvotes: 0