跨平台

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

2022-09-23  本文已影响0人  平安喜乐698
  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
  弹窗动效。
上一篇 下一篇

猜你喜欢

热点阅读