flutter开发实战-实现卡片翻转动画效果
之前开发中遇到了商品卡片翻转,商品正面是商品图片、商品名称;背面是商品价格,需要做卡片翻转动画。
动画实现即:在一段时间内,快速地多次改变UI外观;由于人眼会产生视觉暂留,所以最终看到的就是一个“连续”的动画。
这里实现翻转动画,实现方案Transform+IndexedStack
IndexedStack在翻转角度不同,显示对应的card信息
index: _rotateAnimation.value < pi / 2.0 ? 0 : 1,
一、效果图
运行后效果图如下
二、代码实现
IndexedStack
IndexedStack({
super.key,
super.alignment,
super.textDirection,
super.clipBehavior,
StackFit sizing = StackFit.loose,
this.index = 0,
super.children,
}) : super(fit: sizing);
完整动画效果代码如下
import 'dart:async';
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
class GoodsCardFlip extends StatefulWidget {
const GoodsCardFlip({super.key, required this.cardSize});
final Size cardSize;
State<GoodsCardFlip> createState() => _GoodsCardFlipState();
}
class _GoodsCardFlipState extends State<GoodsCardFlip>
with TickerProviderStateMixin {
late AnimationController _animationController;
// 翻转动画
late Animation<double> _rotateAnimation;
late AnimationStatus _lastStatus = AnimationStatus.dismissed;
Timer? _globalFlipTimer;
// 定义一个翻转从左往右,为false,如果为true,则从右往左开始翻转
bool _flipReversal = false;
int _flipDelay = 0; // 从左向右延迟翻转时间
int _flipReversalDelay = 0; // 从右向左延迟翻转时间
bool _isSetFlipDelay = false;
bool _isDisposed = false;
void initState() {
// TODO: implement initState
super.initState();
_isDisposed = false;
_flipReversal = false;
_animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 500));
//使用弹性曲线
_rotateAnimation =
CurvedAnimation(parent: _animationController, curve: Curves.linear);
_rotateAnimation = Tween(begin: 0.0, end: pi).animate(_rotateAnimation);
_animationController.addListener(() {
if (mounted) {
setState(() {});
}
});
_animationController.addStatusListener((status) {
if (status == _lastStatus) return;
_lastStatus = status;
});
// 横屏的全部翻转到价值的控制定时器
_globalFlipTimer = new Timer.periodic(new Duration(seconds: 15), (timer) {
// 整体翻转动画
flipCards();
});
}
void timerDispose() {
_globalFlipTimer?.cancel();
_globalFlipTimer = null;
}
void animationDispose() {
_animationController.dispose();
}
void dispose() {
// TODO: implement dispose
animationDispose();
timerDispose();
super.dispose();
_isDisposed = true;
}
void switchCard() {}
Widget build(BuildContext context) {
return buildGlobal();
}
Matrix4 _buildTransform() {
final matrix = Matrix4.identity()
..rotateY(_rotateAnimation.value);
return matrix;
}
Widget buildGlobal() {
return Container(
width: widget.cardSize.width,
height: widget.cardSize.height,
alignment: Alignment.center,
child: ClipRRect(
borderRadius:
BorderRadius.all(Radius.circular(13.0)),
child: Transform(
transform: _buildTransform(),
alignment: Alignment.center,
child: IndexedStack(
alignment: Alignment.center,
children: <Widget>[
buildFront(),
buildBack(),
],
index: _rotateAnimation.value < pi / 2.0 ? 0 : 1,
),
),
),
);
}
Widget buildFront() {
return GoodsImageCard(
cardSize: widget.cardSize,
);
}
Widget buildBack() {
return Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..rotateY(pi),
child: GoodsPriceCard(
cardSize: widget.cardSize,
),
);
}
// 处理定时器
void flipCards() {
int delay = 0;
if (_isSetFlipDelay == false) {
// 如果从右向左,翻转时,则卡片在右侧的delay小于卡片在左侧的delay
// 每次间隔的翻转延迟时间,100毫秒
double perDelay = 100.0;
_flipDelay = delay;
// 如果从右向左,翻转时,则卡片在右侧的delay小于卡片在左侧的delay
int reversalDelay =
100;
_flipReversalDelay = reversalDelay;
} else {
if (_flipReversal) {
// 如果从右向左,翻转时,则卡片在右侧的delay小于卡片在左侧的delay
delay = _flipReversalDelay;
} else {
delay = _flipDelay;
}
}
_isSetFlipDelay = true;
Future.delayed(Duration(milliseconds: delay), () {
cardFlip(_flipReversal);
});
}
// 翻转动画
void cardFlip(bool reverse) {
if (_isDisposed == true) {
return;
}
if (_animationController.isAnimating) return;
if (reverse) {
_animationController.reverse();
} else {
_animationController.forward();
}
_flipReversal = !_flipReversal;
if (_flipReversal == true) {
Future.delayed(Duration(seconds: 5), () {
flipCards();
});
}
}
}
class GoodsImageCard extends StatelessWidget {
const GoodsImageCard({super.key, required this.cardSize});
final Size cardSize;
Widget build(BuildContext context) {
return Container(
width: cardSize.width,
height: cardSize.height,
color: Colors.greenAccent,
alignment: Alignment.center,
child: Text('详情card',style: TextStyle(
fontSize: 26,
fontWeight: FontWeight.w700,
fontStyle: FontStyle.normal,
color: Colors.white,
decoration: TextDecoration.none,
),),
);
}
}
class GoodsPriceCard extends StatelessWidget {
const GoodsPriceCard({super.key, required this.cardSize});
final Size cardSize;
Widget build(BuildContext context) {
return Container(
width: cardSize.width,
height: cardSize.height,
color: Colors.blueAccent,
alignment: Alignment.center,
child: Text('价格card',style: TextStyle(
fontSize: 26,
fontWeight: FontWeight.w700,
fontStyle: FontStyle.normal,
color: Colors.white,
decoration: TextDecoration.none,
),),
);
}
}
三、小结
flutter开发实战-Canvas绘图之Path路径动画
flutter开发实战-实现卡片翻转动画效果,实现方案Transform+IndexedStack,IndexedStack在翻转角度不同,显示对应的card信息。
学习记录,每天不停进步。