Jetpack Compose:LazyRow中心单元格动画偏移,如何实现双向对称放大?
问题描述
我实现了一个包含25个单元格的LazyRow,需要满足两个需求:
- 应用启动时自动滚动至中间单元格(如第13个)居中显示,支持左右滚动
- 中心单元格尺寸为其他单元格的两倍
预期效果是中心单元格对称放大,但首次启动时,中心单元格从左上向右放大,导致右侧相邻单元格偏移,手动滚动后才显示正常。
问题:如何调整animateDpAsState动画,使中心单元格向左右两侧对称放大,确保首次启动即呈现预期效果?
解决方案
问题出在LazyRow元素默认的布局锚点是左上角,当中心单元格尺寸变大时,会向右挤压其他元素,而非对称扩展。要实现对称放大,需要让中心元素在尺寸变化时保持自身中心与列表中心对齐,通过偏移修正抵消尺寸变化带来的位移。
具体修改点:
- 为中心元素添加水平偏移:当元素变为双倍尺寸时,向左偏移
cellSize/2,这样元素会向左右两侧各扩展cellSize/2,实现对称放大 - 调整滚动逻辑,确保首次滚动到中心位置时,考虑到中心元素的尺寸差异,避免布局偏移
- 优化状态判断,确保首次启动时能准确识别中心元素
修改后的完整代码
import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.snap 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.offset 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.LaunchedEffect 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() val centerIdx = number / 2 // 首次滚动到中心位置,考虑中心元素的尺寸偏移 LaunchedEffect(number) { // 计算滚动偏移:让中心元素的中心与列表中心对齐 val offset = (cellSize / 2).toPx().toInt() listState.scrollToItem( index = centerIdx, // 偏移量修正,抵消中心元素放大后的位移 scrollOffset = -offset ) } 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 = "" ) // 中心元素的水平偏移:向左偏移cellSize/2,实现对称放大 val animatedOffsetX: Dp by animateDpAsState( targetValue = if (isCenterItem) -(cellSize / 2) else 0.dp, animationSpec = tween(durationMillis = 300), label = "" ) Image( modifier = Modifier .size(animatedSize) .offset(x = animatedOffsetX) .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) + (if (itemInfo.index == idx) -(itemInfo.size - 75.dp.toPx().toInt())/2 else 0) kotlin.math.abs(screenCenter - itemCenter) }?.index ?: -1 (centeredItemIdx + withOffset) == idx } } return isItemInCenter }
关键说明
- 偏移修正:为中心元素添加
offset(x = animatedOffsetX),当元素放大时向左偏移cellSize/2,这样元素会向左右两侧各扩展一半尺寸,实现对称放大效果。 - 滚动位置修正:首次滚动时使用
scrollToItem并传入负偏移量,确保中心元素的中心与列表中心对齐,避免首次启动时的布局偏差。 - 状态判断优化:在
isItemInCenter中调整元素中心的计算逻辑,考虑偏移量的影响,确保中心元素的判断准确。
内容的提问来源于stack exchange,提问作者Sirop4ik
相关产品推荐
相关产品推荐

