Flutter动画入门篇

移动端团队 @ 奇舞团(360集团大前端团队)

什么是动画

动画(Animation)是一系列静止的图像以一定频率连续变化而导致肉眼的视觉暂留。

为什么需要动画

无论是移动端还是Web端开发,当有和用户交互的场景时,引入动画,可以使应用体验更流畅、用户体验更好,增强产品的可用性与趣味性。在 Flutter 中创建动画可以有多种不同实现方式,可以轻松实现各种动画类型。在 Flutter 中动画可以理解为,Widget 在某一段时间内,从一个状态(大小、位置、颜色、透明度、字体等等)变化到另一个状态的过程。

Flutter开发中如何选择动画

image.png

上图从 I want a Flutter animation! 出发,最终有四个终点,可以指导我们选择动画的实现方式,从上到下复杂程度递增。

  • Implicit Animations
  • Explicit Animations
  • Low-Level Animations
  • Third-Party Animation Framework

基础概念

Animation

保存动画目前的状态(例如,是否开始,暂停,前进或倒退)。

abstract class Animation<T> extends Listenable implements ValueListenable<T> {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const Animation();
  ///...
  /// The current status of this animation.
  AnimationStatus get status;

  /// The current value of the animation.
  @override
  T get value;
复制代码

AnimationController

管理 Animation,控制动画开始、结束等

class AnimationController extends Animation<double>
  with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
///...  
}
复制代码

Curve

字面意思为曲线,可以理解为两个值的变化曲线,比如匀速、先加速后减速、减速等等

abstract class Curve extends ParametricCurve<double> {
  /// Abstract const constructor to enable subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const Curve();
///...
}
复制代码

Flutter 内置了多种类型的 Curve。

Curve 各种效果演示

Tween

为动画对象设置变化范围值。

class Tween<T extends dynamic> extends Animatable<T> {
  /// Creates a tween.
  ///
  /// The [begin] and [end] properties must be non-null before the tween is
  /// first used, but the arguments can be null if the values are going to be
  /// filled in later.
  Tween({
    this.begin,
    this.end,
  });
}
复制代码

Ticker

用来注册屏幕刷新的回调来驱动动画的执行。

隐式动画

最简单的动画,隐式动画一般继承自 ImplicitlyAnimatedWidget,之所以叫隐式动画,在使用的时候 Flutter 帮我们隐藏了一些细节,我们无需关注 Animation 和 AnimationController 的创建,只需要关注 Widget 从什么状态变成了什么状态即可。当然隐式动画并非没有 Animation 和 AnimationController 只不过在父类 ImplicitlyAnimatedWidget Flutter 帮我们创建好了,ImplicitlyAnimatedWidget 主要代码如下:

abstract class ImplicitlyAnimatedWidget extends StatefulWidget {
  const ImplicitlyAnimatedWidget({
    Key key,
    this.curve = Curves.linear,
    @required this.duration,
    this.onEnd,
  }) : assert(curve != null),
       assert(duration != null),
       super(key: key);

  final Curve curve;

  final Duration duration;
  final VoidCallback onEnd;

  @override
  ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState();
}

abstract class ImplicitlyAnimatedWidgetState<T extends ImplicitlyAnimatedWidget> extends State<T> with SingleTickerProviderStateMixin<T> {
  @protected
  AnimationController get controller => _controller;
  AnimationController _controller;

  /// The animation driving this widget's implicit animations.
  Animation<double> get animation => _animation;
  Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: widget.duration,
      debugLabel: kDebugMode ? widget.toStringShort() : null,
      vsync: this,
    );
    _controller.addStatusListener((AnimationStatus status) {
      switch (status) {
        case AnimationStatus.completed:
          if (widget.onEnd != null)
            widget.onEnd();
          break;
        case AnimationStatus.dismissed:
        case AnimationStatus.forward:
        case AnimationStatus.reverse:
      }
    });
    _updateCurve();
    _constructTweens();
    didUpdateTweens();
  }
///...
}
复制代码

常用的隐式动画有

  • AnimatedContainer
  • AnimatedAlign
  • AnimatedCrossFade
  • AnimatedDefaultTextStyle
  • AnimatedOpacity
  • AnimatedPadding
  • AnimatedPhysicalModel
  • AnimatedPositioned
  • AnimatedSize
  • AnimatedSwitcher
  • AnimatedTheme

AnimatedContainer

为 Container 添加动画为例:

    InkWell(
      onTap: () {
        setState(() {
          selected = !selected;
        });
      },
      child: AnimatedContainer(
        color: Colors.blue,
        curve: _curve,
        margin: const EdgeInsets.all(20),
        duration: Duration(seconds: 1),
        width: selected ? 200.0 : 100.0,
        height: selected ? 100.0 : 200.0,
      ),
    ),
复制代码

显式动画

显式动画比隐式动画稍复杂一点,需要我们指定 Animation 和 AnimationController。显式动画一般继承自 AnimatedWidget

常用的显式动画有

  • AlignTransition
  • ScaleTransition
  • SizeTransition
  • SlideTransition
  • PositionedTransition
  • RelativePositionedTransition
class _SizeTransitionDemoState extends State<SizeTransitionDemo>
    with TickerProviderStateMixin {
  AnimationController _controller;
  Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 3),
      vsync: this,
    );
      // ..repeat();
    _animation = CurvedAnimation(
      parent: _controller,
      curve: Curves.fastOutSlowIn,
    );
    _controller.repeat();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("SizeTransition"),
        actions: [
          IconButton(icon: Icon(Icons.settings), onPressed: () {}),
        ],
      ),
      body: SingleChildScrollView(
        child: Column(
          children: [
            SizeTransition(
              sizeFactor: _animation,
              axis: Axis.horizontal,
              // axisAlignment: -1,
              child: Center(
                child: FlutterLogo(size: 200.0),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
复制代码

自定义CustomPainter

自定义CustomPainter可以理解为显示动画的高级用法,通过自定义画笔来实现对动画的更精细控制。

class LinePainter extends CustomPainter {
  final double progress;

  LinePainter(this.progress);

  @override
  void paint(Canvas canvas, Size size) {
    var paint = Paint()
      ..color = Colors.yellowAccent
      ..strokeWidth = 5
      ..strokeCap = StrokeCap.round;

    Offset startingPoint = Offset(0, size.height / 2);

    Offset endingPoint = Offset(progress, size.height / 2);

    canvas.drawLine(startingPoint, endingPoint, paint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}
复制代码

Lottie

用 Lottie 执行设计师设计的复杂动画,一般为 json 格式。

Lottie.network("https://assets7.lottiefiles.com/packages/lf20_wXxy5o.json", width: 300, height: 300),
Lottie.network("https://assets3.lottiefiles.com/packages/lf20_r9lh4ebq.json", width: 300, height: 300),
Lottie.asset('assets/64586-like-and-dislike-button.json'),
Lottie.asset('assets/65014-dog-walking.json'),
Lottie.asset('assets/65077-breathing-lotus.json'),
复制代码

视觉暂留演示

lottie files

文章分类
Android
文章标签