41Flutter动画

Flutter动画

1.Flutter平移动画

动画属性:

dismissed:动画初始状态
forward:动画从头到尾播放状态
reverse:动画从尾到头播放状态
completed:动画完成状态
  • 值监听:addListener、removeListener
  • 状态监听:addStatusListener、removeStatusListener

状态值属性:

AnimationStatus.forward	:	执行 controller.forward() 会回调此状态
AnimationStatus.reverse : 	执行 controller.reverse() 会回调此状态
AnimationStatus.dismissed	:	动画从 controller.reverse() 反向执行 结束时会回调此方法
AnimationStatus.completed)	:	动画从 controller.forward() 正向执行 结束时会回调此方法

2.动画

1.平移动画 SlideTransition实现平移动画
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
      home: HomePage(),
    ));
class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>
    with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation<Offset> animation;

  //四种状态
  //1 dismissed 初始状态
  //2 forward 从头到尾播放状态
  //3 reverse 从尾到头播放状态
  //4 completed 完成状态
  @override
  void initState() {
    super.initState();
    controller =
        AnimationController(duration: const Duration(seconds: 2), vsync: this);
    //动画开始、结束、向前移动或向后移动时会调用StatusListener
    controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        //AnimationStatus.completed 动画在结束时停止的状态
        controller.reverse();
      } else if (status == AnimationStatus.dismissed) {
        //AnimationStatus.dismissed 表示动画在开始时就停止的状态
        controller.forward();
      }
    });
    //begin: Offset.zero, end: Offset(1, 0) 以左下角为参考点,相对于左下角坐标 x轴方向向右 平移执行动画的view 的1倍 宽度,y轴方向不动,也就是水平向右平移
    //begin: Offset.zero, end: Offset(1, 1) 以左下角为参考点,相对于左下角坐标 x轴方向向右 平移执行动画的view 的1倍 宽度,y轴方向 向下 平衡执行动画view 的1倍的高度,也就是向右下角平移了
    animation =
        Tween(begin: Offset.zero, end: Offset(1, 1)).animate(controller);
    //开始执行动画
    controller.forward();
  }
  @override
  void dispose() {
    super.dispose();
    controller.dispose();
  }
  //平移
  Widget buildSlideTransition() {
    return Center(
      //SlideTransition 用于执行平移动画
      child: SlideTransition(
        position: animation,
        //将要执行动画的子view
        child: Container(
          width: 200,
          height: 200,
          color: Colors.red,
        ),
      ),
    );
  }
  @override
  Widget build(BuildContext context) {
    return buildSlideTransition();
  }
}
2.RotationTransition
//旋转
  Widget buildRotationTransition() {
    return Center(
      child: RotationTransition(
        //设置动画的旋转中心
        alignment: Alignment.center,
        //动画控制器
        turns: controller,
        //将要执行动画的子view
        child: Container(
          width: 100,
          height: 100,
          color: Colors.red,
        ),
      ),
    );
  }
3.ScaleTransition缩放动画
import 'package:flutter/material.dart';

void main() => runApp(MaterialApp(
      home: HomePage(),
    ));

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage>
    with SingleTickerProviderStateMixin {
  AnimationController controller;

  //四种状态
  //1 dismissed 初始状态
  //2 forward 从头到尾播放状态
  //3 reverse 从尾到头播放状态
  //4 completed 完成状态
  @override
  void initState() {
    super.initState();
    controller =
        AnimationController(duration: const Duration(seconds: 2), vsync: this);
    //动画开始、结束、向前移动或向后移动时会调用StatusListener
    controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        //动画从 controller.forward() 正向执行 结束时会回调此方法
        print("status is completed");
        //重置起点
        // controller.reset();
        // //开启
        // controller.forward();
      } else if (status == AnimationStatus.dismissed) {
        //动画从 controller.reverse() 反向执行 结束时会回调此方法
        print("status is dismissed");
      } else if (status == AnimationStatus.forward) {
        print("status is forward");
        //执行 controller.forward() 会回调此状态
      } else if (status == AnimationStatus.reverse) {
        //执行 controller.reverse() 会回调此状态
        print("status is reverse");
      }
    });
    controller.forward();
  }

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

  Widget buildColumn() {
    return Column(
      children: [
        Container(
          color: Colors.red,
          width: 200,
          height: 100,
          child: OutlinedButton(
            child: Text('你好'),
            onPressed: (){
              if( controller.isCompleted){
                //反向开始
                controller.reverse();
              }else{
                //正向动画开始
                controller.forward();
              }
            },
          ),
        ),
        Center(
          child: ScaleTransition(
            //设置动画的缩放中心
            alignment: Alignment.center,
            //动画控制器
            scale: controller,
            //将要执行动画的子view
            child: Container(
              width: 100,
              height: 100,
              color: Colors.grey,
            ),
          ),
        )
      ],
    );
  }

