Flutter完全开发手册
  • Flutter 动画详解系列——隐式动画

    Flutter中的隐式动画指的是通过使用Flutter的动画库,就可以创建各种炫酷的UI视觉效果。 我们可以使用库中的一套组件来管理动画,这些组件被称为隐式动画组件。隐式动画组件都实现了ImplicitlyAnimatedWidget类,这些隐式动画组件有个共同的特点就是组件都以Aanimated为前缀。使用隐式动画,我们只需要关注动画的初始值和最终值,而不需要做额外的处理,这给我们的工作带来了很大的方便。

    Flutter 动画介绍:

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

    一、隐式动画

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

    1、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 createState() => _AnimatedOpacityPageState();
    }
    
    class _AnimatedOpacityPageState extends State {
      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,
              ),
            ),
          ),
        );
      }
    }
    

    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 createState() => _AnimatedPaddingPageState();
    }
    
    class _AnimatedPaddingPageState extends State {
      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、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 createState() => _AnimatedContainerPageState();
    }
    
    class _AnimatedContainerPageState extends State {
      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,
            ),
          ),
        );
      }
    }
    

    4、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 createState() => _AnimatedPositionedPageState();
    }
    
    class _AnimatedPositionedPageState extends State {
      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")),
              ),
            ],
          ),
        );
      }
    }
    

    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 createState() =>
          _AnimatedDefaultTextStylePageState();
    }
    
    class _AnimatedDefaultTextStylePageState
        extends State {
      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) transitionBuilder = AnimatedSwitcher.defaultTransitionBuilder,    // 动画构建器
      Widget Function(Widget?, List) layoutBuilder = AnimatedSwitcher.defaultLayoutBuilder,    //布局构建器
    })
    

    使用

    
    class AnimatedSwitcherPage extends StatefulWidget {
      const AnimatedSwitcherPage({super.key});
    
      @override
      State createState() => _AnimatedSwitcherPageState();
    }
    
    class _AnimatedSwitcherPageState extends State {
      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(
                        "xxxxxxxxx",
                        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(
                        "xxxxxxx",
                        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),
                ),
              ),
            ),
          ),