Flutter:动画摘要

一、动画API说明:
动画由Animation、Curve、AnimationController、Tween一起配合完成。

1.Animation用于保存动画的过渡值和状态:

addListener():监听每一帧的回调事件。
addStatusListener():监听动画状态改变事件,AnimationStatus.forward为开始、completed为结束、reverse为反向、dismissed为终止。

例:

Animation anim = CurvedAnimation(parent: animController, curve: Curves.linear);

2.Curve用于设置动画效果:

效果常量:

Curves.linear(匀速的)、decelerate(匀减速)、ease(开始加速,后面减速)、easeIn(开始慢,后面快)、easeOut(开始快,后面慢)、easeInOut(开始慢,然后加速,最后再减速)等

自定义Curve:

class MyCurve extends Curve {
  @override
  double transform(double 原值) {
    return 新值; //自定义规则计算新值
  }
}

3.AnimationController用于控制动画:

AnimationController animController = AnimationController(
  duration: const Duration(milliseconds: 动画时长),
  lowerBound: 开始值,  //默认为0.0,默认范围[0.0,1.0]
  upperBound: 结束值,  //默认为1.0,默认范围[0.0,1.0]
  vsync: this,
);
...
animController.forward();   //开始执行动画
animController.stop();      //停止动画
animController.reverse();   //反向播放动画

4.Tween用于生成不同范围或数据类型的动画值(默认范围[0.0,1.0]):

Tween t = Tween<double>(begin: 开始值, end: 结束值);   //数值过渡
Tween t = ColorTween(begin: 开始颜色, end: 结束颜色);  //颜色值过渡
...
Animation<double> anim1 = t.animate(animController);   //Tween传入AnimationController,生成Animation
Animation<double> anim2 = t.animate(anim);             //Tween包装原有Animation,生成新的Animation

5.Ticker处理当前页在后台时停止动画,实现以下类之一即可:

with SingleTickerProviderStateMixin    //适合1个AnimationController
with TickerProviderStateMixin          //适合多个AnimationController

6.线性插值lerp函数(图像是一条直线):

//a 为起始颜色,b为终止颜色,t为当前动画的进度[0,1]
Color.lerp(开始颜色, 结束颜色, 动画进度值);   //进度值为默认[0-1]
Size.lerp(开始大小, 结束大小, 动画进度值);
Rect.lerp(开始大小, 结束大小, 动画进度值);
Offset.lerp(开始偏移值, 结束偏移值, 动画进度值);
Decoration.lerp(开始装饰, 结束装饰, 动画进度值);
...
Tween t = Tween<double>(begin: 开始值, end: 结束值);
t.lerp(动画进度值);
...

二、动画实现:

1.Animation+AnimationController+Curve+Tween实现补间动画(效果类似Tween动画):

(1)AnimatedBuilder方式(推荐,使用AnimatedBuilder包装要动画的Widget,省去..addListener):

class _PageState extends State<AnimationPage> with SingleTickerProviderStateMixin { //with TickerProviderStateMixin适合多个AnimationController
  late Animation<double> animation;  //用于保存动画的过渡值和状态
  late AnimationController animController;   //动画控制类
  @override
  initState() {
    super.initState();
    animController = AnimationController(duration: const Duration(milliseconds: 5000), vsync: this); //创建动画控制类
    animation = CurvedAnimation(parent: animController, curve: Curves.easeInOut);                    //(非必须,不添加时为匀速)添加另外动画效果
    animation = Tween(begin: 0.0, end: 300.0).animate(animation);                                    //Tween将值从0-300,包装带动画效果的Animation,生成新的Animation
    animation.addStatusListener((status) { //监听动画状态改变事件,AnimationStatus.forward为开始、completed为结束、reverse为反向、dismissed为终止
      //...
    });
  }
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ElevatedButton(child: Text("开始动画"),
            onPressed: () {
              animController.forward(); //开始执行动画
            }),
      AnimatedBuilder( //使用AnimatedBuilder包装要动画的Widget
          animation: animController,
          builder: (BuildContext ctx, child) {
            return Container(width: animation.value, height: animation.value, color: Colors.blue);  //通过animation.value值的改变产生动画效果
          })
      ]);
  }
  @override
  dispose() {
    animController.dispose(); //释放动画资源
    super.dispose();
  }
}

(2)AnimatedWidget方式(自定义类继承AnimatedWidget,包装要执行动画的Widget,省去..addListener):

class ... { //同方式1
  ...  //同方式1
  @override
  initState() {
    ...  //同方式1
    animation = Tween(...).animate(animation);  //省略..addListener方法
  }
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ...,  //同方式1
        CustomImage(animation: animation)  //使用自定义AnimatedWidge
      ]);
  }
  ...  //同方式1
}
class CustomImage extends AnimatedWidget {
  const CustomImage({Key? key, required Animation<double> animation}) : super(key: key, listenable: animation);
  @override
  Widget build(BuildContext context) {
    final animation = listenable as Animation<double>;  //获取Animation
    return Image.asset("images/header.png", width: animation.value, height: animation.value);  //通过animation.value值的改变产生动画效果
  }
}