//缩放
  Widget buildScaleTransition() {
    return buildColumn();
    // return Center(
    //   child: ScaleTransition(
    //     //设置动画的缩放中心
    //     alignment: Alignment.center,
    //     //动画控制器
    //     scale: controller,
    //     //将要执行动画的子view
    //     child: Container(
    //       width: 100,
    //       height: 100,
    //       color: Colors.grey,
    //     ),
    //   ),
    // );
  }

  @override
  Widget build(BuildContext context) {
    return buildScaleTransition();
  }
}

4.渐变动画
  //渐变动画
  Widget buildSlideTransition() {
    return Center(
      child: FadeTransition(
        opacity: controller,
        //将要执行动画的子view
        child: Container(
          width: 200,
          height: 200,
          color: Colors.grey,
          child: Image.network(
            "http://img5.duitang.com/uploads/item/201411/16/20141116124947_xBNxM.jpeg",
          ),
        ),
      ),
    );
  }
5AnimatedCrossFade 渐入渐出的动画

firstChild:第一个 childWidget,也就是直接展示的那个

secondChild:第二个 childWidget,切换时显示的

crossFadeState:final CrossFadeState crossFadeState,是一个枚举:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        theme: new ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: _MyHome());
  }
}

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

  @override
  __MyHomeState createState() {
    return __MyHomeState();
  }
}

class __MyHomeState extends State<_MyHome> {
  bool _state = true;

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      body: Column(
        children: <Widget>[
          SizedBox(
            height: 200.0,
          ),
          RaisedButton(
              child: Text('Button'),
              onPressed: () {
                setState(() {
                  _state = !_state;
                });
              }),
          RaisedButton(
              child: Text('Button'),
              onPressed: () {
                setState(() {
                  _state = !_state;
                });
              }),
          AnimatedCrossFade(
            alignment: AlignmentDirectional(0.0, 1.0),
            duration: Duration(milliseconds: 1000),
            firstCurve: Curves.fastOutSlowIn,
            secondCurve: Curves.fastOutSlowIn,
            sizeCurve: Curves.fastOutSlowIn,
            firstChild: FlutterLogo(
              size: 100.0,
            ),
            secondChild: FlutterLogo(
              size: 300.0,
            ),
            crossFadeState:
                _state ? CrossFadeState.showFirst : CrossFadeState.showSecond,
          ),
        ],
      ),
    );
  }
}

3.估值器

  • ReverseTween
  • ColorTween
  • SizeTween
  • RectTween
  • IntTween
  • StepTween
  • ConstantTween
1.Tween

Tween对象不存储任何状态。相反,它提供了evaluate(Animation<double> animation)方法将映射函数应用于动画当前值。 Animation对象的当前值可以通过value()方法取到。evaluate函数还执行一些其它处理,例如分别确保在动画值为0.0和1.0时返回开始和结束状态。

放大:

import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';

class LogoApp extends StatefulWidget {
  _LogoAppState createState() => new _LogoAppState();
}

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

  initState() {
    super.initState();
    controller = new AnimationController(
        duration: const Duration(milliseconds: 2000), vsync: this);
    animation = new Tween(begin: 0.0, end: 300.0).animate(controller)
      ..addListener(() {
        setState(() {
          // the state that has changed here is the animation object’s value
        });
      })
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          controller.reverse();
        } else if (status == AnimationStatus.dismissed) {
          controller.forward();
        }
      });
    controller.forward();
  }

  Widget build(BuildContext context) {
    return new Center(
      child: new Container(
        margin: new EdgeInsets.symmetric(vertical: 10.0),
        height: animation.value,
        width: animation.value,
        child: new FlutterLogo(),
      ),
    );
  }

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

