Reputation: 5263
I have a LazyRow with several cells, and it tracks the item currently centered on the screen. The issue is that I'm using a state to track this center item, and each time the state updates, it triggers a recomposition. I noticed in the layout inspector that the cells in the LazyRow are continuously updating as I scroll left or right. This doesn't happen if I comment out the state tracking. My question is: how can I avoid these unnecessary recompositions?
Here is the code:
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyListItemInfo
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun JustRotator(
number: Int = 25,
cellSize: Dp = 75.dp
) {
val listState: LazyListState = rememberLazyListState()
LazyRow(
modifier = Modifier.fillMaxWidth(),
state = listState,
horizontalArrangement = Arrangement.Center,
flingBehavior = rememberSnapFlingBehavior(lazyListState = listState)
) {
items(count = number) { idx ->
val isCenterItem: Boolean = listState.isItemInCenter(idx)
val animatedSize: Dp by animateDpAsState(
targetValue = if (isCenterItem) (cellSize * 2) else cellSize,
animationSpec = tween(durationMillis = 300),
label = ""
)
Image(
modifier = Modifier
.size(animatedSize)
.clip(CircleShape),
painter = painterResource(id = R.drawable.ic_profile_chooser_lock),
contentDescription = null
)
}
}
}
@Composable
fun LazyListState.isItemInCenter(
idx: Int,
withOffset: Int = 0
): Boolean {
val isItemInCenter: Boolean by remember(this, idx) {
derivedStateOf {
val visibleItemsInfo: List<LazyListItemInfo> = layoutInfo.visibleItemsInfo
val screenCenter: Int = (layoutInfo.viewportStartOffset + layoutInfo.viewportEndOffset) / 2
val centeredItemIdx: Int = visibleItemsInfo.minByOrNull { itemInfo ->
val itemCenter: Int = (itemInfo.offset + itemInfo.size / 2)
kotlin.math.abs(screenCenter - itemCenter)
}?.index ?: -1
(centeredItemIdx + withOffset) == idx
}
}
return isItemInCenter
}
Are there any suggestions?
Upvotes: 1
Views: 71
Reputation: 26
You can reduce the number of recompositions by observing size in layout phase instead of composition. To do that you can use layout
modifier on images instead of size()
, and observe animated size value there.
.layout { measurable, constraints ->
val size = animatedSize.toPx().toInt()
val placeable = measurable.measure(Constraints.fixed(size, size))
layout(placeable.width, placeable.height) {
placeable.placeRelative(0, 0)
}
}
The code above is slightly modified code from this answer https://stackoverflow.com/a/74833382/13090313
Upvotes: 1