美文网首页
Flutter 之动画

Flutter 之动画

作者: Abner_XuanYuan | 来源:发表于2023-10-17 00:09 被阅读0次

    1、原理

    1、动画形成

    在任何系统的 UI 框架中,动画实现的原理都是相同的,即:在一段时间内,快速地多次改变UI外观,由于人眼会产生视觉暂留,所以最终看到的就是一个“连续”的动画,这和电影的原理是一样的。我们将 UI 的一次改变称为一个动画帧,对应一次屏幕刷新,而决定动画流畅度的一个重要指标就是帧率 FPS(Frame Per Second)即每秒的动画帧数。很明显,帧率越高则动画就会越流畅!一般情况下,对于人眼来说,动画帧率超过 16 FPS,就基本能看了,超过 32 FPS 就会感觉相对平滑,而超过 32FPS,大多数人基本上就感受不到差别了。由于动画的每一帧都是要改变 UI 输出,所以在一个时间段内连续的改变 UI 输出是比较耗资源的,对设备的软硬件系统要求都较高,所以在 UI 系统中,动画的平均帧率是重要的性能指标,而在 Flutter 中,理想情况下是可以实现 60FPS 的,这和原生应用能达到的帧率是基本是持平的。

    2、Flutter 动画分类

    FLutter 中的动画主要分为:隐式动画、显式动画、自定义隐式动画、自定义显式动画和 Hero 动画。

    2、隐式动画

    通过几行代码就可以实现隐式动画,由于隐式动画背后的实现原理和繁琐的操作细节都被隐去了,所以叫隐式动画。FLutter 中提供的 AnimatedContainer、AnimatedPadding、AnimatedPositioned、AnimatedOpacity、AnimatedDefaultTextStyle、AnimatedSwitcher 都属于隐式动画。隐式动画中可以通过 duration 配置动画时长、可以通过 Curve (曲线)来配置动画过程。

    1、AnimatedContainer

    AnimatedContainer 的属性和 Container 属性基本是一样的,当 AnimatedContainer 属性改变的时候就会触发动画。

    属性

    (new) AnimatedContainer AnimatedContainer({
      Key? key,
      AlignmentGeometry? alignment,   //子元素相对于容器的对齐方式
      EdgeInsetsGeometry? padding,    //子元素的内边距
      Color? color,    //容器背景颜色(decoration 也能设置背景色,两个不要同时使用)
      Decoration? decoration,    //容器的边框修饰
      Decoration? foregroundDecoration,    //容器的前景边框修饰(使用时会挡住 color 或 decoration 的颜色)
      double? width,    //容器的宽
      double? height,    //容器的高
      BoxConstraints? constraints,    //容器的大小约束,可以指定最小宽高、最大宽高,width 和 height 即使设置更大的宽高也不会有效果.
      EdgeInsetsGeometry? margin,    //容器的外边距
      Matrix4? transform,    //容器的 Matrix 变换
      AlignmentGeometry? transformAlignment,    //transform 不为空时有效,转换的对齐方式,可以理解为起点位置
      Widget? child,
      Clip clipBehavior = Clip.none,    //在decoration不为空的情况下,才有效果,指定剪切的模式,
      Curve curve = Curves.linear,    //动画运用的曲线
      required Duration duration,    //动画时长
      void Function()? onEnd,    //动画执行结束的回调
    })
    

    使用

    class AnimatedContainerPage extends StatefulWidget {
      const AnimatedContainerPage({super.key});
    
      @override
      State<AnimatedContainerPage> createState() => _AnimatedContainerPageState();
    }
    
    class _AnimatedContainerPageState extends State<AnimatedContainerPage> {
      bool flag = false;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              setState(() {
                flag = !flag;
              });
            },
            child: const Icon(Icons.animation),
          ),
          appBar: AppBar(
            title: const Text('AnimatedContainer'),
          ),
          body: Center(
            child: AnimatedContainer(
              duration: const Duration(milliseconds: 250),
              width: flag ? 100 : 300,
              height: flag ? 100 : 300,
              color: Colors.green,
            ),
          ),
        );
      }
    }
    
    2、AnimatedPadding

    属性

    (new) AnimatedPadding AnimatedPadding({
      Key? key,
      required EdgeInsetsGeometry padding,     //子元素的内边距
      Widget? child,
      Curve curve = Curves.linear,     //动画运用的曲线
      required Duration duration,    //动画时长
      void Function()? onEnd,     //动画执行结束的回调
    })
    

    使用

    class AnimatedPaddingPage extends StatefulWidget {
      const AnimatedPaddingPage({super.key});
    
      @override
      State<AnimatedPaddingPage> createState() => _AnimatedPaddingPageState();
    }
    
    class _AnimatedPaddingPageState extends State<AnimatedPaddingPage> {
      bool flag = false;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          floatingActionButton: FloatingActionButton(
              child: const Icon(Icons.change_circle),
              onPressed: () {
                setState(() {
                  flag = !flag;
                });
              }),
          appBar: AppBar(
            title: const Text('AnimatedPadding'),
          ),
          body: AnimatedPadding(
            duration: const Duration(milliseconds: 3000),
            padding: EdgeInsets.fromLTRB(10, flag ? 10 : 500, 0, 0),
            curve: Curves.bounceInOut,
            child: Container(
              width: 100,
              height: 100,
              color: Colors.red,
            ),
          ),
        );
      }
    }
    
    3、AnimatedPositioned

    AnimatedPositioned 是 Stack 组件中的 Positioned 的动画替换组件。可以通过 AnimatedPositioned 实现组件在 Stack 组件的位置,从而实现相对 Stack 组件的移动效果。需要注意的是横向参数(left、right 和 width)、纵向参数(top、bottom 和 height)只能从3个里面选2个设置,否则会导致布局冲突。
    属性

    AnimatedPositioned AnimatedPositioned({
      Key? key,
      required Widget child,
      double? left,
      double? top,
      double? right,
      double? bottom,
      double? width,
      double? height,
      Curve curve = Curves.linear,    //动画运用的曲线
      required Duration duration,    //动画时长
      void Function()? onEnd,    //动画结束后回调
    })
    

    使用

    class AnimatedPositionedPage extends StatefulWidget {
      const AnimatedPositionedPage({super.key});
    
      @override
      State<AnimatedPositionedPage> createState() => _AnimatedPositionedPageState();
    }
    
    class _AnimatedPositionedPageState extends State<AnimatedPositionedPage> {
      bool flag = false;
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('AnimatedPositioned'),
          ),
          body: Stack(
            children: [
              AnimatedPositioned(
                duration: const Duration(seconds: 1),
                curve: Curves.easeInOut,
                top: flag ? 10 : 500,
                left: flag ? 10 : 300,
                child: Container(
                  width: 60,
                  height: 60,
                  color: Colors.green,
                ),
              ),
              Align(
                alignment: const Alignment(0, 0.8),
                child: ElevatedButton(
                    onPressed: () {
                      setState(() {
                        flag = !flag;
                      });
                    },
                    child: const Text("Transform")),
              ),
            ],
          ),
        );
      }
    }
    
    4、AnimatedOpacity

    属性

    
    (new) AnimatedOpacity AnimatedOpacity({
      Key? key,
      Widget? child,
      required double opacity,    //透明度 0~1
      Curve curve = Curves.linear,    //动画运用的曲线
      required Duration duration,    //动画时长
      void Function()? onEnd,    //动画结束后回调
    //是否总是包含语义信息,默认是 false。这个主要是用于辅助访问的,如果是 true,则不管透明度是多少,都会显示语义信息(可以辅助朗读),这对于视障人员来说会更友好。
      bool alwaysIncludeSemantics = false,    
    })
    

    使用

    class AnimatedOpacityPage extends StatefulWidget {
      const AnimatedOpacityPage({super.key});
    
      @override
      State<AnimatedOpacityPage> createState() => _AnimatedOpacityPageState();
    }
    
    class _AnimatedOpacityPageState extends State<AnimatedOpacityPage> {
      bool flag = false;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              setState(() {
                flag = !flag;
              });
            },
            child: const Icon(Icons.opacity),
          ),
          appBar: AppBar(
            title: const Text('AnimatedOpacity'),
          ),
          body: Center(
            child: AnimatedOpacity(
              opacity: flag ? 0 : 1,
              duration: const Duration(seconds: 3),
              curve: Curves.linear,
              child: Container(
                width: 300,
                height: 300,
                color: Colors.green,
              ),
            ),
          ),
        );
      }
    }
    
    5、AnimatedDefaultTextStyle

    属性

    (new) AnimatedDefaultTextStyle AnimatedDefaultTextStyle({
      Key? key,
      required Widget child,
      required TextStyle style,    //子元素的样式,用于动画变化
      TextAlign? textAlign,    //如果文本超过1行时,所有换行的字体的对齐方式,可以是左对齐、右对齐
      bool softWrap = true,    //文本是否应该在软换行符处换行,软换行和硬换行是word用法,具体自阅
      TextOverflow overflow = TextOverflow.clip,    //超过文本行数区域的裁剪方式
      int? maxLines,    //文本最大行数,默认是1
      TextWidthBasis textWidthBasis = TextWidthBasis.parent,    //Text 宽度类型(与父 widget 同宽或最小宽度)
      TextHeightBehavior? textHeightBehavior,    //Text 行高状态
      Curve curve = Curves.linear,    //动画样式
      required Duration duration,    //动画时长
      void Function()? onEnd,    //动画结束后回调
    })
    

    使用

    class AnimatedDefaultTextStylePage extends StatefulWidget {
      const AnimatedDefaultTextStylePage({super.key});
    
      @override
      State<AnimatedDefaultTextStylePage> createState() =>
          _AnimatedDefaultTextStylePageState();
    }
    
    class _AnimatedDefaultTextStylePageState
        extends State<AnimatedDefaultTextStylePage> {
      bool flag = false;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          floatingActionButton: FloatingActionButton(
              child: const Icon(Icons.opacity),
              onPressed: () {
                setState(() {
                  flag = !flag;
                });
              }),
          appBar: AppBar(
            title: const Text('AnimatedDefaultTextStylePage'),
          ),
          body: Center(
            child: Container(
              width: 300,
              height: 300,
              alignment: Alignment.center,
              color: Colors.green,
              child: AnimatedDefaultTextStyle(
                  style: TextStyle(fontSize: flag ? 15 : 20),
                  duration: const Duration(milliseconds: 300),
                  child: const Text("AnimatedDefaultTextStyle")),
            ),
          ),
        );
      }
    }
    
    6、AnimatedSwitcher(切换)

    AnimatedContainer、AnimatedPadding、AnimatedPositioned、AnimatedOpacity、
    AnimatedDefaultTextStyle 都是在属性改变的时候执行动画,AnimatedSwitcher 则是在子元素改变的时候执行动画。相比上面的动画组件 AnimatedSwitcher 多了 transitionBuilder 参数,可以在 transitionBuilder 中自定义动画。

    属性

    (new) AnimatedSwitcher AnimatedSwitcher({
      Key? key,
      Widget? child,
      required Duration duration,    // 新child显示动画时长
      Duration? reverseDuration,     // 旧child隐藏的动画时长
      Curve switchInCurve = Curves.linear,    // 新child显示的动画曲线
      Curve switchOutCurve = Curves.linear,    // 旧child隐藏的动画曲线
      Widget Function(Widget, Animation<double>) transitionBuilder = AnimatedSwitcher.defaultTransitionBuilder,    // 动画构建器
      Widget Function(Widget?, List<Widget>) layoutBuilder = AnimatedSwitcher.defaultLayoutBuilder,    //布局构建器
    })
    

    使用

    class AnimatedSwitcherPage extends StatefulWidget {
      const AnimatedSwitcherPage({super.key});
    
      @override
      State<AnimatedSwitcherPage> createState() => _AnimatedSwitcherPageState();
    }
    
    class _AnimatedSwitcherPageState extends State<AnimatedSwitcherPage> {
      bool flag = true;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          floatingActionButton: FloatingActionButton(
              child: const Icon(Icons.opacity),
              onPressed: () {
                setState(() {
                  flag = !flag;
                });
              }),
          appBar: AppBar(
            title: const Text('AnimatedSwitcherPage'),
          ),
          body: Center(
            child: Container(
              width: 300,
              height: 180,
              alignment: Alignment.center,
              color: Colors.green,
              child: AnimatedSwitcher(
                duration: const Duration(seconds: 3),
                child: flag
                    ? const CircularProgressIndicator()
                    : Image.network(
                        "https://www.itying.com/images/flutter/2.png",
                        fit: BoxFit.cover,
                      ),
              ),
            ),
          ),
        );
      }
    }
    
    //transitionBuilder 自定义动画
    //在上述代码的 body 中的 AnimatedSwitcher 中添加 transitionBuilder 属性
    body: Center(
            child: Container(
              width: 300,
              height: 180,
              alignment: Alignment.center,
              color: Colors.green,
              child: AnimatedSwitcher(
                //transitionBuilder 自定义动画效果
                transitionBuilder: (child, animation) {
                  return ScaleTransition(
                    scale: animation,
                    child: FadeTransition(
                      opacity: animation,
                      child: child,
                    ),
                  );
                },
                duration: const Duration(seconds: 3),
                child: flag
                    ? const CircularProgressIndicator()
                    : Image.network(
                        "https://www.itying.com/images/flutter/2.png",
                        fit: BoxFit.cover,
                      ),
              ),
            ),
          ),
    
    //transitionBuilder 改变子元素执行动画
    //在上述代码更改的基础上,更改 AnimatedSwitcher 中添加  child 属性
    body: Center(
            child: Container(
              width: 300,
              height: 180,
              alignment: Alignment.center,
              color: Colors.green,
              child: AnimatedSwitcher(
                //transitionBuilder 自定义动画效果
                transitionBuilder: (child, animation) {
                  return ScaleTransition(
                    scale: animation,
                    child: FadeTransition(
                      opacity: animation,
                      child: child,
                    ),
                  );
                },
                duration: const Duration(seconds: 3),
                child: Text(
                  key: UniqueKey(),
                  flag ? "你好 Flutter" : "你好啊!",
                  style: const TextStyle(fontSize: 30),
                ),
              ),
            ),
          ),
    

    3、显示动画

    常见的显式动画有 RotationTransition(旋转)、FadeTransition(透明度)、ScaleTransition(缩放)、SlideTransition(移动)、AnimatedIcon(改变常见图标)。在显示动画中开发者需要创建一个 AnimationController,通过 AnimationController 控制动画的开始、暂停、重置、跳转、倒播等。

    1、RotationTransition

    属性

    (new) RotationTransition RotationTransition({
      Key? key,
      required Animation<double> turns,    //动画控制器
      Alignment alignment = Alignment.center,    //设置动画的旋转中心
      FilterQuality? filterQuality,    //在进行图像变换的过程中,图像的取样质量
      Widget? child,    //将要执行动画的子view
    })
    

    使用

    class RotationTransitionPage extends StatefulWidget {
      const RotationTransitionPage({super.key});
    
      @override
      State<RotationTransitionPage> createState() => _RotationTransitionPageState();
    }
    
    class _RotationTransitionPageState extends State<RotationTransitionPage>
        with SingleTickerProviderStateMixin {
      late AnimationController _controller;
      @override
      void initState() {
        super.initState();
        //Vsync 机制可以理解为是显卡与显示器的通信桥梁,显卡在渲染每一帧之前会等待垂直同步信号,只有显示器完成了一次刷新时,发出垂直同步信号,
        //显卡才会渲染下一帧,确保刷新率和帧率保持同步,以达到供需平衡的效果,防止卡顿现象。
        _controller =
            AnimationController(vsync: this, duration: const Duration(seconds: 1));
      }
    
      @override
      void dispose() {
        super.dispose();
    
        _controller.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('RotationTransition'),
          ),
          body: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              RotationTransition(
                turns: _controller,
                child: const FlutterLogo(
                  size: 100,
                ),
              ),
              const SizedBox(
                height: 50,
              ),
              Padding(
                padding: const EdgeInsets.fromLTRB(10, 0, 0, 0),
                child: Wrap(
                  spacing: 10,
                  alignment: WrapAlignment.center,
                  children: [
                    ElevatedButton(
                        onPressed: () {
                          _controller.forward();
                        },
                        child: const Text("正序播放")),
                    ElevatedButton(
                        onPressed: () {
                          _controller.reverse();
                        },
                        child: const Text("倒序播放")),
                    ElevatedButton(
                        onPressed: () {
                          _controller.stop();
                        },
                        child: const Text("停止播放")),
                    ElevatedButton(
                        onPressed: () {
                          _controller.reset();
                        },
                        child: const Text("重置")),
                    ElevatedButton(
                        onPressed: () {
                          _controller.repeat();
                        },
                        child: const Text("重复播放"))
                  ],
                ),
              )
            ],
          ),
        );
      }
    }
    

    lowerBound & upperBound
    AnimationController 用于控制动画,它包含动画的启动 forward() 、停止 stop() 、反向播放reverse() 等方法。 AnimationController 会在动画的每一帧生成一个新的值。默认情况下, AnimationController 在给定的时间段内线性的生成从 0.0 到1.0(默认区间)的数字 ,我们也可以通过 lowerBound 和 upperBound 来修改 AnimationController 生成数字的区间。

    //在上述代码中更改 initState() 方法如下:
    void initState() {
        super.initState();
        //Vsync 机制可以理解为是显卡与显示器的通信桥梁,显卡在渲染每一帧之前会等待垂直同步信号,只有显示器完成了一次刷新时,发出垂直同步信号,
        //显卡才会渲染下一帧,确保刷新率和帧率保持同步,以达到供需平衡的效果,防止卡顿现象。
        _controller = AnimationController(
          vsync: this,
          duration: const Duration(seconds: 1),
    
          //第三圈到第五圈
          lowerBound: 3,
          upperBound: 5,
        );
    
        _controller.addListener(() {
          print(_controller.value);
        });
      }
    
    2、FadeTransition

    属性

    (new) FadeTransition FadeTransition({
      Key? key,
      required Animation<double> opacity,    //组件的透明度
      //是否总是包含语义信息,默认是 false。这个主要是用于辅助访问的,如果是 true,则不管透明度是多少,都会显示语义信息(可以辅助朗读),这对于视障人员来说会更友好。
      bool alwaysIncludeSemantics = false,
      Widget? child,
    })
    

    使用

    class FadeTransitionPage extends StatefulWidget {
      const FadeTransitionPage({super.key});
    
      @override
      State<FadeTransitionPage> createState() => _FadeTransitionPageState();
    }
    
    class _FadeTransitionPageState extends State<FadeTransitionPage>
        with SingleTickerProviderStateMixin {
      late AnimationController _controller;
    
      @override
      void initState() {
        super.initState();
    
        //Vsync 机制可以理解为是显卡与显示器的通信桥梁,显卡在渲染每一帧之前会等待垂直同步信号,只有显示器完成了一次刷新时,
        //发出垂直同步信号,显卡才会渲染下一帧,确保刷新率和帧率保持同步,以达到供需平衡的效果,防止卡顿现象。
        _controller =
            AnimationController(vsync: this, duration: const Duration(seconds: 3));
      }
    
      @override
      void dispose() {
        super.dispose();
    
        _controller.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('FadeTransition'),
          ),
          body: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              FadeTransition(
                opacity: _controller,
                child: const FlutterLogo(
                  size: 50,
                ),
              ),
              const SizedBox(
                height: 50,
              ),
              Padding(
                padding: const EdgeInsets.fromLTRB(10, 0, 0, 0),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                    ElevatedButton(
                        onPressed: () {
                          _controller.forward();
                        },
                        child: const Text("正序播放")),
                    ElevatedButton(
                        onPressed: () {
                          _controller.reverse();
                        },
                        child: const Text("倒序播放")),
                    ElevatedButton(
                        onPressed: () {
                          _controller.repeat();
                        },
                        child: const Text("重复播放"))
                  ],
                ),
              ),
            ],
          ),
        );
      }
    }
    
    3、ScaleTransition

    属性

    (new) ScaleTransition ScaleTransition({
      Key? key,
      required Animation<double> scale,    //动画控制器
      Alignment alignment = Alignment.center,    //设置动画的缩放中心
      FilterQuality? filterQuality,     //在进行图像变换的过程中,图像的取样质量
      Widget? child,    //将要执行动画的子view
    })
    

    使用

    class ScaleTransitionPage extends StatefulWidget {
      const ScaleTransitionPage({super.key});
    
      @override
      State<ScaleTransitionPage> createState() => _ScaleTransitionPageState();
    }
    
    class _ScaleTransitionPageState extends State<ScaleTransitionPage>
        with SingleTickerProviderStateMixin {
      late AnimationController _controller;
    
      @override
      void initState() {
        super.initState();
    
        _controller =
            AnimationController(vsync: this, duration: const Duration(seconds: 3));
      }
    
      @override
      void dispose() {
        super.dispose();
    
        _controller.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('ScaleTransitionPage'),
          ),
          body: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              ScaleTransition(
                scale: _controller,
                child: const FlutterLogo(
                  size: 100,
                ),
              ),
              const SizedBox(
                height: 50,
              ),
              Padding(
                padding: const EdgeInsets.fromLTRB(10, 0, 0, 0),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                    ElevatedButton(
                        onPressed: () {
                          _controller.forward();
                        },
                        child: const Text("正序播放")),
                    ElevatedButton(
                        onPressed: () {
                          _controller.reverse();
                        },
                        child: const Text("倒序播放")),
                  ],
                ),
              ),
            ],
          ),
        );
      }
    }
    

    AnimationController 结合 Tween 控制动画
    默认情况下, AnimationController 对象值的范围是[0.0,1.0]。如果我们需要构建 UI 的动画值在不同的范围或不同的数据类型,则可以使用 Tween 来添加映射以生成不同的范围或数据类型的值。

    //上述代码中更改 ScaleTransition 如下:
    ScaleTransition(
                scale: _controller.drive(Tween(begin: 1,end: 2)),
                child: const FlutterLogo(
                  size: 100,
                ),
              ),
    
    4、SlideTransition

    这是一负责平移的显示动画组件,使用时需要通过 position 属性传入一个 Animated 表示位移程度,通常借助 Tween 实现。
    属性

    (new) SlideTransition SlideTransition({
      Key? key,
      required Animation<Offset> position,    //位置偏移系数(如:0.5 表示向右偏移 width 的 50%)
      bool transformHitTests = true,    //默认为 true。作用是否对 pointerEvent 的 position 进行转换
      TextDirection? textDirection,  //文本方向,一般都是从左到右
      Widget? child,
    })
    

    使用

    class SlideTransitionPage extends StatefulWidget {
      const SlideTransitionPage({super.key});
    
      @override
      State<SlideTransitionPage> createState() => _SlideTransitionPageState();
    }
    
    class _SlideTransitionPageState extends State<SlideTransitionPage>
        with SingleTickerProviderStateMixin {
      late AnimationController _controller;
    
      @override
      void initState() {
        super.initState();
        _controller =
            AnimationController(vsync: this, duration: const Duration(seconds: 1));
      }
    
      @override
      void dispose() {
        super.dispose();
    
        _controller.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('SlideTransitionPage'),
          ),
          body: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              SlideTransition(
                position: _controller.drive(Tween(
                    begin: const Offset(-1, -1),
                    //表示实际的位置向右移动自身宽度的1.2倍
                    end: const Offset(0.5, 0.5))),
                child: const FlutterLogo(
                  size: 100,
                ),
              ),
              const SizedBox(
                height: 50,
              ),
              Padding(
                padding: const EdgeInsets.fromLTRB(10, 0, 0, 0),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                    ElevatedButton(
                        onPressed: () {
                          _controller.forward();
                        },
                        child: const Text("正序播放")),
                    ElevatedButton(
                        onPressed: () {
                          _controller.reverse();
                        },
                        child: const Text("倒序播放")),
                  ],
                ),
              ),
            ],
          ),
        );
      }
    }
    

    Tween.animate 驱动动画

    //如上代码更改 SlideTransition 代码块如下
    SlideTransition(
                // position: _controller.drive(Tween(
                //     begin: const Offset(-1, -1),
                //     //表示实际的位置向右移动自身宽度的1.2倍
                //     end: const Offset(0.5, 0.5))),
    
                //Tween.animate 驱动动画
                position:
                    Tween(begin: const Offset(-1, -1), end: const Offset(0.5, 0.5))
                        .animate(_controller),
                child: const FlutterLogo(
                  size: 100,
                ),
              ),
    

    链式操作修改动画效果

    //如上代码更改 SlideTransition 代码块如下
    SlideTransition(
                // position: _controller.drive(Tween(
                //     begin: const Offset(-1, -1),
                //     //表示实际的位置向右移动自身宽度的1.2倍
                //     end: const Offset(0.5, 0.5))),
                
                // Tween.animate 驱动动画
                // position:
                //     Tween(begin: const Offset(-1, -1), end: const Offset(0.5, 0.5))
                //         .animate(_controller),
    
                //链式操作修改动画效果
                position:
                    Tween(begin: const Offset(0, -1), end: const Offset(0, 0.8))
                        .chain(CurveTween(curve: Curves.bounceIn))
                        .animate(_controller),
                child: const FlutterLogo(
                  size: 100,
                ),
              ),
    

    链式操作修改动动画执行时间

    //如上代码更改 SlideTransition 代码块如下
    SlideTransition(
                // position: _controller.drive(Tween(
                //     begin: const Offset(-1, -1),
                //     //表示实际的位置向右移动自身宽度的1.2倍
                //     end: const Offset(0.5, 0.5))),
    
                //Tween.animate 驱动动画
                // position:
                //     Tween(begin: const Offset(-1, -1), end: const Offset(0.5, 0.5))
                //         .animate(_controller),
    
                //链式操作修改动画效果
                // position:
                //     Tween(begin: const Offset(0, -1), end: const Offset(0, 0.8))
                //         .chain(CurveTween(curve: Curves.bounceIn))
                //         .animate(_controller),
    
                // 链式操作修改动动画执行时间
                position:
                    Tween(begin: const Offset(0, -1), end: const Offset(0, 0.8))
                        .chain(CurveTween(curve: Curves.bounceIn))
                        //最后百分之 30 的时间完成动画
                        .chain(CurveTween(curve: const Interval(0.7, 1.0)))
                        .animate(_controller),
                child: const FlutterLogo(
                  size: 100,
                ),
              ),
    
    5、AnimatedIcon

    AnimatedIcon 顾名思义,是一个用于提供动画图标的组件,它的名字虽然是以 Animated 开头,但是他是一个显式动画组件,需要通过 progress 属性传入动画控制器,另外需要由 Icon 属性传入动画图标数据。

    属性

    (new) AnimatedIcon AnimatedIcon({
      Key? key,
      required AnimatedIconData icon,    //图标
      required Animation<double> progress,    //动画的进度,值0-1
      Color? color,    //icon 的颜色
      double? size,    //icon的大小
      String? semanticLabel,    //语义标签,不在UI中显示,在辅助功能模式下有用
      TextDirection? textDirection,    //图标的方向
    })
    

    方法

    class AnimatedIconPage extends StatefulWidget {
      const AnimatedIconPage({super.key});
    
      @override
      State<AnimatedIconPage> createState() => _AnimatedIconPageState();
    }
    
    class _AnimatedIconPageState extends State<AnimatedIconPage>
        with SingleTickerProviderStateMixin {
      late AnimationController _controller;
    
      @override
      void initState() {
        super.initState();
        _controller =
            AnimationController(vsync: this, duration: const Duration(seconds: 1));
      }
    
      @override
      void dispose() {
        super.dispose();
    
        _controller.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              _controller.forward();
            },
            child: const Icon(Icons.add),
          ),
          appBar: AppBar(
            title: const Text('AnimatedIcon'),
          ),
          body: Center(
            child: AnimatedIcon(
              icon: AnimatedIcons.menu_close,
              progress: _controller,
              size: 50,
            ),
          ),
        );
      }
    }
    

    4、交错动画

    class SlideTransition2Page extends StatefulWidget {
      const SlideTransition2Page({super.key});
    
      @override
      State<SlideTransition2Page> createState() => _SlideTransition2PageState();
    }
    
    class _SlideTransition2PageState extends State<SlideTransition2Page>
        with SingleTickerProviderStateMixin {
      bool flag = true;
      late AnimationController _controller;
      @override
      void initState() {
        super.initState();
    
        _controller =
            AnimationController(vsync: this, duration: const Duration(seconds: 6))
              ..repeat(reverse: true);
      }
    
      @override
      void dispose() {
        super.dispose();
    
        _controller.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              flag ? _controller.forward() : _controller.reverse();
              flag = !flag;
            },
            child: const Icon(Icons.refresh),
          ),
          appBar: AppBar(
            title: const Text('交错动画'),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                SlideringBox(
                    controller: _controller,
                    color: Colors.blue[200],
                    curve: const Interval(0, 0.2)),
                SlideringBox(
                    controller: _controller,
                    color: Colors.blue[400],
                    curve: const Interval(0.2, 0.4)),
                SlideringBox(
                    controller: _controller,
                    color: Colors.blue[600],
                    curve: const Interval(0.4, 0.6)),
                SlideringBox(
                    controller: _controller,
                    color: Colors.blue[800],
                    curve: const Interval(0.6, 0.8)),
                SlideringBox(
                    controller: _controller,
                    color: Colors.blue[900],
                    curve: const Interval(0.8, 1.0)),
              ],
            ),
          ),
        );
      }
    }
    
    class SlideringBox extends StatelessWidget {
      final AnimationController controller;
      final Color? color;
      final Curve curve;
      const SlideringBox(
          {super.key,
          required this.controller,
          required this.color,
          required this.curve});
    
      @override
      Widget build(BuildContext context) {
        return SlideTransition(
          position: Tween(begin: const Offset(0, 0), end: const Offset(0.7, 0))
              .chain(CurveTween(curve: Curves.bounceIn)
                  .chain(CurveTween(curve: curve)))
              .animate(controller),
          child: Container(
            width: 220,
            height: 60,
            color: color,
          ),
        );
      }
    }
    

    5、自定义动画

    1、TweenAnimationBuilder 自定义隐式动画

    每当 Tween 的 end 发生变化的时候就会触发动画。

    属性

    (new) TweenAnimationBuilder<Object?> TweenAnimationBuilder({
      Key? key,
      required Tween<Object?> tween,    //动画值
      required Duration duration,    //动画时长
      Curve curve = Curves.linear,    //动效
      //有三个参数,
      //第一个是BuildContext,
      //第二个是value用于接收上面两个参数定义的动画时间与动画值(类型取决于自己要做动画的数据类型),
      //第三个是TweenAnimationBuilder的子组件,用于优化;
      required Widget Function(BuildContext, Object?, Widget?) builder,
      void Function()? onEnd,  //动画结束后的回调
      Widget? child,
    })
    

    使用

    //大小变化
    class TweenAnimationBuilderPage extends StatefulWidget {
      const TweenAnimationBuilderPage({super.key});
    
      @override
      State<TweenAnimationBuilderPage> createState() =>
          _TweenAnimationBuilderPageState();
    }
    
    class _TweenAnimationBuilderPageState extends State<TweenAnimationBuilderPage> {
      bool flag = true;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          floatingActionButton: FloatingActionButton(
              child: const Icon(Icons.refresh_sharp),
              onPressed: () {
                setState(() {
                  flag = !flag;
                });
              }),
          appBar: AppBar(
            title: const Text('TweenAnimationBuilder'),
          ),
          body: Center(
            child: TweenAnimationBuilder(
                tween: Tween(
                    begin: 100.0, end: flag ? 100.0 : 200.0), //此处的数据必须为 double 类型数据
                duration: const Duration(milliseconds: 100),
                builder: ((context, value, child) {
                  return Icon(
                    Icons.star,
                    size: value.toDouble(),
                  );
                })),
          ),
        );
      }
    }
    
    //透明度变化
    //修改上述 body 中的代码,如下
          //大小变化
          // body: Center(
          //   child: TweenAnimationBuilder(
          //       tween: Tween(
          //           begin: 100.0, end: flag ? 100.0 : 200.0), //此处的数据必须为 double 类型数据
          //       duration: const Duration(milliseconds: 100),
          //       builder: ((context, value, child) {
          //         return Icon(
          //           Icons.star,
          //           size: value.toDouble(),
          //         );
          //       })),
          // ),
    
          //透明度变化
          body: Center(
            child: TweenAnimationBuilder(
                tween: Tween(begin: 0.0, end: flag ? 0.2 : 1.0),
                duration: const Duration(milliseconds: 100),
                builder: ((context, value, child) {
                  return Opacity(
                    opacity: value,
                    child: Container(
                      color: Colors.red,
                      width: 200,
                      height: 200,
                    ),
                  );
                })),
          ),
    
    2、AnimatedBuilder 自定义显式动画

    属性

    (new) AnimatedBuilder AnimatedBuilder({
      Key? key,
      required Listenable animation,    //用于监听该动画,然后通知更新UI
      required Widget Function(BuildContext, Widget?) builder,    //构建动画
      Widget? child,
    })
    

    使用
    透明度动画

    class AnimatedBuilderPage extends StatefulWidget {
      const AnimatedBuilderPage({super.key});
    
      @override
      State<AnimatedBuilderPage> createState() => _AnimatedBuilderPageState();
    }
    
    class _AnimatedBuilderPageState extends State<AnimatedBuilderPage>
        with SingleTickerProviderStateMixin {
      late AnimationController _controller;
    
      @override
      void initState() {
        super.initState();
    
        _controller =
            AnimationController(vsync: this, duration: const Duration(seconds: 1))
              ..repeat(reverse: true);
      }
    
      @override
      void dispose() {
        super.dispose();
    
        _controller.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('AnimatedBuilder'),
          ),
          body: Center(
              child: AnimatedBuilder(
                  animation: _controller,
                  builder: (BuildContext context, Widget? child) {
                    return Opacity(
                      opacity: _controller.value,
                      child: Container(
                        width: 200,
                        height: 200,
                        color: Colors.red,
                        child: const Center(
                          child: Text("AnimatedBuilder"),
                        )
                      ),
                    );
                  })),
        );
      }
    }
    

    自定义变化范围

    //修改上述 body 中的代码,如下
          body: Center(
              child: AnimatedBuilder(
                  animation: _controller,
                  builder: (BuildContext context, Widget? child) {
                    ///透明度动画
                    // return Opacity(
                    //   opacity: _controller.value,
                    //   child: Container(
                    //     width: 200,
                    //     height: 200,
                    //     color: Colors.red,
                    //     child: const Center(
                    //       child: Text("AnimatedBuilder"),
                    //     )
                    //   ),
                    // );
    
                    ///自定义变化范围
                    return Opacity(
                      opacity:
                          Tween(begin: 0.4, end: 1.0).animate(_controller).value,
                      child: Container(
                          width: 200,
                          height: 200,
                          color: Colors.red,
                          child: const Center(
                            child: Text("AnimatedBuilder"),
                          )),
                    );
                  })),
    

    位置变化

    //将上述 body 中的代码更改如下
          body: Center(
            child: AnimatedBuilder(
                animation: _controller,
                builder: (context, child) {
                  return Container(
                    width: 200,
                    height: 200,
                    color: Colors.red,
                    transform: Matrix4.translationValues(
                        Tween(begin: -100.0, end: 100.0)
                            .chain(CurveTween(curve: Curves.bounceIn))
                            .chain(CurveTween(curve: const Interval(0.2, 0.8)))
                            .animate(_controller)
                            .value,
                        0,
                        0),
                    child: const Text("AnimatedBuilder"),
                  );
                }),
          ),
    

    chilid优化

    //将上述 body 中的代码更改如下
        body: Center(
            child: AnimatedBuilder(
              animation: _controller,
              builder: (context, child) {
                return Container(
                  width: 200,
                  height: 200,
                  color: Colors.red,
                  transform: Matrix4.translationValues(
                      Tween(begin: -100.0, end: 100.0)
                          .chain(CurveTween(curve: Curves.bounceIn))
                          .chain(CurveTween(curve: const Interval(0.2, 0.8)))
                          .animate(_controller)
                          .value,
                      0,
                      0),
                  child: child,
                );
              },
              child: const Text("AnimatedBuilder"),
            ),
          ),
    

    6、Hero 动画

    1、介绍

    微信朋友圈点击小图片的时候会有一个动画效果到大图预览,这个动画效果就可以使用 Hero 动画实现。Hero 指的是可以在路由(页面)之间飞行的 widget,简单来说 Hero 动画就是在路由切换时,有一个共享的 widget 可以在新旧路由间切换。

    2、属性
    (new) Hero Hero({
      Key? key,
      //用于关联两个两个界面的 Hero 组件,两个 Hero 组件有关联关系 , 则设置相同的 tag 字符串
      required Object tag,    
      //用于定义 Hero 组件的边界 , 以及定义 Hero 组件在界面切换时,从源界面的起始位置到目的界面的最终位置 , 动画执行的变化过程 
      Tween<Rect?> Function(Rect?, Rect?)? createRectTween,    
      //动画过程组件
      Widget Function(BuildContext, Animation<double>, HeroFlightDirection, BuildContext, BuildContext)? flightShuttleBuilder,
      //占位符组件
      Widget Function(BuildContext, Size, Widget)? placeholderBuilder,
      //使用手势进行转场时,是否显示动画
      bool transitionOnUserGestures = false,
      //普通的 Widget 组件 , Hero 动画作用的组件
      required Widget child, 
    })
    
    3、使用
    //HomePage
    import 'package:flutter/material.dart';
    import 'package:terminalflutter01/listData.dart';
    import './HeroPage.dart';
    
    class HomePage extends StatefulWidget {
      const HomePage({super.key});
    
      @override
      State<HomePage> createState() => _HomePageState();
    }
    
    class _HomePageState extends State<HomePage> {
      ///数据源
      final List<Widget> _getListData = [];
    
      @override
      void initState() {
        super.initState();
    
        for (var i = 0; i < listData.length; i++) {
          _getListData.add(
            ///手势
            GestureDetector(
              onTap: () {
                //添加舔砖图片链接
                Navigator.push(context, MaterialPageRoute(builder: (context) {
                  return HeroPage(arguments: {
                    "imageUrl": listData[i]['imageUrl'],
                    "description": "$i",
                  });
                }));
              },
              child: Container(
                decoration: BoxDecoration(
                    border: Border.all(
                        color: const Color.fromRGBO(233, 233, 233, 0.9), width: 1)),
                child: Column(
                  children: [
                    Hero(
                        tag: listData[i]['imageUrl'],
                        child: Image.network(listData[i]['imageUrl'])),
                    const SizedBox(height: 12),
                    Text(
                      listData[i]['title'],
                      textAlign: TextAlign.center,
                      style: const TextStyle(fontSize: 15),
                    )
                  ],
                ),
              ),
            ),
          );
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('HomePage'),
          ),
          body: GridView.count(
            crossAxisSpacing: 10, //子组件水平间距
            mainAxisSpacing: 10, //子组件垂直间距
            crossAxisCount: 2, //子组件水平数量
            padding: const EdgeInsets.all(10),
            children: _getListData,
          ),
        );
      }
    }
    
    //HeroPage
    import 'package:flutter/material.dart';
    
    class HeroPage extends StatefulWidget {
      final Map arguments;
      const HeroPage({super.key, required this.arguments});
    
      @override
      State<HeroPage> createState() => _HeroPageState();
    }
    
    class _HeroPageState extends State<HeroPage> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('HeroPage'),
          ),
          body: ListView(
            children:[
              Hero(
                tag: widget.arguments["imageUrl"], 
                child: Image.network(widget.arguments["imageUrl"])
                ),
             const SizedBox(height: 20), 
             Padding( 
              padding: const EdgeInsets.all(5), 
              child: Text(
                widget.arguments["description"], 
                style: const TextStyle(fontSize: 22)
                ), 
            )
            ],
          ),
        );
      }
    }
    
    注:可以通过第三方 photo_view 实现单张图片、多张图片预览

    补充:Curves 曲线值

    参考:
    Curves 效果视图 >>>
    Curves 效果视图(背用1) >>>
    Curves 效果视图(背用2) >>>

    相关文章

      网友评论

          本文标题:Flutter 之动画

          本文链接:https://www.haomeiwen.com/subject/vlukpdtx.html