Flutter 基础动画 Hero动画 交错动画

Flutter动画学习
效果直接贴代码 运行看吧,更改push入口查看不同动画效果。
基础动画:颜色渐变、控件大小更改、位置变更
Hero动画:从一个界面push到另一个界面,有一个控件跟着动来动去。
交错动画:把基础动画结合起来,curve 0.0-1.0来控制好几个基础动画执行的先后顺序。

基础动画

动画过程可以是匀速的、加速的或者先加速后减速等。Flutter中通过Curve(曲线)来描述动画过程,Curve可以是线性的(Curves.linear),也可以是非线性的。
常用的曲线(Curve)类型
每种UI框架基本都有这些动画的基础类型
Flutter中,继承Curve类,可以自己定义不同的曲线类型。

继承关系
CurvedAnimation->Animation->Listenable
AnimationController->Animation->Listenable

  • AnimationController
    初始化AnimationController
    final AnimationController animation = new AnimationController(vsync: null)

  • Ticker
    TickerProvider创建Ticker

  • Tween
    默认情况下,AnimationController对象值的范围是0.0到1.0
    Tween.animate()
    要使用Tween对象就要调用animate()方法

  • AnimatedWidget
    animatedWidget替代下面代码
    将要执行的Widget集成AnimatiedWidget

..addListener(() {
    setState(() {
  });
});
  • AnimatedBuilder
    正是将渲染逻辑分离出来
  • addStatusListener()
    动画状态监听
    <colgroup><col style="width: 130px;"><col style="width: 406px;"></colgroup>
-- --
dismissed 动画在起始点停止
forward 动画正在正向执行
reverse 动画正在反向执行
completed 动画在终点停止

图片资源配置

flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  assets:
    - images/IMG_0696.jpg
import 'package:flutter/material.dart';

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

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

class AnimatioStateRoute extends StatefulWidget {
  @override
  _AnimatioStateRouteState createState() => _AnimatioStateRouteState();
}

class _AnimatioStateRouteState extends State<AnimatioStateRoute> with SingleTickerProviderStateMixin {

  Animation<double> animation; // 保存动画的插值和状态
  AnimationController controller; // 控制动画 开始、暂停等

  @override
  void initState() {
    super.initState();
    // 初始化控制动画类 动画用时4秒
    controller = new AnimationController(duration: const Duration(seconds: 4), vsync: this);
    // 动画曲线类型
    animation = CurvedAnimation(parent: controller, curve: Curves.bounceInOut);
    // 动画形态变化(大小从0.0->200.0)监听
    animation = new Tween(begin: 0.0, end: 200.0).animate(controller);
    animation.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        //动画执行结束时反向执行动画
        controller.reverse();
        print("动画完成");
      } else if (status == AnimationStatus.dismissed) {
        //动画恢复到初始状态时执行动画(正向)
        controller.forward();
        print("初始化状态");
      } else if (status == AnimationStatus.forward) {
        print("动画开始");
      } else if (status == AnimationStatus.reverse) {
        print("动画重复");
      }
    });
//    controller.forward();
  }

  void forward() {
    // 开始执行动画
    controller.forward();
  }

  void stop() {
    controller.stop();
  }

  void reverse() {
    controller.reverse();
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new Padding(padding: new EdgeInsets.only(top: 50.0), child: new Column(
        children: <Widget>[
          new Center(
            child: _GrowTransition(
              child: new Image.asset("images/IMG_0696.jpg"),
              animation: this.animation,
            ),
          ),
          new Container(
            child: new FlatButton(onPressed: () {
              forward();
            }, child: new Text("开始")),
          ),
          new Container(
            child: new FlatButton(onPressed: () {
              stop();
            }, child: new Text("暂停")),
          ),
          new Container(
            child: new FlatButton(onPressed: () {
              reverse();
            }, child: new Text("重复")),
          ),
          new Container(
            child: new FlatButton(onPressed: () {
              Navigator.push(context, FadeRoute(
                  builder: (context){
//                    return HeroAnimationRoute(); // Hero动画
//                    return StaggerDemo(); // 交错动画
                    return ItemPage(mTitle: "push",);// FadeRoute push动画
                  }, isActive: true
              )).then((value) {
                print(value);
              });
            }, child: new Text("跳转动画")),
          )
        ],
      ),
      ),
    );

  }

  dispose() {
    // 销毁
    controller.dispose();
    super.dispose();
  }
}

//
class _GrowTransition 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
      ),
    );
  }
}



class ItemPage extends StatelessWidget {
  ItemPage({Key key, this.mTitle}) : super(key : key);
  final String mTitle;
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new Row(
        children: <Widget>[
          new Center(
            child: new Text("ItemPage"),
          ),
          new FlatButton(onPressed: () {
            Navigator.of(context).pop('返回值');
          }, child: new Text(mTitle + "-返回"))
        ],
      )
    );
  }
}

// 继承PageRoute类可以实现各种转场动画
class FadeRoute extends PageRoute {
  FadeRoute({
    @required this.builder,
    this.transitionDuration = const Duration(milliseconds: 300),
    this.opaque = true,
    this.barrierDismissible = false,
    this.barrierColor,
    this.barrierLabel,
    this.maintainState = true,
    this.isActive = true,
  });

  final WidgetBuilder builder;

  @override
  final Duration transitionDuration;

  @override
  final bool opaque;

  @override
  final bool barrierDismissible;

  @override
  final Color barrierColor;

  @override
  final String barrierLabel;

  @override
  final bool maintainState;
  @override
  final bool isActive;

