Flutter-动画-实践篇

Posted 819158327fan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter-动画-实践篇相关的知识,希望对你有一定的参考价值。

一、了解AnimatedWidget

  • 通常我们给一个Widget添加动画的时候都需要监听Animation的addListener()方法,并在这个方法里面不停的调用setState()方法通知Weight进行重绘。
  • AnimatedWidget是Flutter封装的用于执行动画的助手类。使用它可以使我们创建一个可重用动画的Widget。而且我们也不必关心Weight在什么时候需要重绘,因为AnimatedWidget中会自动调用addListener()和setState()。
  • AnimatedWidget实际上是一个StatefulWidget,它里面是定义了一套Widget并由外部将执行的动画传进来,然后根据Animation的value使各个Widget做出相应的改变。
  • Flutter封装了很多AnimatedWidget的助手类。SlideTransition、AlignTransitionPositionedTransitionRelativePositionedTransition等等。

 

技术图片
// 自定义AnimatedWidget
class CustomAnimatedWidget extends AnimatedWidget {
  CustomAnimatedWidget({Key key, Animation<double> animation})
                : super(key: key, listenable: animation);

  Widget build(BuildContext context) {
    final Animation<double> custom = animation;
    return new Center(
      child: new Container(
        height: animation.value,
        width: animation.value,
        child: new Container(),
      ),
    );
  }
}

// 使用CustomAnimatedWidget
class CustomApp extends StatefulWidget {
  _CustomAppState createState() => new _CustomAppState();
}

class _CustomAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation<double> animation;

  initState() {
    super.initState();
    controller = new AnimationController(
        duration: const Duration(milliseconds: 5000), vsync: this);
    animation = new Tween(begin: 0.0, end: 100.0).animate(controller);
    controller.forward();
  }

  Widget build(BuildContext context) {
    return new CustomAnimatedWidget(animation: animation);
  }

  dispose() {
    controller.dispose();
    super.dispose();
  }
}
View Code

 

 

二、了解AnimatedBuilder

  • AnimatedBuilder相比较AnimatedWidget来说更加纯粹。它不知道如何渲染Widget,也不会管理Animatiion。个人感觉AnimatedWidget是一个执行具体动画的控件,而AnimatedBuilder则是一个执行特定动画的容器。
  • Flutter中也创建了很多基于AnimatedBuilder的控件。例如:BottomSheet、 PopupMenu、ProgressIndicator、RefreshIndicator、Scaffold、SnackBar、TabBar、TextField。

 

技术图片
// 自定义CustomAnimatedBuilder
class CustomAnimatedBuilder extends StatelessWidget {
  GrowTransition({this.child, this.animation});

  final Widget child;
  final Animation<double> animation;

  Widget build(BuildContext context) {
    return new Center(
      child: new AnimatedBuilder(
          animation: animation,
          builder: (BuildContext context, Widget child) {
            return new Container(
                height: animation.value, width: animation.value, child: child);
          },
          child: child),
    );
  }
}

// 使用CustomAnimatedBuilder
class CustomApp extends StatefulWidget {
  _CustomAppState createState() => new _CustomAppState();
}

class _CustomAppState extends State<LogoApp> with TickerProviderStateMixin {
  Animation animation;
  AnimationController controller;

  initState() {
    super.initState();
    controller = new AnimationController(
        duration: const Duration(milliseconds: 1000), vsync: this);
    final CurvedAnimation curve =
        new CurvedAnimation(parent: controller, curve: Curves.easeIn);
    animation = new Tween(begin: 0.0, end: 100.0).animate(curve);
    controller.forward();
  }

  Widget build(BuildContext context) {
    return new CustomAnimatedBuilder(child: ‘自己的view’, animation: animation);
  }

  dispose() {
    controller.dispose();
    super.dispose();
  }
}
View Code

 

 

三、动画示例 

1、位移动画

只要Widget能在一定的时间内按照一定的规则位移一定的距离,那边是产生了位移动画。可以通过改变Widget本身的margin,也可以通过改变父容器的padding,也可以通过SlideTransition的Offset产生位移,也可使用Matrix4的transform产生移动(Matrix4解释和使用)。下面看示例:

 

技术图片
// 位移动画 copy 代码可以直接使用
import ‘package:flutter/material.dart‘;

class TransferAnim extends StatefulWidget {
  @override
  _TransferAnimState createState() => _TransferAnimState();
}

// ignore: slash_for_doc_comments
/**
 * 这个实现 实际上是改变 父容器的padding/margin完成的
 */