void main() {
  runApp(new LogoApp());
}

2.ColorTween

颜色估值器

final Tween colorTween =
    new ColorTween(begin: Colors.transparent, end: Colors.black54);

4.插值器Curve 动画曲线Curves 效果一览

定义了时间和数值的抽象类。Flutter封装定义了一系列的插值器,如linear、decelerate、ease、bounce、cubic等。当然Flutter提供的不满足需求的话,也可以自定义插值器。

import 'package:flutter/material.dart';

void main() => runApp(MaterialApp(
  home: HomePage(),
));

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage>
    with SingleTickerProviderStateMixin {
  Animation<double> _doubleAnim;
  AnimationController _animationController;

  void myListener(status) {
    if (status == AnimationStatus.completed) {
      _animationController.removeStatusListener(myListener);
      _animationController.reset();
      _doubleAnim = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(
          parent: _animationController, curve: Curves.fastOutSlowIn));
      _animationController.forward();
    }
  }

  @override
  void initState() {
    super.initState();
    _animationController =
        AnimationController(vsync: this, duration: const Duration(seconds: 2));
    _doubleAnim = Tween(begin: -1.0, end: 0.0).animate(CurvedAnimation(
        parent: _animationController, curve: Curves.elasticOut))
      ..addStatusListener(myListener);
    _animationController.forward();
  }

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

  @override
  Widget build(BuildContext context) {
    var _screenWidth = MediaQuery.of(context).size.width;
    return Scaffold(
      body: AnimatedBuilder(
          animation: _animationController,
          builder: (BuildContext context, Widget child) {
            return Transform(
              transform: Matrix4.translationValues(
                  _doubleAnim.value * _screenWidth, 0.0, 0.0),
              child: Center(
                child: Container(
                  width: 200.0,
                  height: 200.0,
                  child: FlutterLogo(),
                ),
              ),
            );
          }),
    );
  }
}

5.如何做组合动画Interval

Flutter中组合动画使用IntervalInterval继承自Curve,用法如下:

Animation _sizeAnimation = Tween(begin: 100.0, end: 300.0).animate(CurvedAnimation(
    parent: _animationController, curve: Interval(0.5, 1.0)));

表示_sizeAnimation动画从0.5(一半)开始到结束,如果动画时长为6秒,_sizeAnimation则从第3秒开始。
Intervalbeginend参数值的范围是0.0到1.0。

1.先后动画
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
  home: AnimationDemo(),
));



class AnimationDemo extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _AnimationDemo();
}

class _AnimationDemo extends State<AnimationDemo>
    with SingleTickerProviderStateMixin {
  AnimationController _animationController;
  Animation _colorAnimation;
  Animation _sizeAnimation;

  @override
  void initState() {
    _animationController =
    AnimationController(duration: Duration(seconds: 5), vsync: this)
      ..addListener((){setState(() {

      });});

    _colorAnimation = ColorTween(begin: Colors.red, end: Colors.blue).animate(
        CurvedAnimation(
            parent: _animationController, curve: Interval(0.0, 0.5)));

    _sizeAnimation = Tween(begin: 100.0, end: 300.0).animate(CurvedAnimation(
        parent: _animationController, curve: Interval(0.5, 1.0)));

    //开始动画
    _animationController.forward();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          Container(
              height: _sizeAnimation.value,
              width: _sizeAnimation.value,
              color: _colorAnimation.value),
        ],
      ),
    );
  }

  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }
}
2.同时动画Interval