  @override
  Widget buildPage(BuildContext context, Animation<double> animation,
      Animation<double> secondaryAnimation) => builder(context);

  @override
  Widget buildTransitions(BuildContext context, Animation<double> animation,
      Animation<double> secondaryAnimation, Widget child) {
    // 001 平移
    return SlideTransition(position: new Tween<Offset>(
      begin: const Offset(1.0, 0.0),
      end: const Offset(0.0, 0.0),
    ).animate(animation), child: builder(context),);
    // 002 渐变
    //当前路由被激活,是打开新路由
    if(isActive) {
      return FadeTransition(
        opacity: animation,
        child: builder(context),
      );
    }else{
      //是返回,则不应用过渡动画
      return Padding(padding: EdgeInsets.zero);
    }
  }
}


class HeroAnimationRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new Container(
        alignment: Alignment.topCenter,
        child: InkWell(
          child: Hero(
            tag: "tag same", //唯一标记,前后两个路由页Hero的tag必须相同
            child: ClipOval(
              child: Image.asset("images/IMG_0696.jpg",
                width: 50.0,
              ),
            ),
          ),
          onTap: () {
            //打开B路由
            Navigator.push(context, PageRouteBuilder(
                pageBuilder: (BuildContext context, Animation animation,
                    Animation secondaryAnimation) {
                  return new FadeTransition(
                      opacity: animation,
                      child: HeroAnimationRouteB(imageName: "images/IMG_0696.jpg"),
                  );
                })
            );
          },
        ),
      ),
    );
  }
}

class HeroAnimationRouteB extends StatelessWidget {
  HeroAnimationRouteB({this.imageName});
  final String imageName;

  @override Widget build(BuildContext context) {
    return new Scaffold(
      //唯一标记,前后两个路由页Hero的tag必须相同
      body: new Center( child: Hero( tag: "tag same",
          child: Image.asset(imageName,
            width: 350.0,
          )
        ),
      ),
    );
  }
}

// 交错动画
class StaggerAnimation extends StatelessWidget {
  StaggerAnimation({ Key key, this.controller }): super(key: key){
    // 动画的顺序使用curve控制 0.0-1.0
    //高度动画
    height = Tween<double>(
      begin:.0 ,
      end: 300.0,
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(
          0.0, 0.3, //间隔,前60%的动画时间
          curve: Curves.ease,
        ),
      ),
    );
    // 颜色动画
    color = ColorTween(
      begin:Colors.green ,
      end:Colors.red,
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(
          0.3, 0.6,//间隔,前60%的动画时间
          curve: Curves.ease,
        ),
      ),
    );
    // 偏移动画
    padding = Tween<EdgeInsets>(
      begin:EdgeInsets.only(left: .0),
      end:EdgeInsets.only(left: 100.0),
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(
          0.6, 1.0, //间隔,后40%的动画时间
          curve: Curves.ease,
        ),
      ),
    );
  }

  final Animation<double> controller;
  Animation<double> height;
  Animation<EdgeInsets> padding;
  Animation<Color> color;

  Widget _buildAnimation(BuildContext context, Widget child) {
    return new Scaffold(
      body: new Container(
        alignment: Alignment.bottomCenter,
        padding:padding.value ,
        child: Container(
          color: color.value,
          width: 50.0,
          height: height.value,
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      builder: _buildAnimation,
      animation: controller,
    );
  }
}

class StaggerDemo extends StatefulWidget {
  @override
  _StaggerDemoState createState() => _StaggerDemoState();
}

class _StaggerDemoState extends State<StaggerDemo> with TickerProviderStateMixin {
  AnimationController _controller;

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

    _controller = AnimationController(
        duration: const Duration(milliseconds: 2000),
        vsync: this
    );
  }


  void _playAnimation() async {
    try {
      //先正向执行动画
      await _controller.forward().orCancel;
      //再反向执行动画
      await _controller.reverse().orCancel;
    } on TickerCanceled {
      // the animation got canceled, probably because we were disposed
    }
  }

  @override
  Widget build(BuildContext context) {
    return  GestureDetector(
      behavior: HitTestBehavior.opaque,
      onTap: () {
        _playAnimation();
      },
      child: Center(
        child: Container(
          width: 300.0,
          height: 300.0,
          decoration: BoxDecoration(
            color: Colors.black.withOpacity(0.1),
            border: Border.all(
              color:  Colors.black.withOpacity(0.5),
            ),
          ),
          //调用我们定义的交错动画Widget
          child: StaggerAnimation(
              controller: _controller
          ),
        ),
      ),
    );
  }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,265评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,078评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,852评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,408评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,445评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,772评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,921评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,688评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,130评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,467评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,617评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,276评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,882评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,740评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,967评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,315评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,486评论 2 348

推荐阅读更多精彩内容

  • 基本的动画概念和类 关键点 Animation是Flutter动画库中的核心类, 插入用于引导动画的值. Anim...
    最近不在阅读 1,670评论 2 2
  • 参考来源:https://flutterchina.club/animations/ 动画类型 补间(Tween)...
    _白羊阅读 18,209评论 2 11
  • Flutter动画类型 动画分为两类:基于tween或基于物理的。以下部分解释了这些术语的含义,并指出您可以在其中...
    盖世英雄_ix4n04阅读 6,161评论 0 2
  • 动画实现的方式 Flutter中,我们可以简单的把调用this.setState()理解为渲染一帧。那么只要我们不...
    shawn_yy阅读 1,828评论 0 6
  • 【Android 动画】 动画分类补间动画(Tween动画)帧动画(Frame 动画)属性动画(Property ...
    Rtia阅读 6,115评论 1 38