You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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
}
关键说明
  1. 偏移修正:为中心元素添加offset(x = animatedOffsetX),当元素放大时向左偏移cellSize/2,这样元素会向左右两侧各扩展一半尺寸,实现对称放大效果。
  2. 滚动位置修正:首次滚动时使用scrollToItem并传入负偏移量,确保中心元素的中心与列表中心对齐,避免首次启动时的布局偏差。
  3. 状态判断优化:在isItemInCenter中调整元素中心的计算逻辑,考虑偏移量的影响,确保中心元素的判断准确。

内容的提问来源于stack exchange,提问作者Sirop4ik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 14:37:13