Flutter 隐式动画

3,904 阅读4分钟

Flutter 隐式动画

官方文档: flutter.dev/docs/codela…

一、什么是隐式动画

我们平时使用的 AnimatonController 来控制的动画,需要我们指定动画的运行时间,动画的运动曲线,并手动控制动画的开始和结束,这其实是显式动画,与之对应的就是隐式动画。顾名思义,隐式动画其实不需要我们对动画做太多的干预,直接使用 Flutter 内部定义好的动画组件,就可以实现简单的动画效果。

常见的隐式动画组件有 AnimatedOpacity , AnimatedContainer, AnimatedPadding, AnimatedPositioned, AnimatedSwitcher 以及 AnimatedAlign 等,通过这些组件的名字也能看出来这些组件的作用,接下来简单介绍一下如何使用隐式动画组件。

二、AnimatedOpacity 实现渐隐效果

1、AnimatedOpacity 介绍

AnimatedOpacity 这个 Widget 可以实现透明度变化的动画效果。

先看一下 AnimatedOpacity 的构造函数。

  const AnimatedOpacity({
    Key key,
    this.child,
    @required this.opacity,
    Curve curve = Curves.linear,
    @required Duration duration,
    VoidCallback onEnd,
    this.alwaysIncludeSemantics = false,
  }) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0),
       super(key: key, curve: curve, duration: duration, onEnd: onEnd);

这些参数基本上看一眼就知道是干嘛的了。

  • child : 子控件,也就是动画的作用对象
  • opacity : 指定透明度
  • curve: 动画的变换曲线,默认是线性变换
  • duration: 动画时间
  • onEnd : 结束回调

可以看得出,和我们平时正常使用 Widget 其实没有任何区别,但是却大大简化的动画的使用。

2、示例

class FadeInDemo extends StatefulWidget {
  _FadeInDemoState createState() => _FadeInDemoState();
}

class _FadeInDemoState extends State<FadeInDemo> {
  double opacity = 0.0;
  @override
  Widget build(BuildContext context) {
    return Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
      Image.network(
        'https://picsum.photos/250?image=9',
      ),
      MaterialButton(
        child: Text(
          'Show Details',
          style: TextStyle(color: Colors.blueAccent),
        ),
        onPressed: () => setState(() {
          opacity = 1;
        }),
      ),
      AnimatedOpacity(
        duration: Duration(seconds: 3),
        opacity: opacity,
        child: Column(
          children: <Widget>[
            Text('Type: Owl'),
            Text('Age: 39'),
            Text('Employment: None'),
          ],
        ),
      )
    ]);
  }
}

使用 AnimatedOpcacity 需要指定初始的透明度值, 0 为不可见,当点击按钮,设置为 1,同时 setState 将会触发动画效果。duration 指定了动画时间为 3s 。

效果:

三、 AnimatedContainer 实现多属性改变动画

1、AnimatedContainer 介绍

AnimatedContainer 这个 Widget 和 Container 一样,是同一个可以控制多个属性(如 margin、borderRaidu、color)的 widget ,同时还能加上动画。

构造函数如下:

  AnimatedContainer({
    Key key,
    this.alignment,
    this.padding,
    Color color,
    Decoration decoration,
    this.foregroundDecoration,
    double width,
    double height,
    BoxConstraints constraints,
    this.margin,
    this.transform,
    this.child,
    Curve curve = Curves.linear,
    @required Duration duration,
    VoidCallback onEnd,
  }) : assert(margin == null || margin.isNonNegative),
       assert(padding == null || padding.isNonNegative),
       assert(decoration == null || decoration.debugAssertIsValid()),
       assert(constraints == null || constraints.debugAssertIsValid()),
       assert(color == null || decoration == null,
         'Cannot provide both a color and a decoration\n'
         'The color argument is just a shorthand for "decoration: BoxDecoration(color: color)".'
       ),
       decoration = decoration ?? (color != null ? BoxDecoration(color: color) : null),
       constraints =
        (width != null || height != null)
          ? constraints?.tighten(width: width, height: height)
            ?? BoxConstraints.tightFor(width: width, height: height)
          : constraints,
       super(key: key, curve: curve, duration: duration, onEnd: onEnd);

和正常的 Container 使用没有什么区别,就是加上了动画相关的几个属性。

2、示例


const _duration = Duration(milliseconds: 400);

double randomBorderRadius() {
  return Random().nextDouble() * 64;
}

double randomMargin() {
  return Random().nextDouble() * 64;
}

Color randomColor() {
  return Color(0xFFFFFFFF & Random().nextInt(0xFFFFFFFF));
}

