У меня есть LazyRow с несколькими ячейками, и он отслеживает элемент, который в данный момент находится в центре экрана. Проблема в том, что я использую состояние для отслеживания этого центрального элемента, и каждый раз, когда состояние обновляется, оно запускает рекомпозицию. В инспекторе макета я заметил, что ячейки LazyRow постоянно обновляются при прокрутке влево или вправо. Этого не произойдет, если я закомментирую отслеживание состояния. Мой вопрос: как мне избежать этих ненужных рекомпозиций?
Вот код:
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
}
Есть ли предложения?
Вы можете сократить количество рекомпозиций, соблюдая размер на этапе макета, а не композиции. Для этого вы можете использовать модификатор layout для изображений вместо size() и наблюдать там значение анимированного размера.
.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)
}
}
Приведенный выше код представляет собой слегка измененный код из этого ответа https://stackoverflow.com/a/74833382/13090313
Вместо того, чтобы вручную определять центральный элемент, пробовали ли вы использовать HorizontalPager?