Flutter实现卡片顶部层叠与点击带动画展开的技术求助
实现重叠卡片点击展开动画的解决方案
需求:卡片按顺序顶部重叠排列,点击选中卡片时,该卡片带动画展开,其余卡片下移。现有代码能实现重叠效果,但无法触发预期的展开动画,以下是修正方案。
原始尝试代码
import 'dart:math' as math; import 'package:flutter/material.dart'; class MerchantCards extends StatefulWidget { const MerchantCards({super.key}); @override State<MerchantCards> createState() => _MerchantCardsState(); } class _MerchantCardsState extends State<MerchantCards> { int? _selectedCardIndex; void _onCardTap(int index) { setState(() { _selectedCardIndex = index; // Toggle selection }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("Overlapping Animated Cards"), ), body: ListView.builder( itemCount: 3, // Number of cards itemBuilder: (context, index) { return Align( heightFactor: _selectedCardIndex == index ? 2 : 0.3, child: GestureDetector( onTap: () => _onCardTap(index), child: SizedBox( height: 200, width: 300, child: Card( color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt()) .withOpacity(1.0), child: Center( child: Text("Card ${index + 1}"), ), ), ))); }, ), ); } }
问题分析
原代码使用ListView.builder配合Align的heightFactor控制卡片高度,这种方式没有内置动画过渡,且ListView的线性布局逻辑难以精准控制卡片间的重叠位移动画,无法实现点击时的平滑展开和其余卡片下移效果。
修正方案
改用Stack作为容器,结合AnimatedPositioned控制卡片位置动画,AnimatedSize实现卡片高度的平滑变化,具体代码如下:
import 'dart:math' as math; import 'package:flutter/material.dart'; class MerchantCards extends StatefulWidget { const MerchantCards({super.key}); @override State<MerchantCards> createState() => _MerchantCardsState(); } class _MerchantCardsState extends State<MerchantCards> with SingleTickerProviderStateMixin { int? _selectedCardIndex; // 折叠状态下卡片高度与重叠偏移量 final double _collapsedHeight = 60; final double _overlapOffset = 40; // 展开状态下卡片高度 final double _expandedHeight = 200; void _onCardTap(int index) { setState(() { // 切换选中状态:再次点击已选中卡片则取消选中 _selectedCardIndex = _selectedCardIndex == index ? null : index; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("Overlapping Animated Cards"), ), body: Stack( children: List.generate(3, (index) { // 计算卡片top位置:选中卡片置顶,未选中卡片按规则偏移 double topPosition; if (_selectedCardIndex == index) { topPosition = 0; } else if (_selectedCardIndex != null) { if (index < _selectedCardIndex!) { topPosition = _expandedHeight + (index * _overlapOffset); } else { topPosition = _expandedHeight + ((index - 1) * _overlapOffset); } } else { topPosition = index * _overlapOffset; } return AnimatedPositioned( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, top: topPosition, left: 20, right: 20, child: AnimatedSize( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, child: GestureDetector( onTap: () => _onCardTap(index), child: Card( elevation: 4, color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0), child: SizedBox( height: _selectedCardIndex == index ? _expandedHeight : _collapsedHeight, child: Center( child: Text("Card ${index + 1}", style: const TextStyle(fontSize: 18)), ), ), ), ), ), ); }).reversed.toList(), // 反转列表确保第一个卡片在底层,最后一个在顶层 ), ); } }
关键实现点
- Stack容器:支持卡片叠放,通过
reversed调整层级,保证视觉上的叠放顺序 - AnimatedPositioned:控制卡片
top属性的平滑动画,实现选中卡片置顶、其余卡片下移的过渡效果 - AnimatedSize:配合卡片高度变化,实现折叠/展开的高度动画
- 选中逻辑优化:支持再次点击已选中卡片取消选中,恢复初始重叠状态
内容的提问来源于stack exchange,提问作者Mani Basto
相关产品推荐
相关产品推荐

