flutter实现多个transform动画叠加
最近完成的一个点币/红包视觉动效,过程效果图大致如下所示。
可以看到动画的核心是要完成位移+缩放+旋转,多种transform的效果,这个时候就可以考虑使用矩阵变换了。
矩阵变换Matrix4
单独的位移、缩放、旋转变换。
位移
Transform.translate(offset: Offset(-20.0, -5.0))
// 默认原点为左上角,左移20像素,向上平移5像素
缩放
Transform.scale(scale: 0.4,)
// 缩小到0.4倍
旋转
Transform.rotate(angle: pi / 2,)
// 旋转90度
注意:要使用pi
需先进行import 'dart:math';
导包。
矩阵变化
上述的单一转换都可以用矩阵变化实现,矩阵变化可以把多种单一变化合并到一起。
此处不一一列举具体如何直接写矩阵,会通过Tween
来直接映射变化。
创建一个动画控制器和多个动画。
late AnimationController _controllerMatix; // 动画控制器
late Animation<double> _scaleAnim; // 缩放动画
late Animation<double> _rotateAnim; // 旋转动画
late Animation<Offset> _translateAnim; // 位移动画
late Matrix4 _matrix4 = Matrix4.identity(); // 创建一个矩阵,初始化
在初始化函数中,添加控制器设置以及各种监听
void initState() {
super.initState();
_controllerMatix = AnimationController(
vsync: this,
duration: Duration(seconds: 1),
);
// 把多个Animation都绑定到一个controller上
_scaleAnim = Tween<double>(begin: 1.0, end: 0.5).animate(_controllerMatix);
_rotateAnim = Tween<double>(begin: 0.0, end: pi/4*(widget.isCash ? 1 : -1)).animate(_controllerMatix);
_translateAnim = Tween<Offset>(begin: Offset.zero, end: Offset(65 *(widget.isCash ? 1 : -1), -69)).animate(_controllerMatix);
_opacityAnimation = Tween<double>(begin: 1.0, end: 0.6).animate(_controllerMatix);
// controller监听
_controllerMatix.addListener(() {
setState(() {
_matrix4 = Matrix4.identity();
_matrix4.translate(_translateAnim.value.dx, _translateAnim.value.dy, 0.0);
_matrix4.scale(_scaleAnim.value);
_matrix4.rotateZ(_rotateAnim.value);
});
});
// controller状态监听
_controllerMatix.addStatusListener((status) {
if (status == AnimationStatus.dismissed) {
setState(() {
play = false;
});
}
if (status == AnimationStatus.completed) {
setState(() {
isNeedHidden = true;
play = true;
});
// 动画完成后调用父组件传递过来的function
widget.animationCallBack();
}
});
}
父子组件通信
子组件调用父组件的方法可以通过入参的方式function
// 父组件(伪代码)
child: Son(playEndFunction: (){
print('---refesh page---');
);
// 子组件(伪代码)
class Son extends StatefulWidget{
final Function playEndFunction;
const NewUserGainAnimation({required this.playEndFunction, Key? key }) : super(key: key);
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (){
// 调用父组件的方法
widget.playEndFunction()
},
child: Text('点击调用父组件的function')
);
}
}
父组件调用子组件的方法,可以通过GlobalKey的方式。
// 父组件(伪代码)
child: Son(playEndFunction: (){
print('---refesh page---');
);
// 子组件(伪代码)
class Son extends StatefulWidget{
final Function playEndFunction;
const NewUserGainAnimation({required this.playEndFunction, Key? key }) : super(key: key);
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (){
// 调用父组件的方法
widget.playEndFunction()
},
child: Text('点击调用父组件的function')
);
}
}
完整代码
父组件,调用红包的组件
import 'package:flutter/material.dart';
import 'package:flutter_tardis/biz/new_user_welfare/components/new_user_gain_animation.dart';
class DemoTest extends StatefulWidget {
const DemoTest({Key? key}) : super(key: key);
@override
State<DemoTest> createState() => _DemoTestState();
}
class _DemoTestState extends State<DemoTest> {
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
final screenWidth = size.width;
var awardIsCash = false;
return Container(
// padding: const EdgeInsets.only(top: 52),
decoration: const BoxDecoration(
gradient: LinearGradient(begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Color.fromARGB(255, 242, 110, 103), Color(0xffffffff)]),
),
child: Stack(
children: [
Column(
children: [
const SizedBox(height: 52,),
Container(
margin: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
decoration: BoxDecoration(
border: Border.all(color: Colors.red, width: 0.5),
borderRadius: BorderRadius.circular(8)
),
height: 90,
child: const Row(
children: [
Expanded(
child: Text(
'xxx点币',
style: TextStyle(
color: Colors.white,
fontSize: 18.0,
height: 1.2,
decoration:TextDecoration.none,
),
)
),
Expanded(
child: Text(
'xxx元',
style: TextStyle(
color: Colors.white,
fontSize: 18.0,
height: 1.2,
decoration:TextDecoration.none,
),
)
),
],
),
),
const SizedBox(
height: 90,
child: Text(
'其他模块1',
style: TextStyle(color: Colors.white, fontSize: 18.0, height: 1.2, decoration:TextDecoration.none)
),
),
const SizedBox(height: 20,),
const SizedBox(
height: 90,
child: Text(
'其他模块1',
style: TextStyle(color: Colors.white, fontSize: 18.0, height: 1.2, decoration:TextDecoration.none)
),
),
const SizedBox(height: 20,),
GestureDetector(
onTap: (){
print('----播放动效');
gainAnimationKey.currentState?.animationPlay();
},
child: const Text(
'点击播放动效',
style: TextStyle(color: Colors.green, fontSize: 18.0, height: 1.2, decoration:TextDecoration.none)
),
),
],
),
Positioned(
top: 116,
left: 0,
right: 0,
child: Container(
height: 109,
child: Stack(
children: [
Positioned(
child: NewUserGainAnimation(
isCash: awardIsCash,
screenLeft: screenWidth / 2 - 20,
key: gainAnimationKey,
animationCallBack: (){
print('动效播放完成后');
// loadPageData(true);
}
),
),
],
)
)
),
],
),
);
}
}
子组件点币/红包组件
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_tardis/component/image/qd_asset_image.dart';
import 'package:flutter_tardis/generated/assets.dart';
GlobalKey<_NewUserGainAnimationState> gainAnimationKey = GlobalKey();
class NewUserGainAnimation extends StatefulWidget {
final bool isCash;
final double screenLeft;
final Function animationCallBack;
const NewUserGainAnimation({
required this.isCash,
required this.screenLeft,
required this.animationCallBack,
Key? key
})
: super(key: key);
@override
_NewUserGainAnimationState createState() => _NewUserGainAnimationState();
}
class _NewUserGainAnimationState extends State<NewUserGainAnimation> with TickerProviderStateMixin {
late AnimationController _controllerMatix;
late Animation<double> _scaleAnim;
late Animation<double> _rotateAnim;
late Animation<Offset> _translateAnim;
late Matrix4 _matrix4 = Matrix4.identity();
late Animation<double> _opacityAnimation;
bool play = false;
bool isNeedHidden = true;
void animationPlay () {
setState(() {
isNeedHidden = false;
});
play ? (){} : _controllerMatix.forward();
}
@override
void initState() {
super.initState();
_controllerMatix = AnimationController(
vsync: this,
duration: Duration(seconds: 1),
);
_scaleAnim = Tween<double>(begin: 1.0, end: 0.5).animate(_controllerMatix);
_rotateAnim = Tween<double>(begin: 0.0, end: pi/4*(widget.isCash ? 1 : -1)).animate(_controllerMatix);
_translateAnim = Tween<Offset>(begin: Offset.zero, end: Offset(65 *(widget.isCash ? 1 : -1), -69)).animate(_controllerMatix);
_opacityAnimation = Tween<double>(begin: 1.0, end: 0.6).animate(_controllerMatix);
_controllerMatix.addListener(() {
setState(() {
_matrix4 = Matrix4.identity();
_matrix4.translate(_translateAnim.value.dx, _translateAnim.value.dy, 0.0);
_matrix4.scale(_scaleAnim.value);
_matrix4.rotateZ(_rotateAnim.value);
});
});
_controllerMatix.addStatusListener((status) {
if (status == AnimationStatus.dismissed) {
setState(() {
play = false;
});
}
if (status == AnimationStatus.completed) {
print('--AnimationStatus-done-----');
setState(() {
isNeedHidden = true;
play = true;
});
// 动画完成回调
widget.animationCallBack();
}
});
}
@override
void dispose() {
_controllerMatix.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controllerMatix,
builder: (context, child) {
return Stack(
children: [
Positioned(
bottom: 0,
left: widget.screenLeft,
child: SizedBox(
width: 40,
height: 40,
child: Opacity(
opacity: isNeedHidden ? 0 : _opacityAnimation.value,
child: Transform(
transform: _matrix4,
child: Image(
image: QDAssetImage(widget.isCash ? Assets.newUserWelfareRmbIcon : Assets.newUserWelfareDianIcon ),
width: 40,
height: 40,
)
),
),
),
),
],
);
},
);
}
}
转载自:https://juejin.cn/post/7380942251411079203