This is a share your knowledge, Q&A-style question to create a follow up answer to create a Lazy list(Column, Row, Grid) that can compose more items than default behavior allows which is the next item in scroll direction when you start scrolling.
In cases, where some items take more time to load, like a video or downloading an image, so you might want to have more items that are loaded or entering composition offscreen.
LazyRow(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp),
contentPadding = PaddingValues(16.dp),
) {
items(30) {
var loading by remember {
mutableStateOf(true)
}
LaunchedEffect(Unit) {
println("Composing First LazyRow item: $it")
delay(1000)
loading = false
}
MyRow(itemWidth, loading, it)
}
}
@Composable
private fun MyRow(itemWidth: Dp, loading: Boolean, it: Int) {
Box(
modifier = Modifier
.size(itemWidth, 100.dp)
.background(if (loading) Color.Red else Color.Green, RoundedCornerShape(16.dp))
.padding(16.dp)
) {
Text("Row $it", fontSize = 26.sp, color = Color.White)
}
}
It takes 1 second for this item to full be ready but if you scroll so how to make this or more items composed?
Actually i'm also looking how to store states of Composables like moveableContentOf
does in a Row/Column but it doesn't work with LazyColumn for some reason? It would also another approach to storing Composable state that left composition and show with last state before showing on screen.
One of the ways to compose more offscreen items than one that is composed when scroll starts is increasing LazyColumn/Row viewport size bigger than parent Composable or device dimensions.
Since, creating dimensions bigger than parent Composable is not permitted by default one option is to use Modifier.layout
Text("Compose 4 items offscreen in right direction")
LazyRow(
modifier = Modifier.fillMaxWidth().layout { measurable, constraints ->
val width = constraints.maxWidth + 4 * itemWidth.roundToPx()
val wrappedConstraints = constraints.copy(minWidth = width, maxWidth = width)
val placeable = measurable.measure(wrappedConstraints)
layout(
placeable.width, placeable.height
) {
val xPos = (placeable.width - constraints.maxWidth) / 2
placeable.placeRelative(xPos, 0)
}
},
horizontalArrangement = Arrangement.spacedBy(8.dp),
contentPadding = PaddingValues(16.dp)
) {
items(30) {
var loading by remember {
mutableStateOf(true)
}
LaunchedEffect(Unit) {
println("Composing Second LazyRow item: $it")
delay(1000)
loading = false
}
MyRow(itemWidth, loading, it)
}
}
Results 4 more items are composed in right scroll while one while scrolled left.
If you wish to make fling gesture, gesture that scrolls after user lifts off finger, to scroll after is slower so items are more like to be loaded. NestedScrollConnection
to limit fling speed does the trick. Limiting initial velocity between
- threshold < available < threshold
So fling gesture is limited in both directions.
@Composable
fun rememberFlingNestedScrollConnection() = remember {
object : NestedScrollConnection {
override suspend fun onPreFling(available: Velocity): Velocity {
val threshold = 3000f
val availableX = available.x
val consumed = if (availableX > threshold) {
availableX - threshold
} else if (availableX < -threshold) {
availableX + threshold
} else {
0f
}
return Velocity(consumed, 0f)
}
}
}
LazyRow(
modifier = Modifier
.nestedScroll(rememberFlingNestedScrollConnection())
)
In both cases LazyColumn is set to start position and viewport overflows right side of the screen. But what about making it overflow both sides so it can compose more items in both scroll direction.
For that positioning 0 to does the trick but you won't be able to see first 4 and last 4 items and to make them visible need to add contentPadding as
Text("Compose 4 items offscreen in both scroll directions")
LazyRow(
modifier = Modifier
.nestedScroll(rememberFlingNestedScrollConnection())
.fillMaxWidth()
.layout { measurable, constraints ->
val width = constraints.maxWidth + 8 * itemWidth.roundToPx()
val wrappedConstraints = constraints.copy(minWidth = width, maxWidth = width)
val placeable = measurable.measure(wrappedConstraints)
layout(
placeable.width, placeable.height
) {
placeable.placeRelative(0, 0)
}
},
horizontalArrangement = Arrangement.spacedBy(8.dp),
contentPadding = PaddingValues(
vertical = 16.dp,
horizontal = 16.dp + itemWidth * 4
)
) {
items(30) {
var loading by remember {
mutableStateOf(true)
}
LaunchedEffect(Unit) {
println("Composing Forth LazyRow item: $it")
delay(1000)
loading = false
}
MyRow(itemWidth, loading, it)
}
}