同时动画,只需将2个Interval的值都改为`Interval(0.0, 1.0)

  @override
  void initState() {
    _animationController =
    AnimationController(duration: Duration(seconds: 5), vsync: this)
      ..addListener((){setState(() {
      });});
    _colorAnimation = ColorTween(begin: Colors.red, end: Colors.blue).animate(
        CurvedAnimation(
            parent: _animationController, curve: Interval(0.0, 0.5)));
    _sizeAnimation = Tween(begin: 100.0, end: 300.0).animate(CurvedAnimation(
        parent: _animationController, curve: Interval(0.0, 1.0)));
    //开始动画
    _animationController.forward();
    super.initState();
  }
3.TweenSequence来设置动画,来简化Interval,但是不能代替
import 'dart:math';

import 'package:flutter/material.dart';

void main() => runApp(MaterialApp(
      home: TweenSequenceDemo(),
    ));

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

  @override
  _TweenSequenceDemoState createState() {
    return _TweenSequenceDemoState();
  }
}

class _TweenSequenceDemoState extends State<TweenSequenceDemo>
    with SingleTickerProviderStateMixin {
  AnimationController _animationController;
  Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _animationController =
        AnimationController(duration: Duration(milliseconds: 600), vsync: this);
    TweenSequenceItem downMarginItem = TweenSequenceItem<double>(
      tween: Tween(begin: 1.0, end: 50.0),
      weight: 50,
    );
    TweenSequenceItem upMarginItem = TweenSequenceItem<double>(
      tween: Tween(begin: 50.0, end: 100.0),
      weight: 100,
    );
    //然后创建一个动画插值组,把上面两个动画插值放入组中.
    TweenSequence tweenSequence = TweenSequence<double>([
      downMarginItem,
      upMarginItem,
    ]);
    _animation = tweenSequence.animate(_animationController);
    _animation.addListener(() {
      setState(() {});
    });
  }

  void startEasyAnimation() {
    _animationController.forward();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Container(
              width: 200,
              height: 50,
              color: Colors.orangeAccent,
              margin: EdgeInsets.only(top: _animation.value),
            ),
            FlatButton(
              onPressed: startEasyAnimation,
              child: Text(
                "点击执行动画",
                style: TextStyle(color: Colors.black38),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
4.通过weight来设置动画,Transform,后面讲解
5.动画总结

上面三种实现动画组基本上已经说完了,接下来我们就来对比其不同点.

特性监听状态法Interval时间间隔法TweenSequence动画序列法
代码简洁度🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅
动画是否可交织
动画属性是否可以多变

动画是否可交织 : 动画可否交织主要是说两个动画之间是否需要上一个动画完全执行完成之后,下一个动画才能执行.

动画属性是否可以多变 : 动画属性多变是指当前动画过程中可变化的属性是否可以有多个,例如同时变化尺寸和颜色等等.

6.共享动画Hero

Android中有共享元素动画,能够实现页面之间共享元素的切换效果。Flutter 中也提供 相应的 Hero widget 实现该效果。
Hero Widget 动画效果 : Hero 通过动画从 源界面 运动到 目标界面 时 , 目标界面 透明度逐渐增加 , 淡入显示 ;
Hero 是界面的组成部分 , 在 源界面 和 目标界面 都存在该组件 ;
Hero 动画涉及到的 API 较多 ;

属性
const Hero({
    Key key,
    @required this.tag, // 共享元素标识符,两个页面的 tag 需要一样且唯一
    this.createRectTween, // 定义目标 Hero 从起始路径 飞向目标路径时的边界变化。
    this.flightShuttleBuilder,// 可选替代,以提供在 Hero 动画执行期间显示的小部件。
    this.placeholderBuilder, //动画执行后,将占位符小部件保留为 Hero 的 child。
    this.transitionOnUserGestures = false, // 如果 PageRoute 转换是由用户手势(例如iOS上的向后滑动)触发的,是否执行 Hero 转换,默认是 false
    @required this.child, // 共享动画执行的 child 
  })

详细解答:

Hero 动画 tag 标识 : Hero 动画作用的组件在两个界面中都存在 , 给这两个 Hero 组件都设置相同的标识 , 通过该标识可以标识两个 Hero 组件之间进行动画过渡 ;

VoidCallback onTap : 从外部传入一个回调事件 , 这是点击组件后 , 回调的函数 ;
String imageUrl : 作为 Hero 动画的 tag 标识 , 同时也是图片的 url 网络地址 ;
double width : 用于约束 Hero 组件的宽度 ;

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;

void main() {
  runApp(
      MaterialApp(
        home: HeroAnimation(),
      )
  );
}

/// Hero 组件 , 跳转前后两个页面都有该组件
class HeroWidget extends StatelessWidget{
  /// 构造方法
  const HeroWidget({Key key, this.imageUrl, this.width, this.onTap}) : super(key: key);

  /// Hero 动画之间关联的 ID , 通过该标识
  /// 标识两个 Hero 组件之间进行动画过渡
  /// 同时该字符串也是图片的 url 网络地址
  final String imageUrl;
  /// 点击后的回调事件
  final VoidCallback onTap;
  /// 宽度
  final double width;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: width,

      /// 这里定义核心组件 Hero 组件 , 传入 tag 标识 , 与 Hero 动画作用的组件
      child: Hero(tag: imageUrl, child: Material(
        color: Colors.transparent,
        /// 按钮
        child: InkWell(
          /// 按钮点击事件
          onTap: onTap,
          child: Image.network(imageUrl, fit: BoxFit.contain,),
        ),
      ),),
    );
  }
}
class HeroAnimation extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    // 时间膨胀系数 , 用于降低动画运行速度
    timeDilation = 10.0;

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("Hero 动画演示( 跳转前页面 )"),
        ),
        body: Container(
          color: Colors.white,
          padding: EdgeInsets.all(20),
          alignment: Alignment.bottomRight,
          child: HeroWidget(
            imageUrl: "https://img-blog.csdnimg.cn/20210329101628636.jpg",
            width: 300,
            // 点击事件 , 这里点击该组件后 , 跳转到新页面
            onTap: (){

              print("点击事件触发, 切换到新界面");

              Navigator.of(context).push(
                  MaterialPageRoute(
                      builder: (context){
                        /// 跳转到的新界面再此处定义
                        return MaterialApp(
                          home: Scaffold(
                            appBar: AppBar(
                              title: Text("Hero 动画演示( 跳转后页面 )"),
                            ),
                            body: Container(
                              color: Colors.white,
                              padding: EdgeInsets.all(20),
                              alignment: Alignment.topLeft,
                              child: HeroWidget(
                                imageUrl: "https://img-blog.csdnimg.cn/20210329101628636.jpg",
                                width: 100,
                                onTap: (){
                                  /// 退出当前界面
                                  Navigator.of(context).pop();
                                },
                              ),
                            ),
                          ),
                        );
                      }
                  )
              );
            },
          ),
        ),
      ),
    );
  }
}
实现原理

Hero动画,它的整个运动过程分为3个步骤,即动画开始(t=0.0),动画进行中,动画结束(t=1.0),下面是Hero动画运动示意图:

示意图

第一步:在动画开始时,Flutter会计算出Hero的位置并复制一份,然后绘制到Overlay层上,复制的Hero和源Hero的大小是一致的,并且该Hero是在所有路由之上

第二步:依靠Tween来实现动画, 并在动画实现的过程中,Flutter会逐渐把源Hero移除屏幕,通过createRectTween属性把Tween传给Hero

第三步:Flutter将Overlay中的Hero移除,且完成了Hero在目标路由上的显示,这时Overlay是空白的。

案例二: 感觉比较常见

import 'package:flutter/material.dart';

void main() => runApp(MaterialApp(
  home: HeroHomePage(),
));

class HeroHomePage extends StatefulWidget {
  @override
  _TestPageState createState() => _TestPageState();
}

class _TestPageState extends State<HeroHomePage> {
  Widget buildBodyWidget() {
    //水波纹点击事件监听
    return InkWell(
      //手指点击抬起时的回调
      onTap: () {
        //打开新的页面
        openPageFunction();
      },
      child: Container(
        padding: EdgeInsets.all(10),
        color: Colors.white,
        //线性布局左右排列
        child: Row(
          //主轴方向开始对齐 在这里是左对齐
          mainAxisAlignment: MainAxisAlignment.start,
          //交叉轴上开始对齐 在这里是顶部对齐
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            //左侧的图片
            buildLeftImage(),
            //右侧的文本区域
            buildRightTextArea()],
        ),
      ),
    );
  }

  ///左侧的图片区域
  Container buildLeftImage() {
    return Container(
      margin: EdgeInsets.only(right: 12),
      child: Hero(
        tag: "test",
        child: Image.asset(
          "images/a.jpeg",
          width: 96,
          fit: BoxFit.fill,
          height: 96,
        ),
      ),
    );
  }

  ///自定义路由动画
  void openPageFunction() {
    Navigator.of(context).push(
      PageRouteBuilder(
        pageBuilder: (BuildContext context, Animation<double> animation,
            Animation<double> secondaryAnimation) {
          //目标页面
          return DetailsPage();
        },
        //打开新的页面用时
        transitionDuration: Duration(milliseconds: 1800),
        //关半页岩用时
        reverseTransitionDuration: Duration(milliseconds: 1800),
        //过渡动画构建
        transitionsBuilder: (
            BuildContext context,
            Animation<double> animation,
            Animation<double> secondaryAnimation,
            Widget child,
            ) {
          //渐变过渡动画
          return FadeTransition(
            // 透明度从 0.0-1.0
            opacity: Tween(begin: 0.0, end: 1.0).animate(
              CurvedAnimation(
                parent: animation,
                //动画曲线规则,这里使用的是先快后慢
                curve: Curves.fastOutSlowIn,
              ),
            ),
            child: child,
          );
        },
      ),
    );
  }

  ///右侧的文本区域
  Expanded buildRightTextArea() {
    return Expanded(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        mainAxisSize: MainAxisSize.min,
        children: [
          Text(
            "优美的应用",
            softWrap: true,
            overflow: TextOverflow.ellipsis,
            maxLines: 3,
            style: TextStyle(fontSize: 16),
          ),
          Text(
            "优美的应用体验 来自于细节的处理,更源自于码农的自我要求与努力,当然也需要码农年轻灵活的思维。",
            softWrap: true,
            overflow: TextOverflow.ellipsis,
            maxLines: 3,
            style: TextStyle(fontSize: 14, color: Colors.black38),
          )
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      //背景
      backgroundColor: Colors.grey[200],
      //标题
      appBar: AppBar(
        title: Text("每日分享"),
      ),
      //页面主体
      body: buildBodyWidget(),
    );
  }
}


class DetailsPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      //背景透明
      backgroundColor: Colors.white,
      appBar: AppBar(
        title: Text("精彩人生"),
      ),
      body: buildCurrentWidget(context),
    );
  }
  ///图片区域
  Hero buildHero(BuildContext context) {
    return Hero(
      tag: "test",
      child: Material(
        color: Colors.blue,
        child: InkWell(
          onTap: () {
            Navigator.of(context).pop();
          },
          child: Image.asset(
            "images/a.jpeg",
            fit: BoxFit.fill,
            width: MediaQuery.of(context).size.width,
          ),
        ),
      ),
    );
  }

  Container buildTextContainer() {
    return Container(
      width: double.infinity,
      child: Text(
        "优美的应用体验 来自于细节的处理,更源自于码农的自我要求与努力",
        softWrap: true,
        overflow: TextOverflow.ellipsis,
        maxLines: 3,
        style: TextStyle(fontSize: 16),
      ),
    );
  }

  Widget buildCurrentWidget(BuildContext context) {
    return Container(
      color: Colors.white,
      padding: EdgeInsets.all(8),
      margin: EdgeInsets.all(10),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          //图片区域
          buildHero(context),
          SizedBox(
            width: 22,
            height: 20,
          ),
          //文字区域
          buildTextContainer(),
        ],
      ),
    );
  }
}

7通过Transform 矩阵变换实现旋转平移缩放

1.属性
const Transform({
    Key key,
    @required this.transform,//控制子组件的平移、旋转、缩放、倾斜变换
    this.origin, //指定子组件做平移、旋转、缩放时中心点
    this.alignment,//子组件在Transform对齐方式
    this.transformHitTests = true,//点击区域是否也做相应的变换,为true时执行相应的变换,为false不执行
    Widget child,
  })
  • 旋转

    transform: Matrix4.rotationX(radian),
    transform: Matrix4.rotationY(radian),
    transform: Matrix4.rotationZ(radian),
    
  • 平移

    transform:Matrix4.translation(Vector3(x, y, z)),
    transform:Matrix4.translation(Vector3.all(val)),
    transform:Matrix4.translationValues(x, y, z),
    
  • 缩放

    transform:Matrix4.diagonal3(Vector3(x, y, z)),
    transform:Matrix4.diagonal3(Vector3.all(val)),
    transform:Matrix4.diagonal3Values(x, y, z),
    
  • 倾斜

    transform:Matrix4.skewX(alpha),
    transform:Matrix4.skewY(double beta),
    transform:Matrix4.skew(alpha, beta),
    
  • identity:恢复初始状态,也就是4*4的单位矩阵

     transform: Matrix4.identity()
                ..setEntry(3, 2, 0.001)
    
  • 平移、旋转、缩放组合变换

    Quaternion.axisAngle(Vector3 axis, double angle)
    Matrix4.compose(Vector3 translation, Quaternion rotation, Vector3 scale)
    
  • 组合动画

    class MyLogo extends AnimatedWidget {
      final Tween<double> _rotateAnim = Tween<double>(begin: 0.0, end: 20.0);
      final Tween<double> _scaleAnim = Tween<double>(begin: 1.0, end: 10.0);
    //  final Tween<Color> _colorAnim = Tween<Color>(begin: Colors.white, end: Colors.red);
    
      MyLogo({Key key, @required Animation animation})
          : super(key: key, listenable: animation);
    
      @override
      Widget build(BuildContext context) {
        final Animation<double> animation = listenable;
        return Transform.scale(
          scale: _scaleAnim.evaluate(animation),
          child: Transform.rotate(
            angle: _rotateAnim.evaluate(animation),
            child: Container(
              child: FlutterLogo(),
            ),
          ),
        );
      }
    

}


简化:

Transform.scale
Transform.rotate
Transform.translate


### 8路由动画

其实路由动画的原理很简单,就是重写并继承`PageRouterBuilder`这个类里的`transitionsBuilder`方法。

#### 属性

```dart
PageRouteBuilder({
  RouteSettings? settings,
  required this.pageBuilder,   //pageWidget
  this.transitionsBuilder = _defaultTransitionsBuilder,  //默认动画
  this.transitionDuration = const Duration(milliseconds: 300), //动画事件
  this.reverseTransitionDuration = const Duration(milliseconds: 300),
  this.opaque = true,
  this.barrierDismissible = false,
  this.barrierColor,
  this.barrierLabel,
  this.maintainState = true,
  bool fullscreenDialog = false,
}) 
import 'package:flutter/material.dart';

