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 initStatesuper.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 disposeanimationDispose();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小于卡片在左侧的delayint reversalDelay =100;_flipReversalDelay = reversalDelay;} else {if (_flipReversal) {// 如果从右向左,翻转时,则卡片在右侧的delay小于卡片在左侧的delaydelay = _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信息。
学习记录,每天不停进步。