feat: 为划转页面账户卡片添加位置互换动画

- 移除按钮旋转动画,保持按钮静止
- 两个账户卡片(从/到)平滑互换位置
- 使用 AnimatedSwitcher + FadeTransition + SlideTransition
- 动画时长 300ms,easeOut 曲线
- 上方卡片向上滑出,下方卡片向下滑入
- 视觉效果更流畅自然
This commit is contained in:
2026-03-30 11:47:07 +08:00
parent 9920a29261
commit e3c5a0b24d
84 changed files with 46826 additions and 46669 deletions

View File

@@ -18,29 +18,14 @@ class TransferPage extends StatefulWidget {
State<TransferPage> createState() => _TransferPageState();
}
class _TransferPageState extends State<TransferPage> with SingleTickerProviderStateMixin {
class _TransferPageState extends State<TransferPage> {
final _amountController = TextEditingController();
int _direction = 1; // 1: 资金→交易, 2: 交易→资金
bool _isLoading = false;
late AnimationController _rotationController;
late Animation<double> _rotationAnimation;
@override
void initState() {
super.initState();
// 初始化旋转动画
_rotationController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
_rotationAnimation = Tween<double>(
begin: 0,
end: 0.5, // 180度 = 0.5 圈
).animate(CurvedAnimation(
parent: _rotationController,
curve: Curves.easeInOut,
));
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<AssetProvider>().refreshAll(force: true);
});
@@ -49,7 +34,6 @@ class _TransferPageState extends State<TransferPage> with SingleTickerProviderSt
@override
void dispose() {
_amountController.dispose();
_rotationController.dispose();
super.dispose();
}
@@ -144,17 +128,11 @@ class _TransferPageState extends State<TransferPage> with SingleTickerProviderSt
_amountController.text = amount.toStringAsFixed(8).replaceAll(RegExp(r'\.?0+$'), '');
}
/// 切换方向并触发旋转动画
/// 切换方向
void _toggleDirection() {
setState(() {
_direction = _direction == 1 ? 2 : 1;
});
// 触发旋转动画
if (_rotationController.status == AnimationStatus.completed) {
_rotationController.reset();
}
_rotationController.forward();
}
@override
@@ -187,53 +165,106 @@ class _TransferPageState extends State<TransferPage> with SingleTickerProviderSt
padding: AppSpacing.pagePadding,
child: Column(
children: [
// 从账户卡片
_buildAccountCard(
label: '',
accountName: _fromLabel,
balance: _fromBalance,
isDark: isDark,
colorScheme: colorScheme,
// 第一个卡片(根据方向显示"从"或"到"
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (child, animation) {
return FadeTransition(
opacity: animation,
child: SlideTransition(
position: Tween<Offset>(
begin: Offset(0, _direction == 1 ? -0.1 : 0.1),
end: Offset.zero,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeOut,
)),
child: child,
),
);
},
child: Container(
key: ValueKey('card1-$_direction'),
child: _direction == 1
? _buildAccountCard(
label: '',
accountName: _fromLabel,
balance: _fromBalance,
isDark: isDark,
colorScheme: colorScheme,
)
: _buildAccountCard(
label: '',
accountName: _toLabel,
balance: _toBalance,
isDark: isDark,
colorScheme: colorScheme,
),
),
),
// 方向切换按钮
// 方向切换按钮(固定在中间)
GestureDetector(
onTap: _toggleDirection,
child: RotationTransition(
turns: _rotationAnimation,
child: Container(
margin: EdgeInsets.symmetric(vertical: AppSpacing.sm),
padding: EdgeInsets.all(AppSpacing.sm + AppSpacing.xs),
decoration: BoxDecoration(
color: colorScheme.primary,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.3),
blurRadius: 8,
),
],
),
child: Icon(
Icons.swap_vert,
color: colorScheme.onPrimary,
size: 22,
),
child: Container(
margin: EdgeInsets.symmetric(vertical: AppSpacing.sm),
padding: EdgeInsets.all(AppSpacing.sm + AppSpacing.xs),
decoration: BoxDecoration(
color: colorScheme.primary,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.3),
blurRadius: 8,
),
],
),
child: Icon(
Icons.swap_vert,
color: colorScheme.onPrimary,
size: 22,
),
),
),
// 到账户卡片
_buildAccountCard(
label: '',
accountName: _toLabel,
balance: _toBalance,
isDark: isDark,
colorScheme: colorScheme,
// 第二个卡片(根据方向显示"到"或"从"
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (child, animation) {
return FadeTransition(
opacity: animation,
child: SlideTransition(
position: Tween<Offset>(
begin: Offset(0, _direction == 1 ? 0.1 : -0.1),
end: Offset.zero,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeOut,
)),
child: child,
),
);
},
child: Container(
key: ValueKey('card2-$_direction'),
child: _direction == 1
? _buildAccountCard(
label: '',
accountName: _toLabel,
balance: _toBalance,
isDark: isDark,
colorScheme: colorScheme,
)
: _buildAccountCard(
label: '',
accountName: _fromLabel,
balance: _fromBalance,
isDark: isDark,
colorScheme: colorScheme,
),
),
),
SizedBox(height: AppSpacing.lg),
// 金额输入卡片
_buildAmountSection(colorScheme, isDark),