class AnimatedContainerDemo extends StatefulWidget {
  _AnimatedContainerDemoState createState() => _AnimatedContainerDemoState();
}

class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
  Color color;
  double borderRadius;
  double margin;

  @override
  void initState() {
    super.initState();
    color = Colors.deepPurple;
    borderRadius = randomBorderRadius();
    margin = randomMargin();
  }

  void change() {
    setState(() {
      color = randomColor();
      borderRadius = randomBorderRadius();
      margin = randomMargin();
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            SizedBox(
              width: 128,
              height: 128,
              child: AnimatedContainer(
                margin: EdgeInsets.all(margin),
                decoration: BoxDecoration(
                  color: color,
                  borderRadius: BorderRadius.circular(borderRadius),
                ),
                duration: _duration,
                curve: Curves.easeInOutBack,
              ),
            ),
            MaterialButton(
              color: Theme.of(context).primaryColor,
              child: Text(
                'change',
                style: TextStyle(color: Colors.white),
              ),
              onPressed: () => change(),
            ),
          ],
        ),
      ),
    );
  }
}

上面示例的代码可以动态的改变 Container 的 margin,borderRaidus 以及 color 属性。 效果:

四、AnimatedSwitcher 实现切换动画效果

AnimatedSwitcher 的详细的介绍和原理可以参考 Flutter 中文网—通用“动画切换”组件

1、AnimatedSwitcher 介绍

AnimatedSwitcher 可以同时对其新、旧子元素添加显示、隐藏动画。也就是说在AnimatedSwitcher的子元素发生变化时,会对其旧元素和新元素。 其构造函数如下:

const AnimatedSwitcher({
  Key key,
  this.child,
  @required this.duration, // 新child显示动画时长
  this.reverseDuration,// 旧child隐藏的动画时长
  this.switchInCurve = Curves.linear, // 新child显示的动画曲线
  this.switchOutCurve = Curves.linear,// 旧child隐藏的动画曲线
  this.transitionBuilder = AnimatedSwitcher.defaultTransitionBuilder, // 动画构建器
  this.layoutBuilder = AnimatedSwitcher.defaultLayoutBuilder, //布局构建器
})

当AnimatedSwitcher的child发生变化时(类型或Key不同),旧child会执行隐藏动画,新child会执行执行显示动画。究竟执行何种动画效果则由transitionBuilder参数决定,该参数接受一个AnimatedSwitcherTransitionBuilder类型的builder,定义如下:

typedef AnimatedSwitcherTransitionBuilder =
  Widget Function(Widget child, Animation<double> animation);

该builder在AnimatedSwitcher的child切换时会分别对新、旧child绑定动画:

对旧child,绑定的动画会反向执行(reverse) 对新child,绑定的动画会正向指向(forward) 这样一下,便实现了对新、旧child的动画绑定。AnimatedSwitcher的默认值是AnimatedSwitcher.defaultTransitionBuilder :

Widget defaultTransitionBuilder(Widget child, Animation<double> animation) {
  return FadeTransition(
    opacity: animation,
    child: child,
  );
}

可以看到,返回了FadeTransition对象,也就是说默认情况,AnimatedSwitcher会对新旧child执行“渐隐”和“渐显”动画。

2、示例


class AnimatedSwitcherCounterRoute extends StatefulWidget {
  const AnimatedSwitcherCounterRoute({Key key}) : super(key: key);

  @override
  _AnimatedSwitcherCounterRouteState createState() => _AnimatedSwitcherCounterRouteState();
}

class _AnimatedSwitcherCounterRouteState extends State<AnimatedSwitcherCounterRoute> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          AnimatedSwitcher(
            duration: const Duration(milliseconds: 500),
            transitionBuilder: (Widget child, Animation<double> animation) {

              ///缩放动画
              return ScaleTransition(child: child, scale: animation);

              ///上下左右平移动画
//              return SlideTransitionX(
//                child: child,
//                direction: AxisDirection.up, //上入下出
//                position: animation,
//              );

            },
            child: Text(
              '$_count',
              //显示指定key,不同的key会被认为是不同的Text,这样才能执行动画
              key: ValueKey<int>(_count),
              style: Theme.of(context).textTheme.display1,
            ),
          ),


          RaisedButton(
            child: const Text('+1',),
            onPressed: () {
              setState(() {
                _count += 1;
              });
            },
          ),


        ],
      ),
    );
  }
}

上面的 transitionBuilder 通过指定不同的返回值类型,可以控制不同的动画效果。

缩放:

平移:


github

推荐阅读

Flutter 动画简易教程

Flutter 3D 动画

Flutter Path 基础

Flutter 进阶


欢迎关注「Flutter 编程开发」微信公众号 。