class  GradualChangeRoute extends PageRouteBuilder{
  final Widget widget;
  GradualChangeRoute(this.widget):super(
      transitionDuration:Duration(seconds: 2),
      pageBuilder:(
          BuildContext context,
          Animation<double>animation1,
          Animation<double>animation2,
          ){
        return widget;
      },
      transitionsBuilder:(
          BuildContext context,
          Animation<double>animation1,
          Animation<double>animation2,
          Widget child
          ){
        //渐隐渐变效果
        return FadeTransition(
          opacity: Tween(begin: 0.0,end: 1.0).animate(
              CurvedAnimation(
                  parent: animation1,
                  curve: Curves.fastOutSlowIn
              )
          ),
          child: child,
        );
      }
  );
}

如何使用

class FirstPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: Colors.blue,
        appBar:AppBar(
          title:Text('FirstPage',style: TextStyle(fontSize: 36.0)),
          elevation: 0.0,
        ),
        body:Center(
          child: MaterialButton(
            child: Icon(
              Icons.navigate_next,
              color:Colors.white,
              size:64.0,
            ),
            onPressed: (){
              Navigator.of(context).push(
                  CustomRoute(SecondPage()));
                  // MaterialPageRoute(
                  //     builder:(BuildContext context){
                  //       return SecondPage();
                  //     }));
            },
          ),
        )
    );
  }
}

9.官方提供的动画属性

AlignTransition
AnimatedAlign
AnimatedContainer
AnimatedCrossFade
AnimatedDefaultTextStyle
AnimatedIcon
AnimatedList
AnimatedModalBarrier
AnimatedOpacity
AnimatedPadding
AnimatedPositioned
AnimatedPositionedDirectional
AnimatedSwitcher
DecoratedBoxTransition
DefaultTextStyleTransition
FadeTransition
PositionedTransition
RelativePositionedTransition
RotationTransition
ScaleTransition
SizeTransition
SlideTransition
TweenAnimationBuilder
以上实现类大致分为2类(以下结论不一定正确, 没有全部测试过),

显式动画: 以Transition结尾的类,例如OpacityTransitions,PositionedTransition等。 显式动画指的是需要手动设置动画的时间,运动曲线,取值范围的动画。将值传递给动画部件如: RotationTransition,最后使用一个AnimationController 控制动画的开始和结束。
隐式动画: 以Animated开头的类, 例如AnimatedContainer, AnimatedOpacity等, 通过设置动画的起始值和最终值来触发。当使用 setState 方法改变部件的动画属性值时,框架会自动计算出一个从旧值过渡到新值的动画。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值