Flutter實現(xiàn)紅包動畫效果的示例代碼
前言
紅包動畫效果實現(xiàn),如圖:

該效果的實現(xiàn)難道其實比較簡單,就是基礎的平移、旋轉和縮放動畫,但比較麻煩的就是需要寫很多小動畫組合,共由11個小動畫組合而成。
動畫拆解
紅包顯示動畫
紅包顯示時的動畫,由0到1的放大。
late AnimationController controller;
late Animation<double> animation;
///紅包展開動畫
controller = AnimationController(
duration: const Duration(milliseconds: 200), vsync: this);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
controller.forward();
});
animation = Tween(begin: 0.0, end: 1.0).animate(controller);紅包未開前,平移動畫
紅包未開前,整體微微上下平移
late AnimationController bgController;
late Animation<Offset> bgAnimation;
///紅包背景上下平移動畫
bgController = AnimationController(
duration: const Duration(milliseconds: 1000), vsync: this)
..repeat(reverse: true);
bgAnimation = Tween<Offset>(begin: Offset.zero, end: const Offset(0.0, -10))
.animate(bgController);紅包未開前,”開“按鈕縮放動畫
"開"按鈕縮放動畫,由1到0.8,動畫循環(huán)執(zhí)行。
late AnimationController openBtController;
late Animation<double> openBtAnimation;
///紅包未開時,按鈕縮放動畫
openBtController = AnimationController(
duration: const Duration(milliseconds: 1000), vsync: this)
..repeat(reverse: true);
openBtAnimation = Tween(begin: 1.0, end: 0.8).animate(openBtController);紅包開啟后,背景光顯示動畫
開紅包后,會顯示背景光
late AnimationController openLightScaleController;
late Animation<double> openLightScaleAnimation;
///開紅包后,顯示背景光
openLightScaleController = AnimationController(
duration: const Duration(milliseconds: 200), vsync: this);
openLightScaleAnimation =
Tween(begin: 0.4, end: 1.0).animate(openLightScaleController);紅包開啟后,背景光放大動畫
開紅包后,背景光微微放大1.2倍,動畫循環(huán)執(zhí)行。
late AnimationController lightScaleController;
late Animation<double> lightScaleAnimation;
///背景光放大動畫,只放大1.2倍
lightScaleController = AnimationController(
duration: const Duration(milliseconds: 1000), vsync: this)
..repeat(reverse: true);
lightScaleAnimation =
Tween(begin: 1.0, end: 1.2).animate(lightScaleController);紅包開啟后,背景光旋轉動畫
開紅包后,背景光微微旋轉,只旋轉0.02的弧度,動畫循環(huán)執(zhí)行。
late AnimationController lightRotateController;
late Animation<double> lightRotateAnimation;
///背景光旋轉動畫,微微旋轉,只旋轉0.02的弧度
lightRotateController = AnimationController(
duration: const Duration(milliseconds: 1000), vsync: this)
..repeat(reverse: true);
lightRotateAnimation =
Tween(begin: 0.0, end: 0.02).animate(lightRotateController);紅包開啟后,新背景放大動畫
開紅包后,原紅包背景縮小至不見,新紅包背景顯示
late AnimationController openController;
late Animation<double> openAnimation;
///開紅包 背景放大動畫
openController = AnimationController(
duration: const Duration(milliseconds: 200), vsync: this);
openAnimation = Tween(begin: 0.4, end: 1.0).animate(openController);紅包開啟后,新背景平移動畫
開紅包后,新背景也微微上下平移
late AnimationController openBgController;
late Animation<Offset> openBgAnimation;
///紅包背景上下平移動畫
openBgController = AnimationController(
duration: const Duration(milliseconds: 1000), vsync: this)
..repeat(reverse: true);
openBgAnimation =
Tween<Offset>(begin: Offset.zero, end: const Offset(0.0, -10))
.animate(openBgController);紅包開啟后,”立即使用“按鈕縮放動畫
開紅包后,”立即使用“按鈕縮放動畫,由1到0.9,動畫循環(huán)執(zhí)行。
late AnimationController useBtController;
late Animation<double> useBtAnimation;
///立即使用按鈕縮放動畫
useBtController = AnimationController(
duration: const Duration(milliseconds: 1000), vsync: this)
..repeat(reverse: true);
useBtAnimation = Tween(begin: 1.0, end: 0.9).animate(useBtController);紅包開啟后,金額顯示的卡片上移動畫
開紅包后,顯示金額的卡片上移
late final AnimationController offsetTopController; late final Animation<Offset> offsetTopAnimation; ///卡片上移動畫 offsetTopController = AnimationController( duration: const Duration(milliseconds: 500), vsync: this, ); offsetTopAnimation = Tween<Offset>( begin: const Offset(0, 0.6), end: const Offset(0, 0), ).animate(CurvedAnimation( parent: offsetTopController, curve: Curves.easeInOutCubic, ));
紅包開啟后,金額顯示的動畫
開紅包后,金額會從某個數(shù)值自增至實際金額數(shù)值
late AnimationController priceController;
late Animation<double> priceAnimation;
///金額變換效果
price = widget.price;
double startPrice = 0;
if (price <= 100) {
///小于100的金額從0開始自增
startPrice = 0;
} else {
///大于100的金額對半開始自增
startPrice = price / 2;
}
priceController = AnimationController(
duration: const Duration(milliseconds: 600), vsync: this);
priceAnimation =
Tween(begin: startPrice, end: price).animate(priceController);資源文件
class ImageAssets{
static const String homeIcRed1Webp = 'assets/home/ic-red-1.webp';
static const String homeIcRedBgWebp = 'assets/home/ic-red-bg.webp';
static const String homeIcRedLightWebp = 'assets/home/ic-red-light.webp';
static const String homeIcRedOpenWebp = 'assets/home/ic-red-open.webp';
static const String homeIcRed2BgWebp = 'assets/home/ic-red2-bg.webp';
static const String homeIcRed2BottomWebp = 'assets/home/ic-red2-bottom.webp';
static const String homeIcRed2BtWebp = 'assets/home/ic-red2-bt.webp';
static const String homeIcRed2TopBgWebp = 'assets/home/ic-red2-top-bg.webp';
static const String homeIcRed2TopWebp = 'assets/home/ic-red2-top.webp';
static const String homeIcCloseWhiteWebp = 'assets/home/ic-close-white.webp';
}homeIcRedBgWebp :