class _TransferAnimState extends State<TransferAnim>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;
  Animation<EdgeInsets> anim;

  Animation<Offset> slideTransition;

  @override
  void initState() {
    super.initState();
    _initAnim();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: Text("位移动画"),
      ),
      body: Column(
        children: <Widget>[
          Expanded(
            child: Container(
              padding: anim.value,
              child: Center(
                child: Container(
                  width: 100,
                  height: 50,
                  margin: ,
                  color: Colors.amber,
                  child: Center(
                    child: Text("位移动画"),
                  ),
                ),
              ),
            ),
          ),
          Expanded(
            child: Container(
              child: Center(
                child: SlideTransition(
                  position: slideTransition,
                  child: Container(
                    width: 100,
                    height: 50,
                    color: Colors.amber,
                    child: Center(
                      child: Text("位移动画"),
                    ),
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }

  void _initAnim() {
    _controller = AnimationController(
      vsync: this,
      duration: Duration(
        seconds: 3,
      ),
    );
    anim = new EdgeInsetsTween(
      begin: EdgeInsets.only(left: 0, top: 0),
      end: EdgeInsets.only(left: 100, top: 150),
    ).animate(_controller);

    //Offset  这里解释一下,是相对于自己移动的比例倍数
    slideTransition = Tween<Offset>(
      begin: Offset(0, 0),
      end: Offset(0, 2),
    ).animate(_controller);

    anim.addListener(() {
      setState(() {});
    });

    anim.addStatusListener((status) {
      debugPrint(‘fanlei => $status‘);
      switch (status) {
        case AnimationStatus.dismissed:
          _controller?.forward();
          break;
        case AnimationStatus.forward:
          break;
        case AnimationStatus.reverse:
          break;
        case AnimationStatus.completed:
          _controller?.reverse();
          break;
      }
    });

    _controller?.forward();
  }

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

 

 

2、旋转动画

旋转动画就是一个Weight以某个点或者某个坐标轴旋转。可以使用Container()的transform(接受Matrix4)属性,也可以使用RotationTransition()执行旋转动画。下面看示例:

 

技术图片
import ‘package:flutter/material.dart‘;

class RotateAnim extends StatefulWidget {
  @override
  _RotateAnimState createState() => _RotateAnimState();
}
// ignore: slash_for_doc_comments

class _RotateAnimState extends State<RotateAnim>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;
  Animation<Matrix4> anim;

  Animation<double> doubleAnim;

  @override
  void initState() {
    super.initState();
    _initAnim();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: Text("旋转动画"),
      ),
      body: Container(
        child: Column(
          children: <Widget>[
            Expanded(
              child: Center(
                child: Container(
                  transform: anim.value,
                  width: 200,
                  height: 50,
                  color: Colors.amber,
                  child: Center(
                    child: Text("旋转动画矩阵变换"),
                  ),
                ),
              ),
            ),
            Expanded(
              child: Center(
                child: RotationTransition(
                  turns: doubleAnim,
                  child: Container(
                    width: 200,
                    height: 50,
                    color: Colors.greenAccent,
                    child: Center(
                      child: Text("旋转动画Rotation"),
                    ),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  void _initAnim() {
    _controller = AnimationController(
      vsync: this,
      duration: Duration(
        seconds: 3,
      ),
    );
    anim = new Matrix4Tween(
      begin: Matrix4.rotationZ(0),
      end: Matrix4.rotationZ(2.0),
    ).animate(_controller);
    anim.addListener(() {
      setState(() {});
    });

    anim.addStatusListener((status) {
      debugPrint(‘fanlei => $status‘);
      switch (status) {
        case AnimationStatus.dismissed:
          _controller?.forward();
          break;
        case AnimationStatus.forward:
          break;
        case AnimationStatus.reverse:
          break;
        case AnimationStatus.completed:
          _controller?.reverse();
          break;
      }
    });

    doubleAnim = Tween<double>(
      begin: 0,
      end: 1.0,
    ).animate(_controller);

    _controller?.forward();
  }

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

 

 

3、缩放动画

本文档使用了两种方式来达到缩放的效果。一种是直接使用Animation改变Container()的width和height的值。另一种则是使用ScaleTransition(),其原理也是使用了Matrix4。下面看代码示例:

 

技术图片
import ‘package:flutter/material.dart‘;

class ScaleAnim extends StatefulWidget {
  @override
  _ScaleAnimState createState() => _ScaleAnimState();
}
// ignore: slash_for_doc_comments

class _ScaleAnimState extends State<ScaleAnim>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;
  Animation<double> animWidth;
  Animation<double> animHeight;

  Animation<double> animScale;

  @override
  void initState() {
    super.initState();
    _initAnim();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: Text("缩放动画"),
      ),
      body: Container(
        child: Column(
          children: <Widget>[
            Expanded(
              child: Center(
                child: Container(
                  width: animWidth.value,
                  height: animHeight.value,
                  color: Colors.amber,
                  child: Center(
                    child: Text("缩放动画"),
                  ),
                ),
              ),
            ),
            Expanded(
              child: ScaleTransition(
                scale: animScale,
                child: Center(
                  child: Container(
                    width: 200,
                    height: 50,
                    color: Colors.greenAccent,
                    child: Center(
                      child: Text("缩放动画"),
                    ),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  void _initAnim() {
    _controller = AnimationController(
      vsync: this,
      duration: Duration(
        seconds: 3,
      ),
    );
    animWidth = new Tween<double>(
      begin: 100,
      end: 300,
    ).animate(_controller);
    animWidth.addListener(() {
      setState(() {});
    });

    animHeight = new Tween<double>(
      begin: 50,
      end: 150,
    ).animate(_controller);

    animScale = new Tween<double>(
      begin: 1,
      end: 1.5,
    ).animate(_controller);

    animWidth.addStatusListener((status) {
      debugPrint(‘fanlei => $status‘);
      switch (status) {
        case AnimationStatus.dismissed:
          _controller?.forward();
          break;
        case AnimationStatus.forward:
          break;
        case AnimationStatus.reverse:
          break;
        case AnimationStatus.completed:
          _controller?.reverse();
          break;
      }
    });

    _controller?.forward();
  }

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

 

 

4、透明度动画(渐隐渐现)

本文档使用了两种方式来达到渐隐渐现的效果。第一种使用了Opacity(),通过Animation改变其opacity属性。第二种则是使用FadeTransition()。下面看代码示例:

 

技术图片
import ‘package:flutter/material.dart‘;

class FadeAnim extends StatefulWidget {
  @override
  _FadeAnimState createState() => _FadeAnimState();
}
// ignore: slash_for_doc_comments

class _FadeAnimState extends State<FadeAnim>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;
  Animation<double> anim;

  Animation<double> fadeTransition;

  @override
  void initState() {
    super.initState();
    _initAnim();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: Text("透明度动画"),
      ),
      body: Container(
        child: Column(
          children: <Widget>[
            Expanded(
              child: Center(
                child: Opacity(
                  opacity: anim.value,
                  child: Container(
                    width: 200,
                    height: 50,
                    color: Colors.amber,
                    child: Center(
                      child: Text("透明度动画"),
                    ),
                  ),
                ),
              ),
            ),
            Expanded(
              child: Center(
                child: FadeTransition(
                  opacity: fadeTransition,
                  child: Container(
                    width: 200,
                    height: 50,
                    color: Colors.greenAccent,
                    child: Center(
                      child: Text("透明度动画"),
                    ),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  void _initAnim() {
    _controller = AnimationController(
      vsync: this,
      duration: Duration(
        seconds: 3,
      ),
    );
    anim = new Tween<double>(
      begin: 1,
      end: 0.2,
    ).animate(_controller);
    anim.addListener(() {
      setState(() {});
    });

    anim.addStatusListener((status) {
      debugPrint(‘fanlei => $status‘);
      switch (status) {
        case AnimationStatus.dismissed:
          _controller?.forward();
          break;
        case AnimationStatus.forward:
          break;
        case AnimationStatus.reverse:
          break;
        case AnimationStatus.completed:
          _controller?.reverse();
          break;
      }
    });

    fadeTransition = Tween<double>(
      begin: 0,
      end: 1,
    ).animate(_controller);

    _controller?.forward();
  }

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

 

 

5、非线性曲线动画

非线性曲线动画其主要类是使用CurvedAnimation创建一个非线性的Animation。CurvedAnimation的curve属性接受一个Curve类。Flutter SDK API中的Curves类是Flutter已经为我们写好了的各种非线性曲线。Curves非线性gif示例。下面看代码示例:

 

技术图片
import ‘package:flutter/material.dart‘;
// 本示例融合前面四种动画
class NonlinearAnim extends StatefulWidget {
  @override
  _NonlinearAnimState createState() => _NonlinearAnimState();
}

class _NonlinearAnimState extends State<NonlinearAnim>
    with SingleTickerProviderStateMixin {
  // 位移
  AnimationController _animController;
  CurvedAnimation _translateCurved;
  Animation<double> _translateAnim;

  CurvedAnimation _scaleCurved;
  Animation<double> _scaleAnim;

  // 旋转

  CurvedAnimation _rotationCurved;
  Animation<double> _rotationAnim;

  // 透明度
  CurvedAnimation _opacityCurved;
  Animation<double> _opacityAnim;

  @override
  void initState() {
    _initTransfer();
    _initScale();
    _initRotation();
    _initOpacity();
    _initListener();
    super.initState();
  }

  @override
  void dispose() {
    _animController?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: Text("非线性曲线动画"),
      ),
      body: Container(
        child: GridView.count(
          crossAxisCount: 2,
          padding: EdgeInsets.only(left: 10, right: 10, top: 10),
          mainAxisSpacing: 10,
          crossAxisSpacing: 10,
          children: <Widget>[
            GestureDetector(
              onTap: () {
                if (!_animController.isAnimating) {
                  _animController.forward();
                }
              },
              child: Container(
                color: Colors.amber,
                child: Stack(
                  children: <Widget>[
                    Align(
                      alignment: Alignment.topCenter,
                      child: Text("bounceOut"),
                    ),
                    Align(
                      alignment: Alignment.bottomCenter,
                      child: Transform.translate(
                        offset: Offset(0, _translateAnim.value),
                        child: Container(
                          height: 40,
                          width: 40,
                          color: Colors.white,
                        ),
                      ),
                    )
                  ],
                ),
              ),
            ),

//            GestureDetector(
//              onTap: (){
//                _animController.forward();
//              },
//              child: ,
//            ),
            GestureDetector(
              onTap: () {
                _animController.forward();
              },
              child: Container(
                color: Colors.cyan,
                child: Center(
                  child: Container(
                    width: _scaleAnim.value,
                    height: _scaleAnim.value,
                    color: Colors.white,
                  ),
                ),
              ),
            ),
            GestureDetector(
              onTap: () {
                _animController.forward();
              },
              child: Container(
                color: Colors.green,
                child: Center(
                  child: RotationTransition(
                    turns: _rotationAnim,
                    child: Container(
                      height: 40,
                      width: 40,
                      color: Colors.white,
                    ),
                  ),
                ),
              ),
            ),
//            AnimatedOpacity(opacity: null, duration: null)

            Container(
              color: Colors.indigoAccent,
              child: Center(
                child: Opacity(
                  opacity:_getOpacityValue(_opacityAnim.value),
                  child: Container(
                    height: 40,
                    width: 40,
                    color: Colors.white,
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  void _initTransfer() {
    _animController =
        AnimationController(vsync: this, duration: Duration(seconds: 2));
    _translateCurved =
        CurvedAnimation(parent: _animController, curve: Curves.bounceOut);
    _translateAnim = Tween<double>(
      begin: 0,
      end: -100,
    ).animate(_translateCurved);

    _translateAnim.addListener(() {
      setState(() {});
    });
  }

  void _initScale() {
    _scaleCurved =
        CurvedAnimation(parent: _animController, curve: Curves.easeInOutBack);
    _scaleAnim = Tween<double>(
      begin: 40,
      end: 140,
    ).animate(_scaleCurved);
  }

  void _initRotation() {
    _rotationCurved =
        CurvedAnimation(parent: _animController, curve: Curves.easeInOutBack);
    _rotationAnim = Tween<double>(
      begin: 0,
      end: 1,
    ).animate(_rotationCurved);
  }

  void _initOpacity() {
    _opacityCurved =
        CurvedAnimation(parent: _animController, curve: Curves.elasticInOut);
    _opacityAnim = Tween<double>(
      begin: 0,
      end: 1,
    ).animate(_opacityCurved);
  }

  double _getOpacityValue(double opacity) {
    double temp = 0;
    if (opacity < 0) {
      temp = 0;
      return temp;
    }
    if (opacity > 1) {
      temp = 1;
      return temp;
    }
    temp = opacity;
    return temp;
  }

  void _initListener() {
    _animController.addStatusListener((status) {
      switch (status) {
        case AnimationStatus.dismissed:
          _animController.forward();
          break;
        case AnimationStatus.forward:
        // TODO: Handle this case.
          break;
        case AnimationStatus.reverse:
        // TODO: Handle this case.
          break;
        case AnimationStatus.completed:
          _animController.reverse();
          break;
      }
    });

    _animController.forward();
  }
}
View Code

 

 

6、使用AnimatedWidget构建可重用动画

编写一个继承AnimatedWidget的Widget,在此Widget里我们可以编写自己想要的样式并执行相应的动画。下面是代码示例:

 

技术图片
import ‘package:flutter/material.dart‘;

class AnimWidgetPage extends StatefulWidget {
  @override
  _AnimWidgetPageState createState() => _AnimWidgetPageState();
}

class _AnimWidgetPageState extends State<AnimWidgetPage>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;

  CurvedAnimation _curved;
  Animation<double> _anim;

  @override
  void initState() {
    _controller = AnimationController(vsync: this,duration: Duration(milliseconds: 1500));
    _curved = CurvedAnimation(parent: _controller, curve: Curves.bounceInOut);
    _anim = Tween<double>(begin: 1, end: 5).animate(_curved);
    _controller.forward();
    super.initState();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: Text("AnimatedWidget"),
      ),
      body: Container(
        child: Center(
          child: CustomAnimWidget(
            animation: _anim,
          ),
        ),
      ),
    );
  }
}

class CustomAnimWidget extends AnimatedWidget {
  CustomAnimWidget({Key key, Animation<double> animation})
      : super(key: key, listenable: animation);

  Widget build(BuildContext context) {
    final Animation<double> animation = listenable;
    return Center(
      child: Container(
        height: 200,
        width: 200,
        child: ScaleTransition(
          scale: animation,
          child: Icon(
            Icons.android,
            color: Colors.green,
          ),
        ),
      ),
    );
  }
}
View Code

 

 

7、组合动画

组合动画顾名思义就是一个或多个Widget被几个动画同时作用。

 

技术图片
import ‘package:flutter/material.dart‘;

class CombinationAnimPage extends StatefulWidget {
  @override
  _CombinationAnimPageState createState() => _CombinationAnimPageState();
}

class _CombinationAnimPageState extends State<CombinationAnimPage>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;

  // 曲线
  CurvedAnimation _curvedAnimation;

  // 缩放
  Animation<double> _scaleAnim;

  // 旋转
  Animation<double> _rotationAnim;

  @override
  void initState() {
    _controller = AnimationController(vsync: this,duration: Duration(milliseconds: 2000));
    _curvedAnimation =
        CurvedAnimation(parent: _controller, curve: Curves.bounceInOut);
    _scaleAnim = Tween<double>(
      begin: 1,
      end: 5,
    ).animate(_curvedAnimation);
    _rotationAnim = Tween<double>(
      begin: 0,
      end: 1,
    ).animate(_curvedAnimation);

    _controller.addStatusListener((status) {
      switch (status) {
        case AnimationStatus.dismissed:
          _controller.forward();
          break;
        case AnimationStatus.forward:
          // TODO: Handle this case.
          break;
        case AnimationStatus.reverse:
          // TODO: Handle this case.
          break;
        case AnimationStatus.completed:
          _controller.reverse();
          break;
      }
    });

    _controller.forward();

    super.initState();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("组合动画"),
        centerTitle: true,
      ),
      body: Container(
        child: Center(
          child: ScaleTransition(
            scale: _scaleAnim,
            child: RotationTransition(
              turns: _rotationAnim,
              child: Icon(
                Icons.android,
                color: Colors.green,
              ),
            ),
          ),
        ),
      ),
    );
  }
}
View Code

 

   

8、使用AnimatedBuilder构建动画

AnimatedBuilder将动画和视图分离。它接受一个Animation和一个Widget。下面是代码示例:

 

技术图片
import ‘package:flutter/material.dart‘;

class CustomAnimBuildPage extends StatefulWidget {
  @override
  _CustomAnimBuildPageState createState() => _CustomAnimBuildPageState();
}

class _CustomAnimBuildPageState extends State<CustomAnimBuildPage>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;
  Animation<double> animation;
  Animation<Color> colorAnim;

  @override
  void initState() {
    _controller =
        AnimationController(vsync: this, duration: Duration(seconds: 1));
    animation = Tween<double>(
      begin: 50,
      end: 200,
    ).animate(_controller);

    colorAnim = ColorTween(begin: Colors.amber, end: Colors.deepPurple)
        .animate(_controller);

    _controller.addStatusListener((status) {
      switch (status) {
        case AnimationStatus.dismissed:
          _controller.forward();
          break;
        case AnimationStatus.forward:
          // TODO: Handle this case.
          break;
        case AnimationStatus.reverse:
          // TODO: Handle this case.
          break;
        case AnimationStatus.completed:
          _controller.reverse();
          break;
      }
    });

    _controller.forward();
    super.initState();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("AnimatedBuilder"),
        centerTitle: true,
      ),
      body: Container(
        child: Center(
          child: _CustomTransition(
            child: Icon(
              Icons.android,
              size: 50,
              color: Colors.green,
            ),
            animation: animation,
            colorAnim: colorAnim,
          ),
        ),
      ),
    );
  }
}

class _CustomTransition extends StatelessWidget {
  Widget child;
  Animation<double> animation;
  Animation<Color> colorAnim;

  _CustomTransition({this.child, this.animation, this.colorAnim});

  @override
  Widget build(BuildContext context) {
    return Container(
      child: AnimatedBuilder(
        animation: animation,
        builder: (BuildContext context, Widget widget) {
          return Container(
            color: colorAnim.value,
            width: animation.value,
            height: animation.value,
            child: Transform.translate(
              offset: Offset(animation.value-50, 0),
              child: child,
            ),
          );
        },
      ),
    );
  }
}
View Code

 

 

9、列表动画

列表动画本质上也是每个Item做相应的动画。下面是官方代码示例:

 

技术图片
import ‘package:flutter/material.dart‘;

class AnimatedListSample extends StatefulWidget {
  @override
  _AnimatedListSampleState createState() => new _AnimatedListSampleState();
}

class _AnimatedListSampleState extends State<AnimatedListSample> {
  final GlobalKey<AnimatedListState> _listKey =
      new GlobalKey<AnimatedListState>();
  ListModel<int> _list;
  int _selectedItem;
  int _nextItem; // The next item inserted when the user presses the ‘+‘ button.

  @override
  void initState() {
    super.initState();
    _list = new ListModel<int>(
      listKey: _listKey,
      initialItems: <int>[0, 1, 2],
      removedItemBuilder: _buildRemovedItem,
    );
    _nextItem = 3;
  }

  // Used to build list items that haven‘t been removed.
  Widget _buildItem(
      BuildContext context, int index, Animation<double> animation) {
    return new CardItem(
      animation: animation,
      item: _list[index],
      selected: _selectedItem == _list[index],
      onTap: () {
        setState(() {
          _selectedItem = _selectedItem == _list[index] ? null : _list[index];
        });
      },
    );
  }

  // Used to build an item after it has been removed from the list. This method is
  // needed because a removed item remains  visible until its animation has
  // completed (even though it‘s gone as far this ListModel is concerned).
  // The widget will be used by the [AnimatedListState.removeItem] method‘s
  // [AnimatedListRemovedItemBuilder] parameter.
  Widget _buildRemovedItem(
      int item, BuildContext context, Animation<double> animation) {
    return new CardItem(
      animation: animation,
      item: item,
      selected: false,
      // No gesture detector here: we don‘t want removed items to be interactive.
    );
  }

  // Insert the "next item" into the list model.
  void _insert() {
    final int index =
        _selectedItem == null ? _list.length : _list.indexOf(_selectedItem);
    _list.insert(index, _nextItem++);
  }

  // Remove the selected item from the list model.
  void _remove() {
    if (_selectedItem != null) {
      _list.removeAt(_list.indexOf(_selectedItem));
      setState(() {
        _selectedItem = null;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new Scaffold(
        appBar: new AppBar(
          title: const Text(‘AnimatedList‘),
          actions: <Widget>[
            new IconButton(
              icon: const Icon(Icons.add_circle),
              onPressed: _insert,
              tooltip: ‘insert a new item‘,
            ),
            new IconButton(
              icon: const Icon(Icons.remove_circle),
              onPressed: _remove,
              tooltip: ‘remove the selected item‘,
            ),
          ],
        ),
        body: new Padding(
          padding: const EdgeInsets.all(16.0),
          child: new AnimatedList(
            key: _listKey,
            initialItemCount: _list.length,
            itemBuilder: _buildItem,
          ),
        ),
      ),
    );
  }
}

/// Keeps a Dart List in sync with an AnimatedList.
///
/// The [insert] and [removeAt] methods apply to both the internal list and the
/// animated list that belongs to [listKey].
///
/// This class only exposes as much of the Dart List API as is needed by the
/// sample app. More list methods are easily added, however methods that mutate the
/// list must make the same changes to the animated list in terms of
/// [AnimatedListState.insertItem] and [AnimatedList.removeItem].
class ListModel<E> {
  ListModel({
    @required this.listKey,
    @required this.removedItemBuilder,
    Iterable<E> initialItems,
  })  : assert(listKey != null),
        assert(removedItemBuilder != null),
        _items = new List<E>.from(initialItems ?? <E>[]);

  final GlobalKey<AnimatedListState> listKey;
  final dynamic removedItemBuilder;
  final List<E> _items;

  AnimatedListState get _animatedList => listKey.currentState;

  void insert(int index, E item) {
    _items.insert(index, item);
    _animatedList.insertItem(index);
  }

  E removeAt(int index) {
    final E removedItem = _items.removeAt(index);
    if (removedItem != null) {
      _animatedList.removeItem(index,
          (BuildContext context, Animation<double> animation) {
        return removedItemBuilder(removedItem, context, animation);
      });
    }
    return removedItem;
  }

  int get length => _items.length;

  E operator [](int index) => _items[index];

  int indexOf(E item) => _items.indexOf(item);
}

/// Displays its integer item as ‘item N‘ on a Card whose color is based on
/// the item‘s value. The text is displayed in bright green if selected is true.
/// This widget‘s height is based on the animation parameter, it varies
/// from 0 to 128 as the animation varies from 0.0 to 1.0.
class CardItem extends StatelessWidget {
  const CardItem(
      {Key key,
      @required this.animation,
      this.onTap,
      @required this.item,
      this.selected: false})
      : assert(animation != null),
        assert(item != null && item >= 0),
        assert(selected != null),
        super(key: key);

  final Animation<double> animation;
  final VoidCallback onTap;
  final int item;
  final bool selected;

  @override
  Widget build(BuildContext context) {
    TextStyle textStyle = Theme.of(context).textTheme.display1;
    if (selected)
      textStyle = textStyle.copyWith(color: Colors.lightGreenAccent[400]);
    return new Padding(
      padding: const EdgeInsets.all(2.0),
      child: new SizeTransition(
        axis: Axis.vertical,
        sizeFactor: animation,
        child: new GestureDetector(
          behavior: HitTestBehavior.opaque,
          onTap: onTap,
          child: new SizedBox(
            height: 128.0,
            child: new Card(
              color: Colors.primaries[item % Colors.primaries.length],
              child: new Center(
                child: new Text(‘Item $item‘, style: textStyle),
              ),
            ),
          ),
        ),
      ),
    );
  }
}
View Code

 

 

10、共享元素动画

所谓共享元素动画可以简单的理解为两个页面共用同一个元素。但是其实是两个页面的的两个元素被相同的Tag所标记,再进行页面跳转的时候被框架识别,从而执行相应的动画。Flutter中使用共享元素动画需要使用Hero这个StatefulWidget。Hero的tag属性标记两个元素。下面是代码示例:

 

技术图片
import ‘package:flutter/material.dart‘;
import ‘package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart‘;

class HeroAnimation extends StatelessWidget {
  Widget build(BuildContext context) {
//    timeDilation = 5.0; // 1.0 means normal animation speed.

    return Scaffold(
      appBar: AppBar(
        title: Text(‘Basic Hero Animation‘),
        centerTitle: true,
      ),
      body: GridView.count(
        crossAxisCount: 2,
        children: <Widget>[
          ItemView(myData[0], 150),
          ItemView(myData[1], 150),
          ItemView(myData[2], 150),
          ItemView(myData[3], 150),
          ItemView(myData[4], 150),
          ItemView(myData[5], 150),
          ItemView(myData[6], 150),
          ItemView(myData[7], 150),
          ItemView(myData[8], 150),
          ItemView(myData[9], 150),
        ],
      ),
    );
  }
}

Widget getHeroAnim2(ItemModel itemModel) {
  return Scaffold(
    appBar: AppBar(
      title: Text("共享元素"),
      centerTitle: true,
    ),
    body: Container(
      alignment: Alignment.topLeft,
      child: ItemView(itemModel, 400),
    ),
  );
}

List<ItemModel> myData = <ItemModel>[
  ItemModel(
      title: ‘啦啦啦1111‘,
      imgUrl:
          ‘https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2717595227,1512397782&fm=26&gp=0.jpg‘),
  ItemModel(
      title: ‘啦啦啦2222‘,
      imgUrl:
          ‘https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3454574876,1377139334&fm=26&gp=0.jpg‘),
  ItemModel(
      title: ‘啦啦啦3333‘,
      imgUrl:
          ‘https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=1499844476,2082399552&fm=26&gp=0.jpg‘),
  ItemModel(
      title: ‘啦啦啦4444‘,
      imgUrl:
          ‘https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=1938482571,2420691429&fm=26&gp=0.jpg‘),
  ItemModel(
      title: ‘啦啦啦5555‘,
      imgUrl:
          ‘https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3548575507,3156953806&fm=26&gp=0.jpg‘),
  ItemModel(
      title: ‘啦啦啦6666‘,
      imgUrl:
          ‘https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3484495061,2102329231&fm=26&gp=0.jpg‘),
  ItemModel(
      title: ‘啦啦啦7777‘,
      imgUrl:
          ‘https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3562330430,950864085&fm=26&gp=0.jpg‘),
  ItemModel(
      title: ‘啦啦啦8888‘,
      imgUrl:
          ‘https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2985783351,2052499916&fm=26&gp=0.jpg‘),
  ItemModel(
      title: ‘啦啦啦9999‘,
      imgUrl:
          ‘https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=311914474,2668302507&fm=26&gp=0.jpg‘),
  ItemModel(
      title: ‘啦啦啦0000‘,
      imgUrl:
          ‘https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=2471845590,913308006&fm=26&gp=0.jpg‘),
];

// 数据类型
class ItemModel {
  String title;
  String imgUrl;

  ItemModel({this.title, this.imgUrl});
}

class ItemView extends StatelessWidget {
  ItemModel model;

  double height;

  ItemView(this.model, this.height);

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        Navigator.of(context).push(
          new MaterialPageRoute<Null>(
            builder: (BuildContext context) {
              return getHeroAnim2(model);
            },
          ),
        );
      },
      child: Container(
        alignment: Alignment.center,
        child: SizedBox(
          width: height,
          height: height,
          child: Container(
            decoration: BoxDecoration(
                borderRadius: BorderRadius.all(Radius.circular(6)),
                color: Colors.white),
            child: Hero(
              // 一个viewTree下面不能有相同的
              tag: model.imgUrl,
              child: Material(
                color: Colors.transparent,
                child: Column(
                  children: <Widget>[
                    Expanded(
                        child: Image.network(
                      model.imgUrl,
                      fit: BoxFit.cover,
                    )),
                    Text(model.title),
                  ],
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}
View Code

 

 

11、拖拽动画

拖拽需要使用GestureDetector()监听用户手势,其函数会返回一个DragUpdateDetails对象,这个对象可以获取当前手指位移的坐标,然后通过Offset()给Widget设置偏移量。下面是代码示例:

 

技术图片
import ‘package:flutter/material.dart‘;

class DragAnimPage extends StatefulWidget {
  @override
  _DragAnimPageState createState() => _DragAnimPageState();
}

class _DragAnimPageState extends State<DragAnimPage> {

  double mDx = 0;
  double mDy = 0;

  GlobalKey _globalKey = new GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("拖拽动画"),
        centerTitle: true,
      ),
      body: Container(
        child: Transform.translate(
          offset: Offset(mDx, mDy),
          child: GestureDetector(
            onPanUpdate: (dragUpdateDetails) {
              mDx = dragUpdateDetails.globalPosition.dx;
              mDy = dragUpdateDetails.globalPosition.dy;
              setState(() {});
            },
            child: Container(
              width: 100,
              height: 50,
              alignment: Alignment.center,
              color: Colors.indigoAccent,
              key: _globalKey,
              child: Text("拖拽"),
            ),
          ),
        ),
      ),
    );
  }
}
View Code

 

 

12、第三方动画

①、Lottie

Lottie动画是Airbnb公司出的一款跨平台的动画框架(基础篇有介绍链接)。下面是代码示例:

 

技术图片
import ‘package:flutter/material.dart‘;
import ‘package:flutter_lottie/flutter_lottie.dart‘;

class LottieAnimPage extends StatefulWidget {
  @override
  _LottieAnimPageState createState() => _LottieAnimPageState();
}

class _LottieAnimPageState extends State<LottieAnimPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Lottie动画"),
        centerTitle: true,
      ),
      body: Container(
        padding: EdgeInsets.all(20),
        child: Center(
          child: LottieView.fromFile(
            filePath: "assets/anim/8075-the-frog-to-drive.json",
            autoPlay: true,
            loop: true,
            reverse: true,
            onViewCreated: (lottieController) {

            },
          ),
        ),
      ),
    );
  }
}
View Code

 

 

 

②、Flare

Flare动画框架是Flutter官方推荐的一个动画框架(详细介绍请看基础篇)。下面是代码示例:

 

技术图片
import ‘package:flare_flutter/flare_actor.dart‘;
import ‘package:flutter/material.dart‘;

class FlareAnimPage extends StatefulWidget {
  @override
  _FlareAnimPageState createState() => _FlareAnimPageState();
}

class _FlareAnimPageState extends State<FlareAnimPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: Text("Flare动画(官方推荐)"),
      ),
      body: Container(
        child: Column(
          children: <Widget>[
            Expanded(
              child: FlareActor(
                "assets/anim/Filip.flr",
                alignment: Alignment.center,
                fit: BoxFit.contain,
                animation: ‘idle‘,
              ),
            ),
          ],
        ),
      ),
    );
  }
}
View Code

 

 

 

四、Demo代码地址

点击进入github项目地址

 

 

参考文献:

以上是关于Flutter-动画-实践篇的主要内容,如果未能解决你的问题,请参考以下文章

Flutter 绘制实践 | 路径篇 - 阴影模糊

Flutter 绘制实践 | 路径篇 - 阴影模糊

VSCode自定义代码片段——CSS动画

VSCode自定义代码片段7——CSS动画

VSCode自定义代码片段7——CSS动画

错误记录Flutter 混合开发获取 BinaryMessenger 报错 ( FlutterActivityAndFragmentDelegate.getFlutterEngine() )(代码片段