Jetpack Compose中棋子移动被单元格遮挡的动画问题排查
解决Jetpack Compose国际象棋棋子移动被单元格遮挡的问题
问题核心是棋子的渲染层级低于后续单元格:当前每个棋子都嵌套在对应位置的单元格Box内部,而Column从上到下依次渲染行、Row从左到右渲染列。黑棋向下移动时,目标行的单元格是后渲染的,层级比原棋子所在单元格更高,导致移动过程中棋子被遮挡。
解决方案:将棋子统一渲染在棋盘顶层
把所有棋子从单元格中抽离,放在整个棋盘的最上层,确保棋子的绘制层级高于所有单元格,彻底解决遮挡问题。
修改后的代码示例
@Composable fun RenderChessBoard( chessBoardState: ChessBoard, composableScope: CoroutineScope = rememberCoroutineScope(), onPieceMove: (ChessPiece, Position) -> Unit ) { var cellSize by remember { mutableStateOf(IntSize(0, 0)) } val selectedPiece = remember { mutableStateOf<ChessPiece?>(null) } var pieceOffsets by remember { mutableStateOf<Map<ChessPiece, IntOffset>>(emptyMap()) } // 外层Box:先渲染棋盘单元格(底层),再渲染顶层棋子 Box { // 棋盘单元格区域(仅渲染格子和可移动提示) Column { chessBoardState.cells.forEachIndexed { rowIndex, row -> Row { row.forEachIndexed { columnIndex, _ -> val currentPosition = Position(rowIndex, columnIndex) val cellColor = determineCellColor(selectedPiece.value, currentPosition, chessBoardState) Box( modifier = Modifier .weight(1f) .onSizeChanged { if (cellSize.width == 0) cellSize = it } .aspectRatio(1f) .background(cellColor), contentAlignment = Alignment.Center ) { DrawAvailableMoves( selectedPiece = selectedPiece.value, currentPosition = currentPosition, chessBoard = chessBoardState, onPieceMove = { chessPiece, targetPosition -> // 计算移动偏移量 pieceOffsets = pieceOffsets + (chessPiece to IntOffset( (targetPosition.column - chessPiece.position.column) * cellSize.width, (targetPosition.row - chessPiece.position.row) * cellSize.height )) // 动画结束后更新棋盘状态并清理偏移记录 composableScope.launch { delay(ANIMATION_DURATION_MS) onPieceMove.invoke(chessPiece, targetPosition) pieceOffsets = pieceOffsets - chessPiece } selectedPiece.value = null } ) } } } } } // 顶层统一渲染所有棋子 chessBoardState.cells.flatMap { row -> row.filterNotNull() }.forEach { currentPiece -> // 计算棋子在棋盘上的基础位置 val baseOffset = IntOffset( currentPiece.position.column * cellSize.width, currentPiece.position.row * cellSize.height ) // 动画偏移量 val targetOffset = pieceOffsets[currentPiece] ?: IntOffset(0, 0) val animatedOffset by animateIntOffsetAsState( targetValue = targetOffset, animationSpec = tween(durationMillis = ANIMATION_DURATION_MS.toInt()) ) PieceView( modifier = Modifier .offset { baseOffset + animatedOffset } .size(cellSize.width.dp, cellSize.height.dp), piece = currentPiece ) } } }
关键修改点说明
- 层级重构:用外层
Box包裹棋盘单元格和棋子,先渲染单元格(底层)再渲染棋子(顶层),确保棋子不会被任何单元格遮挡。 - 位置计算优化:通过
baseOffset计算棋子在棋盘上的初始位置,叠加动画偏移量实现全局坐标定位,避免单元格内局部偏移的层级问题。 - 状态清理:动画结束后移除对应棋子的偏移记录,避免影响后续移动的初始位置计算。
其他可选方案(不推荐)
如果不想重构布局,可尝试给棋子添加zIndex = Float.MAX_VALUE修饰符,但zIndex仅在同级布局内生效,跨Column/Row的单元格时可能失效,可靠性远不如顶层渲染方案。
内容的提问来源于stack exchange,提问作者Royss
相关产品推荐
相关产品推荐