(3)方式3(最不推荐,手动添加..addListener执行setState):

class ... { //同方式1
  ...  //同方式1
  @override
  initState() {
    ...  //同方式1
    animation = Tween(...).animate(animation) //同方式1
      ..addListener(() {    //需要添加..addListener方法监听每一帧的回调事件
        setState(() => {}); //更新UI状态
      });
  }
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ...,  //同方式1
        Image.asset("images/header.png", width: animation.value, height: animation.value)  //通过animation.value值的改变产生动画效果
      ]);
  }
  ...  //同方式1
}

(4)系统预置过渡类:

FadeTransition(opacity: animation, child: Widget类());   //渐隐渐显过渡
ScaleTransition(scale: animation, child: Widget类());    //放大缩小过渡
SizeTransition(sizeFactor: animation, child: Widget类());//位移过渡

2.实现页面跳转动画:

系统自带页面跳转动画类:

MaterialPageRoute:与系统页面保持一致的页面跳转动画
CupertinoPageRoute:iOS风格的页面跳转动画

(1)PageRouteBuilder实现页面跳转动画:

Navigator.push(context,
    PageRouteBuilder(  //PageRouteBuilder实现自定义页面跳转动画
      transitionDuration: Duration(milliseconds: 跳转过渡时长),
      pageBuilder: (BuildContext context, Animation<double> animation, Animation secondaryAnimation) {
        return FadeTransition(opacity: animation, child: 页面2()); //此处根据不同过渡类实现不同页面跳转效果
      }
    ));

(2)自定义PageRoute类实现页面跳转动画:

class MyPageRoute extends PageRoute {//自定义PageRoute类,修改过渡动画
  MyPageRoute({
    required this.builder,
    this.transitionDuration = const Duration(milliseconds: 1000),  //过渡时长
    this.maintainState = true,
    this.barrierLabel = "",
    this.barrierColor = Colors.blue, //动画切换时页面周边的颜色
  });
  @override
  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) => builder(context);
  @override
  Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
    return ScaleTransition(scale: animation, child: builder(context));    //此处配置动画过渡类
  }
  final WidgetBuilder builder;
  @override
  final Duration transitionDuration;
  @override
  final bool maintainState;
  @override
  final String barrierLabel;
  @override
  final Color barrierColor;
}
...
Navigator.push(context, MyPageRoute(builder: (context) { //使用自定义PageRoute类实现页面跳转动画
  return Page2();
}));

3.Hero实现飞行动画:
实现视觉效果:第1页中指定Widget飞到另1个页面中指定位置

(1)页面1实现(将待飞行Widget用Hero包装):

class HeroPage1 extends StatelessWidget {//页面1
  const HeroPage1({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: const Text("页面1标题")),
        body: Row(
          children: <Widget>[
            Hero( //将child的Widget执行飞行动画
                tag: "Hero-Tag", //Hero动画唯一标记,切换的两个页面Hero的tag要一致
                child: Image.asset( "images/header.png",)   //待飞行的Widget
            ),
            ElevatedButton(  //只是用来点击触发动画用
                child: Text("点击触发飞行动画"),
                onPressed: () {
                  Navigator.push(context, PageRouteBuilder( //页面跳转
                    pageBuilder: (BuildContext context, animation, secondaryAnimation) {
                      return FadeTransition(opacity: animation, child: HeroPage2());  //FadeTransition为渐隐渐入过渡动效,HeroPage2为第2个页面
                    },
                  ));
                })
          ],
        ));
  }
}

(2)页面2实现(将飞行终点Widget用Hero包装):

class HeroPage2 extends StatelessWidget {//页面2
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: const Text("页面2标题")),
        body: Hero(
          tag: "Hero-Tag", //Hero动画唯一标记,切换的两个页面Hero的tag要一致
          child: Image.asset("images/photo.jpg") //飞行停止后的最终Widget
        ));
  }
}

4.交织动画(动画组,多个动画同时进行):

说明:
需要多个Animation
必须由一个AnimationController控制所有的Animation
可以给每个Animation指定Interval

Interval( //动画执行时长截取类
  0.2, 0.8, //对动画原时长截取,例:动画时长为5秒时,只在1秒-4秒之间执行动画,0-1秒与4-5秒不执行
  curve: Curves.easeInSine,
)

(1)多个Animation封装到Widget中,由外部传入AnimationController进行控制:

class AnimGroupWidget extends StatelessWidget { //封装了多个动画的Widget
  AnimGroupWidget({Key? key, required this.animController}) : super(key: key);
  late final AnimationController animController;    //由外部传入1个AnimationController,控制所有动画
  late final Animation<double> anim1 = Tween<double>(begin: 0, end: 100).animate(animController);  //动画1,此处为数值渐变动画
  late final Animation<Color?> anim2 = ColorTween(begin: Colors.green, end: Colors.red).animate(animController);  //动画2,此处为颜色渐变动画
  late final Animation<double> anim3 = Tween<double>(begin: 0, end: 100).animate(CurvedAnimation( //动画3,此处使用了Interval
    parent: animController,
    curve: const Interval( //动画执行时长
      0.2, 0.8, //对动画原时长截取,例:动画时长为5秒时,只在1秒-4秒之间执行动画,0-1秒与4-5秒不执行
      curve: Curves.easeInSine,
    ),
  ));
  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(animation: animController,
      builder: (BuildContext ctx, child) {
        return Container(width: anim1.value, height: anim1.value, color: anim2.value); //Container根据动画1+动画2,改变背景色与宽高
      }
    );
  }
}

(2)使用动画组Widget,并控制开始动画:

class _PageState extends State<Page1> with TickerProviderStateMixin {
  late AnimationController animController; //控制多个动画
  @override
  void initState() {
    super.initState();
    animController = AnimationController(duration: const Duration(milliseconds: 5000), vsync: this);
  }
  @override
  Widget build(BuildContext context) {
    return Column(children: [
        ElevatedButton(
          onPressed: () {
            animController.forward(); //开始动画(多个动画同时进行)
          },
          child: Text("点击开始多个动画"),
        ),
        AnimGroupWidget(animController: animController) //AnimGroupWidget封装了多个动画的Widget,传入统一的AnimationController
      ],
    );
  }
}

5.AnimatedSwitcher实现Widget切换动画:

late List<T> layoutList;
...
AnimatedSwitcher( //AnimatedSwitcher也是Widget
  reverseDuration: Duration(milliseconds: 2000),  //旧child隐藏的动画时长
  duration: Duration(milliseconds: 2000),         //新child显示的动画时长
  switchOutCurve: Curves.easeInOut,               //旧child隐藏的动画效果
  switchInCurve: Curves.easeInOut,                //新child显示的动画效果
  transitionBuilder: (Widget child, Animation<double> animation) { //动画构造器
    return FadeTransition(child: child, opacity: animation);  //过渡类
  },
  child: layoutList[position],  //需要动画的布局,同个布局名要设置不同的key才有动画效果
)

6.Widget属性改变时实现过渡动画:

(1)AnimatedPadding,padding值变化时执行过渡动画:

double padding = 0;  //旧padding值
...
AnimatedPadding(
    duration: Duration(milliseconds: 2000),
    padding: EdgeInsets.all(padding),   //此值改变时执行过渡动画
    child: Text("AnimatedPadding测试")
)
...
setState(() {
  padding = 10;   //设为新padding值时执行过渡动画
});

(2)AnimatedPositioned(与Stack联合使用),位置或大小变化时执行过渡动画:

double position = 0;  //旧位置
...
Stack(
  children: <Widget>[
    AnimatedPositioned(
        duration: Duration(milliseconds: 2000),
        left: position,  //此值改变时执行过渡动画
        top: position,   //此值改变时执行过渡动画
        child: Text("AnimatedPositioned测试")
    )
  ]
)
...
setState(() {
  position = 40;  //设为新位置时执行过渡动画
});

(3)AnimatedAlign,alignment变化时执行过渡动画:

Alignment align = Alignment.topLeft;  //旧对齐方式
...
AnimatedAlign(
  duration: Duration(milliseconds: 2000),
  alignment: align,     //此值改变时执行过渡动画
  child: Text("AnimatedAlign测试"),
)
...
setState(() {
  align = Alignment.bottomRight; //设为新对齐方式时触发动画
});

(4)AnimatedOpacity,透明度opacity发生变化时执行过渡动画:

double opacity = 0.5;  //旧透明度
...
AnimatedOpacity(
  duration: Duration(milliseconds: 2000),
  opacity: scale,   //此值改变时执行过渡动画
  child: Text("AnimatedOpacity测试")
)
...
setState(() {
  opacity = 0.5; //设为新透明度时触发动画
});

(5)AnimatedContainer,属性值变化时执行过渡动画:

double size = 50;  //旧宽高
...
AnimatedContainer(
  duration: Duration(milliseconds: 2000),
  width: size,        //此值改变时执行过渡动画
  height: size,       //此值改变时执行过渡动画
  child: Text("AnimatedContainer测试")
)
...
setState(() {
  size = 100; //设为新宽高时触发动画
});

(6)AnimatedDefaultTextStyle,字体样式发生变化时执行过渡动画:

TextStyle style = const TextStyle(fontSize: 14, color: Colors.red);  //旧样式
...
AnimatedDefaultTextStyle(
    duration: Duration(milliseconds: 2000),
    style: style,        //此值改变时执行过渡动画
    child: Text("AnimatedDefaultTextStyle测试")),
...
setState(() {
  style = const TextStyle(fontSize: 16, color: Colors.yellow); //设为新样式时触发动画
});


 

猜你喜欢

转载自blog.csdn.net/a526001650a/article/details/127654284