美文网首页跨平台
Flutter了解之入门篇13-1(动画)

Flutter了解之入门篇13-1(动画)

作者: 平安喜乐698 | 来源:发表于2022-09-23 08:22 被阅读0次
      1. 内置动画组件
      2. 转换动画(通常命名为xxTransition)
    

    内置动画组件

    1. AnimatedPadding(缩放效果,改变padding)
      AnimatedPadding({ 
        Key? key,
        required this.padding,  // 不允许为负,否则异常
        this.child,
        Curve curve = Curves.linear,
        required Duration duration,
        VoidCallback? onEnd,
      });
    
    2. AnimatedPositioned(配合Stack使用,改变定位)
      AnimatedPositioned({
        Key? key,
        required this.child,
        this.left,
        this.top,
        this.right,
        this.bottom,
        this.width,
        this.height,
        // 相比Positioned组件多了如下3个属性
        Curve curve = Curves.linear,
        required Duration duration,
        VoidCallback? onEnd,
      }) 
    
    3. AnimatedOpacity (图片渐现过渡效果,改变透明度)
      AnimatedOpacity({
        Key? key,
        this.child,
        required this.opacity,  // 0-1
        Curve curve = Curves.linear,
        required Duration duration,  // 持续时间
        VoidCallback? onEnd,  // 动画结束后的回调
        this.alwaysIncludeSemantics = false,  // 默认是 false。用于辅助访问,如果是 true,则不管透明度是多少,都会显示语义信息(可以辅助朗读)。
      }) 
    
    4. AnimatedAlign(改变alignment)
    
    5. AnimatedContainer(改变container属性)
      AnimatedContainer({
        Key? key,
        this.alignment,
        this.padding,
        Color? color,
        Decoration? decoration,
        this.foregroundDecoration,
        double? width,
        double? height,
        BoxConstraints? constraints,
        this.margin,
        this.transform,
        this.transformAlignment,
        this.child,
        this.clipBehavior = Clip.none,
        // 相比Container多了如下3个属性
        Curve curve = Curves.linear,  // 动画曲线,默认是线性
        required Duration duration,  // 持续时间
        VoidCallback? onEnd,  // 动画结束后的回调
      });
    
    6. AnimatedDefaultTextStyle(改变字体样式)
      AnimatedDefaultTextStyle({
        Key? key,
        required this.child,
        required this.style,
        this.textAlign,
        this.softWrap = true,
        this.overflow = TextOverflow.clip,
        this.maxLines,
        this.textWidthBasis = TextWidthBasis.parent,
        this.textHeightBehavior,
        Curve curve = Curves.linear,
        required Duration duration,
        VoidCallback? onEnd,
      })
    
    7. AnimatedDecoratedBox
    

    示例(AnimatedOpacity图片渐显过渡,一张图逐渐消失另一张图逐渐显示)

    class _SwtichImageDemoState extends State<SwtichImageDemo> {
      var opacity1 = 1.0;
      var opacity2 = 0.0;
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('图片切换'),
            brightness: Brightness.dark,
            backgroundColor: Colors.black,
          ),
          backgroundColor: Colors.black,
          body: Center(
            child: Stack(
              alignment: Alignment.center,
              children: [
                AnimatedOpacity(
                  duration: Duration(milliseconds: 5000),
                  opacity: opacity1,
                  child: ClipOval(
                    child: Image.asset(
                      'images/beauty.jpeg',
                      width: 300,
                      height: 300,
                    ),
                  ),
                  curve: Curves.ease,
                ),
                AnimatedOpacity(
                  duration: Duration(milliseconds: 5000),
                  opacity: opacity2,
                  child: ClipOval(
                    child: Image.asset(
                      'images/beauty2.jpeg',
                      width: 300,
                      height: 300,
                    ),
                  ),
                  curve: Curves.ease,
                ),
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            child: Text(
              '变',
              style: TextStyle(
                color: Colors.white,
              ),
              textAlign: TextAlign.center,
            ),
            onPressed: () {
              setState(() {
                opacity1 = 0.0;
                opacity2 = 1.0;
              });
            },
          ),
        );
      }
    }
    

    示例(AnimatedPositioned 火箭发射)

    class RocketLaunch extends StatefulWidget {
      RocketLaunch({Key? key}) : super(key: key);
      @override
      _RocketLaunchState createState() => _RocketLaunchState();
    }
    class _RocketLaunchState extends State<RocketLaunch> {
      var rocketBottom = -80.0;
      var rocketWidth = 160.0;
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('火箭发射'),
            brightness: Brightness.dark,
            backgroundColor: Colors.black,
          ),
          backgroundColor: Colors.black,
          body: Center(
            child: Stack(
              alignment: Alignment.bottomCenter,
              children: [
                Image.asset(
                  'images/earth.jpeg',
                  height: double.infinity,
                  fit: BoxFit.fill,
                ),
                AnimatedPositioned(
                  child: Image.asset(
                    'images/rocket.png',
                    fit: BoxFit.fitWidth,
                  ),
                  bottom: rocketBottom,
                  width: rocketWidth,
                  duration: Duration(seconds: 5),
                  curve: Curves.easeInCubic,
                ),
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            child: Text(
              '发射',
              style: TextStyle(
                color: Colors.white,
              ),
              textAlign: TextAlign.center,
            ),
            onPressed: () {
              setState(() {
                rocketBottom = MediaQuery.of(context).size.height;
                rocketWidth = 40.0;
              });
            },
          ),
        );
      }
    }
    

    示例(AnimatedPadding、AnimatedPositioned、AnimatedAlign、AnimatedContainer、AnimatedDefaultTextStyle、AnimatedDecoratedBox)

    import 'package:flutter/material.dart';
    class AnimatedWidgetsTest extends StatefulWidget {
      @override
      _AnimatedWidgetsTestState createState() => _AnimatedWidgetsTestState();
    }
    class _AnimatedWidgetsTestState extends State<AnimatedWidgetsTest> {
      double _padding = 10;
      var _align = Alignment.topRight;
      double _height = 100;
      double _left = 0;
      Color _color = Colors.red;
      TextStyle _style = TextStyle(color: Colors.black);
      Color _decorationColor = Colors.blue;
      @override
      Widget build(BuildContext context) {
        var duration = Duration(seconds: 5);
        return SingleChildScrollView(
          child: Column(
            children: <Widget>[
              RaisedButton(
                onPressed: () {
                  setState(() {
                    _padding = 20;
                  });
                },
                child: AnimatedPadding(
                  duration: duration,
                  padding: EdgeInsets.all(_padding),
                  child: Text("AnimatedPadding"),
                ),
              ),
              SizedBox(
                height: 50,
                child: Stack(
                  children: <Widget>[
                    AnimatedPositioned(
                      duration: duration,
                      left: _left,
                      child: RaisedButton(
                        onPressed: () {
                          setState(() {
                            _left = 100;
                          });
                        },
                        child: Text("AnimatedPositioned"),
                      ),
                    )
                  ],
                ),
              ),
              Container(
                height: 100,
                color: Colors.grey,
                child: AnimatedAlign(
                  duration: duration,
                  alignment: _align,
                  child: RaisedButton(
                    onPressed: () {
                      setState(() {
                        _align = Alignment.center;
                      });
                    },
                    child: Text("AnimatedAlign"),
                  ),
                ),
              ),
              AnimatedContainer(
                duration: duration,
                height: _height,
                color: _color,
                child: FlatButton(
                  onPressed: () {
                    setState(() {
                      _height = 150;
                      _color = Colors.blue;
                    });
                  },
                  child: Text(
                    "AnimatedContainer",
                    style: TextStyle(color: Colors.white),
                  ),
                ),
              ),
              AnimatedDefaultTextStyle(
                child: GestureDetector(
                  child: Text("hello world"),
                  onTap: () {
                    setState(() {
                      _style = TextStyle(
                        color: Colors.blue,
                        decorationStyle: TextDecorationStyle.solid,
                        decorationColor: Colors.blue,
                      );
                    });
                  },
                ),
                style: _style,
                duration: duration,
              ),
              AnimatedDecoratedBox(
                duration: duration,
                decoration: BoxDecoration(color: _decorationColor),
                child: FlatButton(
                  onPressed: () {
                    setState(() {
                      _decorationColor = Colors.red;
                    });
                  },
                  child: Text(
                    "AnimatedDecoratedBox",
                    style: TextStyle(color: Colors.white),
                  ),
                ),
              )
            ].map((e) {
              return Padding(
                padding: EdgeInsets.symmetric(vertical: 16),
                child: e,
              );
            }).toList(),
          ),
        );
      }
    }
    

    转换动画(通常命名为xxTransition)

    苹果风格的全屏转换动效
    CupertinoFullscreenDialogTransition({
      Key? key,
      required Animation<double> primaryRouteAnimation,
      required Animation<double> secondaryRouteAnimation,
      required this.child,
      required bool linearTransition,
    }) 
    /*
    看一下CupertinoFullscreenDialogTransition的build方法:
    // 使用了两个SlideTransition实现该动效。
    Widget build(BuildContext context) {
      assert(debugCheckHasDirectionality(context));
      final TextDirection textDirection = Directionality.of(context);
      return SlideTransition(
        position: _secondaryPositionAnimation,
        textDirection: textDirection,
        transformHitTests: false,
        child: SlideTransition(
          position: _positionAnimation,
          child: child,
        ),
      );
    }
    */
    
    横向转换(可实现抽屉效果)
    CupertinoPageTransition({
      Key? key,
      required Animation<double> primaryRouteAnimation,
      required Animation<double> secondaryRouteAnimation,
      required this.child,
      required bool linearTransition,
    })
    
    更改 子组件的外框的特性来实现动效
    DecoratedBoxTransition
    
    滑动转换
    SlideTransition({  // AnimatedWidget的子类
      Key? key,
      // 使用AnimationController控制,是一个比例偏移。
      // new_x = width * dx; new_y = height * dy;
      // 如果想让组件从左边滑入,可以设置dx为负值。
      required Animation<Offset> position, 
      this.transformHitTests = true,
      this.textDirection,
      this.child,
    })
    
    旋转转换
    RotationTransition({
      Key? key, 
      required Animation<double> turns, 
      Alignment alignment, 
      FilterQuality? filterQuality, 
      Widget? child
    })
    
    尺寸转换
    SizeTransition({
      Key? key,
      this.axis = Axis.vertical,  // vertical则更改高度;horizontal则更改宽度。
      required Animation<double> sizeFactor, // 控制组件尺寸变化的 Animation 对象,乘数。
      this.axisAlignment = 0.0, // 子组件的对齐位置,默认为0.0从中间开始更改尺寸(横向时可实现卷轴从中间向两边打开效果)。当axis为vertical时,-1.0代表顶部对齐开始动画(即尺寸从上到下开始变大);当 axis 为horizontal 时,开始的方向和文本的反向有关(TextDirection.ltr 还是 TextDirection.rtl),当文本为从左到右时(TextDirection.ltr,默认),-1.0表示从左侧开始动画(即尺寸从左到右开始变大)。
      this.child,
    }) 
    
    缩放转换
    ScaleTransition({
      Key? key,
      required Animation<double> scale,
      this.alignment = Alignment.center,
      this.child,
    })
    
    更改组件在Stack中的位置
    PositionedTransition
    
    渐显转换
    FadeTransition
    

    示例(FadeTransition 渐显转换)

    Widget build(BuildContext context) {
      return Container(
        color: Colors.white,
        child: FadeTransition(
          opacity: _animation,
          child: const Padding(padding: EdgeInsets.all(8), child: FlutterLogo()),
        ),
      );
    }
    

    示例(PositionedTransition 更改组件在Stack中的位置)

    @override
    Widget build(BuildContext context) {
      const double smallLogo = 100;
      const double bigLogo = 200;
      return LayoutBuilder(
        builder: (BuildContext context, BoxConstraints constraints) {
          final Size biggest = constraints.biggest;
          return Stack(
            children: <Widget>[
              PositionedTransition(
                rect: RelativeRectTween(
                  begin: RelativeRect.fromSize(
                      const Rect.fromLTWH(0, 0, smallLogo, smallLogo), biggest),
                  end: RelativeRect.fromSize(
                      Rect.fromLTWH(biggest.width - bigLogo,
                          biggest.height - bigLogo, bigLogo, bigLogo),
                      biggest),
                ).animate(CurvedAnimation(
                  parent: _controller,
                  curve: Curves.elasticInOut,
                )),
                child: const Padding(
                    padding: EdgeInsets.all(8), child: FlutterLogo()),
              ),
            ],
          );
        },
      );
    }
    

    示例(DecoratedBoxTransition 更改外框)

    class _MyStatefulWidgetState extends State<MyStatefulWidget>
        with TickerProviderStateMixin {
      final DecorationTween decorationTween = DecorationTween(
        begin: BoxDecoration(
          color: const Color(0xFFFFFFFF),
          border: Border.all(style: BorderStyle.none),
          borderRadius: BorderRadius.circular(60.0),
          shape: BoxShape.rectangle,
          boxShadow: const <BoxShadow>[
            BoxShadow(
              color: Color(0x66666666),
              blurRadius: 10.0,
              spreadRadius: 3.0,
              offset: Offset(0, 6.0),
            )
          ],
        ),
        end: BoxDecoration(
          color: const Color(0xFFFFFFFF),
          border: Border.all(
            style: BorderStyle.none,
          ),
          borderRadius: BorderRadius.zero,
          // No shadow.
        ),
      );
      late final AnimationController _controller = AnimationController(
        vsync: this,
        duration: const Duration(seconds: 3),
      )..repeat(reverse: true);
      @override
      void dispose() {
        _controller.dispose();
        super.dispose();
      }
      @override
      Widget build(BuildContext context) {
        return Container(
          color: Colors.white,
          child: Center(
            child: DecoratedBoxTransition(
              position: DecorationPosition.background,
              decoration: decorationTween.animate(_controller),
              child: Container(
                width: 200,
                height: 200,
                padding: const EdgeInsets.all(10),
                child: const FlutterLogo(),
              ),
            ),
          ),
        );
      }
    }
    

    示例(SlideTransition 滑动)

    // 一张图片左侧滑入,一张图片右侧划出。
    class SlideTransitionDemo extends StatefulWidget {
      SlideTransitionDemo({Key? key}) : super(key: key);
      @override
      _SlideTransitionDemoState createState() => _SlideTransitionDemoState();
    }
    class _SlideTransitionDemoState extends State<SlideTransitionDemo>
        with SingleTickerProviderStateMixin {
      bool _forward = true;
      final begin = Offset.zero;
      // 第一张图片结束位置移出右侧屏幕
      final end1 = Offset(1.1, 0.0);
      // 第二张图片的初始位置在左侧屏幕
      final begin2 = Offset(-1.1, 0.0);
      late Tween<Offset> tween1 = Tween(begin: begin, end: end1);
      late Tween<Offset> tween2 = Tween(begin: begin2, end: begin);
      late AnimationController _controller =
          AnimationController(duration: const Duration(seconds: 1), vsync: this);
      // 使用自定义曲线动画过渡效果
      late Animation<Offset> _animation1 = tween1.animate(
        CurvedAnimation(
          parent: _controller,
          curve: Curves.easeInOut,
        ),
      );
      late Animation<Offset> _animation2 = tween2.animate(CurvedAnimation(
        parent: _controller,
        curve: Curves.easeInOut,
      ));
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('SlideTransition'),
            brightness: Brightness.dark,
            backgroundColor: Colors.black,
          ),
          backgroundColor: Colors.black,
          body: Center(
            child: Container(
              padding: EdgeInsets.all(10.0),
              child: Stack(
                children: [
                  SlideTransition(
                    child: ClipOval(
                      child: Image.asset('images/beauty.jpeg'),
                    ),
                    position: _animation1,
                  ),
                  SlideTransition(
                    child: ClipOval(
                      child: Image.asset('images/beauty2.jpeg'),
                    ),
                    position: _animation2,
                  ),
                ],
              ),
            ),
          ),
          floatingActionButton: FloatingActionButton(
            child: Icon(Icons.swap_horizontal_circle_sharp),
            onPressed: () {
              setState(() {
                if (_forward) {
                  _controller.forward();
                } else {
                  _controller.reverse();
                }
                _forward = !_forward;
              });
            },
          ),
        );
      }
    }
    

    示例(SizeTransition)

    // 图片从左向右飞入
    class SizeTransitionDemo extends StatefulWidget {
      SizeTransitionDemo({Key? key}) : super(key: key);
      @override
      _SizeTransitionDemoState createState() => _SizeTransitionDemoState();
    }
    class _SizeTransitionDemoState extends State<SizeTransitionDemo>
        with SingleTickerProviderStateMixin {
      late AnimationController _controller =
          AnimationController(duration: const Duration(seconds: 3), vsync: this)
            ..repeat();
      late Animation<double> _animation = CurvedAnimation(
          parent: _controller, curve: Curves.fastLinearToSlowEaseIn);
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('SizeTransition'),
            brightness: Brightness.dark,
            backgroundColor: Colors.blue,
          ),
          body: SizeTransition(
            child: Center(
              child: Image.asset(
                'images/superman.png',
                width: 300.0,
                height: 300.0,
              ),
            ),
            sizeFactor: _animation,
            axis: Axis.horizontal,
            axisAlignment: 1.0,
          ),
        );
      }
      @override
      void dispose() {
        _controller.stop();
        _controller.dispose();
        super.dispose();
      }
    }
    

    示例(ScaleTransition)

    class ScaleTransitionDemo extends StatefulWidget {
      ScaleTransitionDemo({Key? key}) : super(key: key);
      @override
      _ScaleTransitionDemoState createState() => _ScaleTransitionDemoState();
    }
    class _ScaleTransitionDemoState extends State<ScaleTransitionDemo>
        with SingleTickerProviderStateMixin {
      late AnimationController _controller =
          AnimationController(duration: const Duration(seconds: 10), vsync: this)
            ..repeat();
      late Animation<double> _animation =
          CurvedAnimation(parent: _controller, curve: Curves.easeOut);
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('ScaleTransition'),
            brightness: Brightness.dark,
            backgroundColor: Colors.blue,
          ),
          body: Center(
            child: balloon(),
          ),
        );
      }
      @override
      void dispose() {
        _controller.stop();
        _controller.dispose();
        super.dispose();
      }
      Widget balloon() {
        return ScaleTransition(
          alignment: Alignment.bottomCenter,
          child: Image.asset(
            'images/balloon.png',
          ),
          scale: _animation,
        );
      }
    }
    

    其他

    Transform 组件

    对子组件进行转换操作

    定义如下:
    Transform({
        Key? key,
        required this.transform,  // 一个Matrix4 对象,用于定义三维空间的变换操作。
        this.origin,  // 一个坐标偏移量,实际会加入到 Matrix4 的 translation(平移)中。
        this.alignment,  // 转变进行的参考方位
        this.transformHitTests = true,
        Widget? child,  // 
      }) : assert(transform != null),
           super(key: key, child: child);
    

    TweenAnimationBuilder组件(由用户触发动画)

    TweenAnimationBuilder({
      Key? key,
      required this.tween,  // Twee<T>类型,动画过程中会把 Tween 的中间插值传给 builder 来构建子组件,从而可以实现过渡动画效果。
      required Duration duration,
      Curve curve = Curves.linear,
      // 构建组件。value参数为tween动画过程中的中间插值,动画期间会不断调用builder重新绘制子组件。从源码中可看出初始化时tween起始值和结束值不一致就会启动动画。
      // typedef ValueWidgetBuilder<T> = Widget Function(BuildContext context, T value, Widget? child);
      required this.builder,  
      VoidCallback? onEnd,
      this.child,
    }) 
    

    示例(滤镜)

    class TweenAnimationDemo extends StatefulWidget {
      TweenAnimationDemo({Key? key}) : super(key: key);
      @override
      _TweenAnimationDemoState createState() => _TweenAnimationDemoState();
    }
    class _TweenAnimationDemoState extends State<TweenAnimationDemo> {
      var _sliderValue = 0.0;
      Color _newColor = Colors.orange;
      @override
      void initState() {
        super.initState();
      }
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('TweenAnimationBuilder'),
            brightness: Brightness.dark,
            backgroundColor: Colors.black,
          ),
          backgroundColor: Colors.black,
          body: Center(
            child: Column(
              children: [
                TweenAnimationBuilder(
                  tween: ColorTween(
                    begin: Colors.white,
                    end: _newColor,
                  ),
                  duration: Duration(seconds: 1),
                  builder: (_, color, child) {
                    // 对子组件的每一个像素进行颜色过滤。实际上是插入了一个颜色层,从而看起来有滤镜效果。
                    return ColorFiltered(  
                      colorFilter:
                          ColorFilter.mode(color as Color, BlendMode.modulate),
                      child: ClipOval(
                        child: ClipOval(
                          child: Image.asset(
                            'images/beauty.jpeg',
                            width: 300,
                          ),
                        ),
                      ),
                    );
                  },
                ),
                Slider.adaptive(
                  value: _sliderValue,
                  onChanged: (value) {
                    setState(() {
                      _sliderValue = value;
                    });
                  },
                  onChangeEnd: (value) {
                    setState(() {
                      _newColor = _newColor.withRed((value * 255).toInt());
                    });
                  },
                )
              ],
            ),
          ),
        );
      }
    }
    

    AnimatedModelBarrier

    ModalBarrier 的替换,可以挡住它下层的组件使得这些组件无法与用户交互,并且在组件上加一层颜色动画过渡遮罩。

    AnimatedModalBarrier({  
      Key? key,
      required Animation<Color?> color,
      bool dismissible,  // 为true时点击遮罩会退出当前页返回到上一页
      String? semanticsLabel,
      bool? barrierSemanticsDismissible
    })
    

    AnimatedPhysicalModel

    控制组件的阴影、颜色、边框圆弧等物理模型,但组件自身的形状不发生改变

    AnimatedPhysicalModel({
      Key? key,
      required Widget child,
      required BoxShape shape,
      Clip clipBehavior,
      BorderRadius borderRadius,
      required double elevation,
      required Color color,
      bool animateColor,
      required Color shadowColor,
      bool animateShadowColor,
      Curve curve = Curves.linear,
      required Duration duration,
      VoidCallback? onEnd
    })
    

    示例

    更改elevation 属性实现Z 轴阴影的变化
    
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: Text('AnimatedPhysicalModel 动画'),
        ),
        body: Center(
          child: AnimatedPhysicalModel(
            child: Container(
              width: 300,
              height: 300,
            ),
            duration: Duration(seconds: 1),
            color: _elevation == 0.0 ? Colors.blue : Colors.green,
            animateColor: true,
            animateShadowColor: true,
            elevation: _elevation,
            shape: BoxShape.circle,
            shadowColor: Colors.blue[900]!,
            curve: Curves.easeInOutCubic,
          ),
        ),
        floatingActionButton: FloatingActionButton(
          child: Text(
            'Play',
            style: TextStyle(
              color: Colors.white,
            ),
            textAlign: TextAlign.center,
          ),
          onPressed: () {
            setState(() {
              _elevation = _elevation == 0 ? 10.0 : 0.0;
            });
          },
        ),
      );
    }
    

    AnimatedSize

    Widget build(BuildContext context) {
      return GestureDetector(
        onTap: () => _updateSize(),
        child: Container(
          color: Colors.amberAccent,
          child: AnimatedSize(
            curve: Curves.easeIn,
            duration: const Duration(seconds: 1),
            child: FlutterLogo(size: _size),
          ),
        ),
      );
    }
    

    三方库

    1. animations 三方库
    1. Container Transform
      转场时将两个页面的元素联系使得转场更为平滑。
      类似Hero动画。
    2. Shared Axis
      共享轴,适用于UI元素之间有空间或引导联系的场景,通过在x,y或z轴的转换,实现界面之间的联系来进行动画过渡。
    3. Fade Through
      通过快速渐现和消失来实现没有关联UI界面的切换,避免突兀。
    4. Fade
      弹窗动效。
    

    相关文章

      网友评论

        本文标题:Flutter了解之入门篇13-1(动画)

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