zoukankan      html  css  js  c++  java
  • 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,
             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,  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(
                       100,
                      height: 50,
                      margin: ,
                      color: Colors.amber,
                      child: Center(
                        child: Text("位移动画"),
                      ),
                    ),
                  ),
                ),
              ),
              Expanded(
                child: Container(
                  child: Center(
                    child: SlideTransition(
                      position: slideTransition,
                      child: Container(
                         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,
                       200,
                      height: 50,
                      color: Colors.amber,
                      child: Center(
                        child: Text("旋转动画矩阵变换"),
                      ),
                    ),
                  ),
                ),
                Expanded(
                  child: Center(
                    child: RotationTransition(
                      turns: doubleAnim,
                      child: Container(
                         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(
                       animWidth.value,
                      height: animHeight.value,
                      color: Colors.amber,
                      child: Center(
                        child: Text("缩放动画"),
                      ),
                    ),
                  ),
                ),
                Expanded(
                  child: ScaleTransition(
                    scale: animScale,
                    child: Center(
                      child: Container(
                         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(
                         200,
                        height: 50,
                        color: Colors.amber,
                        child: Center(
                          child: Text("透明度动画"),
                        ),
                      ),
                    ),
                  ),
                ),
                Expanded(
                  child: Center(
                    child: FadeTransition(
                      opacity: fadeTransition,
                      child: Container(
                         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,
                               40,
                              color: Colors.white,
                            ),
                          ),
                        )
                      ],
                    ),
                  ),
                ),
    
    //            GestureDetector(
    //              onTap: (){
    //                _animController.forward();
    //              },
    //              child: ,
    //            ),
                GestureDetector(
                  onTap: () {
                    _animController.forward();
                  },
                  child: Container(
                    color: Colors.cyan,
                    child: Center(
                      child: Container(
                         _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,
                           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,
                         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,
             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,
                 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(
               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(
                   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项目地址

    参考文献:

  • 相关阅读:
    Android之Parcel
    Android常用的IPC通信
    Android之Surface绘制原理
    Android之Surface
    Android之SurfaceFlinger服务
    Android Service(服务)
    Android Service进阶之路
    Android之UI线程启动
    Android之Activity显示原理
    python17-Django进阶
  • 原文地址:https://www.cnblogs.com/819158327fan/p/11539998.html
Copyright © 2011-2022 走看看