homeIcRed1Webp:

homeIcRedLightWebp:

homeIcRedOpenWebp:

homeIcRed2BgWebp:

homeIcRed2BottomWebp:

homeIcRed2BtWebp:

homeIcRed2TopBgWebp:

homeIcRed2TopWebp:

完整代碼
項目用到了GetX,需要注意導入。
import 'package:flutter/material.dart';
import 'package:get/get.dart';
///新人紅包
class RedEnvelopeDialog extends StatefulWidget {
double price;
RedEnvelopeDialog({Key? key, required this.price}) : super(key: key);
@override
_PageState createState() => _PageState();
}
class _PageState extends State<RedEnvelopeDialog>
with TickerProviderStateMixin {
double width = 0;
double height = 0;
double openSize = 0;
double btBgTopMargin = 0;
double openBgBottomMargin = 0;
double openBgTopMargin = 0;
double openTopBgHeight = 0;
double openBottomBgHeight = 0;
double openTopBgBottomMargin = 0;
double moveHeight = 0;
double openHeight = 0;
double useBtWidth = 0;
late AnimationController controller;
late Animation<double> animation;
late AnimationController openBtController;
late Animation<double> openBtAnimation;
late AnimationController lightScaleController;
late Animation<double> lightScaleAnimation;
late AnimationController lightRotateController;
late Animation<double> lightRotateAnimation;
late AnimationController openLightScaleController;
late Animation<double> openLightScaleAnimation;
late AnimationController bgController;
late Animation<Offset> bgAnimation;
late AnimationController openController;
late Animation<double> openAnimation;
late AnimationController openBgController;
late Animation<Offset> openBgAnimation;
late AnimationController useBtController;
late Animation<double> useBtAnimation;
late final AnimationController offsetTopController;
late final Animation<Offset> offsetTopAnimation;
late AnimationController priceController;
late Animation<double> priceAnimation;
RxBool showOpen = false.obs;
double price = 0;
@override
void initState() {
super.initState();
///根據(jù)設計稿比例計算實際數(shù)值
width = Get.width - 100;
height = (332 / 273) * width;
openSize = (78 / 332) * height;
btBgTopMargin = (20 / 332) * height;
openHeight = (332 / 273) * width;
openBgBottomMargin = (12 / 273) * width;
openBgTopMargin = (50 / 273) * width;
openTopBgHeight = (194 / 273) * width;
openBottomBgHeight = (189 / 273) * width;
openTopBgBottomMargin = (138 / 273) * width;
moveHeight = (143 / 273) * width;
useBtWidth = (178 / 273) * width;
///紅包展開動畫
controller = AnimationController(
duration: const Duration(milliseconds: 200), vsync: this);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
controller.forward();
});
animation = Tween(begin: 0.0, end: 1.0).animate(controller);
///開按鈕縮放動畫
openBtController = AnimationController(
duration: const Duration(milliseconds: 1000), vsync: this)
..repeat(reverse: true);
openBtAnimation = Tween(begin: 1.0, end: 0.8).animate(openBtController);
///背景光顯示動畫
openLightScaleController = AnimationController(
duration: const Duration(milliseconds: 200), vsync: this);
openLightScaleAnimation =
Tween(begin: 0.4, end: 1.0).animate(openLightScaleController);
///背景光放大動畫,只放大1.2倍
lightScaleController = AnimationController(
duration: const Duration(milliseconds: 1000), vsync: this)
..repeat(reverse: true);
lightScaleAnimation =
Tween(begin: 1.0, end: 1.2).animate(lightScaleController);
///背景光旋轉動畫,微微旋轉,只旋轉0.02的弧度
lightRotateController = AnimationController(
duration: const Duration(milliseconds: 1000), vsync: this)
..repeat(reverse: true);
lightRotateAnimation =
Tween(begin: 0.0, end: 0.02).animate(lightRotateController);
///紅包背景上下平移動畫
bgController = AnimationController(
duration: const Duration(milliseconds: 1000), vsync: this)
..repeat(reverse: true);
bgAnimation = Tween<Offset>(begin: Offset.zero, end: const Offset(0.0, -10))
.animate(bgController);
///開紅包 背景放大動畫
openController = AnimationController(
duration: const Duration(milliseconds: 200), vsync: this);
openAnimation = Tween(begin: 0.4, end: 1.0).animate(openController);
///開紅包背景上下平移動畫
openBgController = AnimationController(
duration: const Duration(milliseconds: 1000), vsync: this)
..repeat(reverse: true);
openBgAnimation =
Tween<Offset>(begin: Offset.zero, end: const Offset(0.0, -10))
.animate(openBgController);
///開按鈕縮放動畫
useBtController = AnimationController(
duration: const Duration(milliseconds: 1000), vsync: this)
..repeat(reverse: true);
useBtAnimation = Tween(begin: 1.0, end: 0.9).animate(useBtController);
///卡片上移動畫
offsetTopController = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
offsetTopAnimation = Tween<Offset>(
begin: const Offset(0, 0.6),
end: const Offset(0, 0),
).animate(CurvedAnimation(
parent: offsetTopController,
curve: Curves.easeInOutCubic,
));
///金額變換效果
price = widget.price;
double startPrice = 0;
if (price <= 100) {
///小于100的金額從0開始自增
startPrice = 0;
} else {
///大于100的金額對半開始自增
startPrice = price / 2;
}
priceController = AnimationController(
duration: const Duration(milliseconds: 600), vsync: this);
priceAnimation =
Tween(begin: startPrice, end: price).animate(priceController);
}
@override
void dispose() {
controller.dispose();
openBtController.dispose();
lightScaleController.dispose();
lightRotateController.dispose();
openLightScaleController.dispose();
bgController.dispose();
openController.dispose();
openBgController.dispose();
useBtController.dispose();
offsetTopController.dispose();
priceController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: Stack(
alignment: Alignment.center,
children: [
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Stack(
alignment: Alignment.center,
children: [
///背景光
Obx(
() => Visibility(
visible: showOpen.value,
child: AnimatedBuilder(
animation: openLightScaleAnimation,
builder: (context, child) {
return Transform.scale(
scale: openLightScaleAnimation.value,
child: AnimatedBuilder(
animation: lightScaleAnimation,
builder: (context, child) {
return Transform.scale(
scale: lightScaleAnimation.value,
child: AnimatedBuilder(
animation: lightRotateAnimation,
builder: (context, child) {
return Transform.rotate(
angle: lightRotateAnimation.value,
child: Image.asset(
ImageAssets.homeIcRedLightWebp,
width: double.infinity,
fit: BoxFit.fitWidth,
),
);
},
),
);
},
),
);
},
),
),
),
///開紅包前
AnimatedBuilder(
animation: animation,
builder: (context, child) {
return Transform.scale(
scale: animation.value,
child: Container(
margin: EdgeInsets.all(50),
child: AnimatedBuilder(
animation: bgAnimation,
builder: (context, child) {
return Transform.translate(
offset: bgAnimation.value,
child: Stack(
children: [
Image.asset(
ImageAssets.homeIcRedBgWebp,
width: double.infinity,
fit: BoxFit.fitWidth,
),
SizedBox(
height: height,
width: width,
child: Column(
children: [
Expanded(
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Text(
"新人見面禮",
style: TextStyle(
fontSize: 30,
color: const Color(
0xFFFFDC81)),
),
SizedBox(
height: 10,
),
Text(
"$priceRellyStr元",
style: TextStyle(
fontSize: 30,
color: const Color(
0xFFFFBB81)),
),
SizedBox(
height: 5,
),
Container(
padding:
EdgeInsets.symmetric(
horizontal: 12,
vertical: 4),
decoration: BoxDecoration(
color: const Color(
0xFFDC1215),
borderRadius:
BorderRadius
.circular(6),
),
child: Text(
"無門檻",
style: TextStyle(
fontSize: 15,
color: const Color(
0xFFFF862F)),
),
)
],
),
flex: 173,
),
Expanded(
child: Stack(
alignment:
Alignment.topCenter,
children: [
Container(
height: double.infinity,
alignment: Alignment
.bottomCenter,
margin: EdgeInsets.only(
bottom: 20),
child: Text(
"新人專享\u3000福利大派送",
style: TextStyle(
fontSize: 14,
color: const Color(
0xFFFF6571)),
),
),
Padding(
padding: EdgeInsets.only(
top: btBgTopMargin),
child: Image.asset(
ImageAssets
.homeIcRed1Webp,
width: double.infinity,
fit: BoxFit.fitWidth,
),
),
GestureDetector(
child: AnimatedBuilder(
animation:
openBtAnimation,
builder:
(context, child) {
return Transform
.scale(
scale:
openBtAnimation
.value,
child: Image.asset(
ImageAssets
.homeIcRedOpenWebp,
width: openSize,
height: openSize,
),
);
},
),
onTap: () {
controller.reverse();
showOpen.value = true;
openController
.forward();
openLightScaleController
.forward();
offsetTopController
.forward()
.whenComplete(() =>
offsetTopController
.stop());
priceController
.forward()
.whenComplete(() =>
priceController
.stop());
},
)
],
),
flex: 159,
)
],
),
)
],
),
);
},
),
));
}),
///開紅包后
Obx(() => Visibility(
visible: showOpen.value,
child: AnimatedBuilder(
animation: openAnimation,
builder: (context, child) {
return Transform.scale(
scale: openAnimation.value,
child: AnimatedBuilder(
animation: openBgAnimation,
builder: (context, child) {
return Transform.translate(
offset: openBgAnimation.value,
child: Container(
height: openHeight,
margin: EdgeInsets.all(50),
child: Stack(
alignment: Alignment.bottomCenter,
children: [
Padding(
padding: EdgeInsets.only(
bottom:
openBgBottomMargin,
top: openBgTopMargin),
child: Image.asset(
ImageAssets
.homeIcRed2BgWebp,
width: double.infinity,
fit: BoxFit.fitWidth,
),
),
Padding(
padding: EdgeInsets.only(
bottom:
openTopBgBottomMargin),
child: SlideTransition(
position:
offsetTopAnimation,
child: Stack(
alignment:
Alignment.center,
children: [
Image.asset(
ImageAssets
.homeIcRed2TopBgWebp,
height:
openTopBgHeight,
fit:
BoxFit.fitWidth,
),
Container(
height:
openTopBgHeight,
width: double
.infinity,
alignment:
Alignment
.center,
child: Column(
mainAxisAlignment:
MainAxisAlignment
.center,
crossAxisAlignment:
CrossAxisAlignment
.center,
children: [
Expanded(
child:
Column(
children: [
Stack(
alignment:
Alignment.center,
children: [
Image.asset(
ImageAssets.homeIcRed2TopWebp,
height: (30 / 273) * width,
fit: BoxFit.fitHeight,
),
Container(
width: (184 / 273) * width,
alignment: Alignment.center,
padding: EdgeInsets.symmetric(horizontal: 8),
child: Text(
"開門紅包",
style: TextStyle(
fontSize: 16,
color: const Color(0xFFBA683D),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
)
],
),
Expanded(
child:
Container(
alignment:
Alignment.center,
child:
Row(
crossAxisAlignment:
CrossAxisAlignment.end,
mainAxisAlignment:
MainAxisAlignment.center,
children: [
AnimatedBuilder(
animation: priceController,
builder: (BuildContext context, Widget? child) {
return Text(
priceStr,
style: TextStyle(fontSize: 48, color: const Color(0xFFF30313), height: 1, fontWeight: FontWeight.bold),
);
},
),
Text(
'元',
style: TextStyle(fontSize: 20, height: 2, color: Color(0xFF141414)),
)
],
),
))
],
),
flex: 128,
),
Expanded(
child:
Column(
children: [
Expanded(
child:
Container(
alignment:
Alignment.bottomCenter,
child: Text("永久有效",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color: const Color(0xFF8A8A8A),
height: 1,
)),
),
flex:
2,
),
Expanded(
child:
const SizedBox(),
flex:
3,
)
],
),
flex: 65,
)
],
))
],
)),
),
Image.asset(
ImageAssets
.homeIcRed2BottomWebp,
width: double.infinity,
fit: BoxFit.fitWidth,
),
Column(
children: [
Expanded(
child: const SizedBox(),
flex: 143,
),
Expanded(
child: Container(
alignment:
Alignment.center,
child: Column(
mainAxisAlignment:
MainAxisAlignment
.center,
children: [
SizedBox(
height: 20),
AnimatedBuilder(
animation:
useBtAnimation,
builder:
(context,
child) {
return Transform.scale(
scale: useBtAnimation.value,
child: GestureDetector(
onTap: () {
//todo 點擊事件
},
child: Stack(
alignment: Alignment.center,
children: [
Image.asset(
ImageAssets.homeIcRed2BtWebp,
width: useBtWidth,
fit: BoxFit.fitWidth,
),
Text("立即領取", style: TextStyle(fontSize: 16, color: const Color(0xFFFFF0E1))),
],
)));
}),
SizedBox(
height: ((30 /
273) *
width),
),
Text(
"可在“我的-優(yōu)惠券”中查看",
style: TextStyle(
fontSize:
12,
color: const Color(
0xFFFF6571)),
),
],
)),
flex: 189,
),
],
)
],
),
));
}));
})))
],
)
],
),
//關閉按鈕
GestureDetector(
onTap: () {
Get.back();
},
child: Container(
margin: EdgeInsets.only(bottom: height * 1.5, left: width),
child: Image.asset(
ImageAssets.homeIcCloseWhiteWebp,
width: 21,
height: 21,
),
))
],
),
);
}
///金額數(shù)值變化
String get priceStr {
if (price % 1 == 0) {
return (priceAnimation.value.toInt()).toString();
} else {
return priceAnimation.value.toStringAsFixed(2);
}
}
///小數(shù)據(jù)點后沒有尾數(shù)則不顯示
String get priceRellyStr {
if (price % 1 == 0) {
return (price.toInt()).toString();
} else {
String pr = price.toStringAsFixed(2);
if (pr.endsWith("0")) {
return pr.substring(0, pr.length - 1);
}
return price.toStringAsFixed(2);
}
}
}以上就是Flutter實現(xiàn)紅包動畫效果的示例代碼的詳細內(nèi)容,更多關于Flutter紅包動畫的資料請關注腳本之家其它相關文章!
相關文章
Android ViewPager制作新手導航頁(動態(tài)加載)
這篇文章主要為大家詳細介紹了Android ViewPager制作新手導航頁,了解什么是動態(tài)加載指示器,感興趣的小伙伴們可以參考一下2016-05-05
Android Studio下添加assets目錄的實現(xiàn)方法
下面小編就為大家?guī)硪黄狝ndroid Studio下添加assets目錄的實現(xiàn)方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-03-03
Android動畫 實現(xiàn)開關按鈕動畫(屬性動畫之平移動畫)實例代碼
這篇文章主要介紹了Android動畫 實現(xiàn)開關按鈕動畫(屬性動畫之平移動畫)實例代碼的相關資料,需要的朋友可以參考下2016-11-11
Android中實現(xiàn)ProgressBar菊花旋轉進度條的動畫效果
大家在一些頁面經(jīng)常會遇到加載中需要顯示一個加載動畫,像旋轉的菊花旋轉的圈圈動畫效果,本文通過實例代碼給大家講解下,需要的朋友參考下吧2021-09-09
Android編程實現(xiàn)兩個Activity之間共享數(shù)據(jù)及互相訪問的方法
這篇文章主要介紹了Android編程實現(xiàn)兩個Activity之間共享數(shù)據(jù)及互相訪問的方法,簡單分析了Android中Activity數(shù)據(jù)共享與訪問的相關技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-11-11

