Flutter - 变换动画

android 里面咱们就能够根据 layout 的变化设计本身的 latyout 切换动画,甚至矢量动画还能够进行 path 方面的无缝切换,好比从圆形天然过分到矩形html

Flutter 这里天然也响应提供了相关动画,可是区别确定仍是有的,首先 Flutter 这里名字就改了叫作:隐式动画,我想说何须给 coder 找不自在呢,延续 android 的传统很差嘛~java

  • AnimatedSwitcher - widget 内容改变时能够播放本身指定的动画
  • AnimatedContainer - 带动画的 Container,像 Container 一眼使用,在其中 color、width、height、圆角改变时会触发过分动画,动画不能控制,有些相似与 path 动画
  • AnimatedCrossFade - 切换不一样布局时能够显示动画,可是不能本身设置动画,默认就是淡入淡出,而且在大小不通切换时显示很差
  • DecoratedBoxTransition - 边框动画,核心是经过圆角角度的改变实现形状上的变化,这个变化是天然过分的,这点和 path 动画是同样了
  • AnimatedDefaultTextStyle - 文字样式改变时的切换动画,主要呈现的大小变换方面的动画,颜色的渐变过分不明显,可是体验很差的地方在于,大小字切换时字体粗细的变化真实有点辣眼,有点卡顿
  • AnimatedModalBarrier - 颜色改变的变换动画,特殊的地方在于其必须放到所操的 widget 的 child 中,有明确的应用场景,就是点击时改变背景色,好比 dialog 弹出时,背景变灰色
  • AnimatedOpacity - 透明度的变化动画
  • AnimatedPhysicalModel - 阴影变换动画,设置有些复杂
  • AnimatedPositioned - stack 中 widget 位置,大小变换动画
  • AnimatedSize - widget 大小变换动画

AnimatedContainer

AnimatedContainer 顾名思义就是带动画的 Container,属性设置使用和 Container 时如出一辙的,区别就是能够设置动画时间和插值器android

动画效果这块和 矢量动画相似,均可以实现先后状态间的无缝切换,由系统完成动画每帧的数值输出。可是能作到 矢量动画 那种效果的属性只有:colorwidthheight圆角,其余都不行,好比图片切换就是一下就切换了,shape 形状的切换,好比放大从圆形到矩形,圆形在达到最大时一瞬间会切换到矩形,矩形再慢慢放大缓存

AnimatedContainer 的使用套路就是,把属性值写在外部,经过 setState 改变属性值就能够实现切换动画了dom

这方面看我写的例子: ide

class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {
  double width = 50;
  double height = 50;
  Color color = Colors.blueAccent;
  BoxShape shape = BoxShape.circle;

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        AnimatedContainer(
          duration: Duration(seconds: 1),
          width: width,
          height: height,
          decoration: BoxDecoration(
            color: color,
            shape: shape,
          ),
        ),
        RaisedButton(
          child: Text("AAA"),
          onPressed: () {
            setState(() {
              width = 300;
              height = 300;
              color = Colors.pink;
              shape = BoxShape.rectangle;
            });
          },
        ),
      ],
    );
  }
}
复制代码

官方文档这里给出了一个很是好的例子,AnimatedContainer 能实现的极限都在这里面了,其中形状的改变是经过改变圆角矩形的角度实现的:BorderRadius.circular(8);布局

class TestWidgetState extends State<TestWidget> with SingleTickerProviderStateMixin {
  double _width = 50;
  double _height = 50;
  Color _color = Colors.green;
  BorderRadiusGeometry _borderRadius = BorderRadius.circular(8);

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Center(
          child: AnimatedContainer(
            // Use the properties stored in the State class.
            width: _width,
            height: _height,
            decoration: BoxDecoration(
              color: _color,
              borderRadius: _borderRadius,
            ),
            // Define how long the animation should take.
            duration: Duration(seconds: 1),
            // Provide an optional curve to make the animation feel smoother.
            curve: Curves.fastOutSlowIn,
          ),
        ),
        RaisedButton(
          child: Icon(Icons.play_arrow),
          // When the user taps the button
          onPressed: () {
            // Use setState to rebuild the widget with new values.
            setState(() {
              // Create a random number generator.
              final random = Random();

              // Generate a random width and height.
              _width = random.nextInt(300).toDouble();
              _height = random.nextInt(300).toDouble();

              // Generate a random color.
              _color = Color.fromRGBO(
                random.nextInt(256),
                random.nextInt(256),
                random.nextInt(256),
                1,
              );

              // Generate a random border radius.
              _borderRadius =
                  BorderRadius.circular(random.nextInt(100).toDouble());
            });
          },
        ),
      ],
    );
  }
}
复制代码

最后你们注意啊,AnimatedContainer 动画只能直接操做 Container 自己的属性,child 里的子 widget 就管不了了字体


AnimatedSwitcher

基本使用

AnimatedSwitcher 是 Flutter 中提供的用于 widget 切换内容时得动画样式,目前看到只能支持同一个 widget 得内容变化,切换不一样类型的 widget 还在研究动画

AnimatedSwitcher 属性有几个:ui

  • child - 内容切换动画做用于得 widget
  • duration - 动画从 A -> B 的时间
  • reverseDuration - 动画反过来从 B -> A 的时间
  • switchInCurve - 动画从 A -> B 的动画插值器,Curves.linear
  • switchOutCurve - 反过来得插值器
  • transitionBuilder - 动画
  • layoutBuilder - 包装新旧 Widget 的组件,默认是一个 Stack

其中注意点是 key,flutter widget 树自身有缓存的,同一个 widget 只是内容更新的话是不会重建该 widget 的,可是 AnimatedSwitcher 想要有动画出来,必须是2个 widget 才行的,因此咱们要手动设置 key 以规避 Flutter 中的 widget 缓存机制

网上这部份内容基本都出自官方文档:9.6 通用"切换动画"组件(AnimatedSwitcher)

官方文档没 gif,看不出效果,这里我帖一下效果和代码:

class TestWidgetState extends State<TestWidget> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          AnimatedSwitcher(
            duration: const Duration(milliseconds: 500),
            transitionBuilder: (Widget child, Animation<double> animation) {
              //执行缩放动画
              return ScaleTransition(child: child, scale: animation);
            },
            child: Text(
              '$_count',
              //显示指定key,不一样的key会被认为是不一样的Text,这样才能执行动画
              key: ValueKey<int>(_count),
              style: Theme.of(context).textTheme.display1,
            ),
          ),
          RaisedButton(
            child: const Text('+1',),
            onPressed: () {
              setState(() {
                _count += 1;
              });
            },
          ),
        ],
      ),
    );
  }
}
复制代码

而后咱们再来一个 icon 切换的例子

class TestWidgetState extends State<TestWidget> {
  IconData _actionIcon = Icons.delete;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          AnimatedSwitcher(
            transitionBuilder: (child, anim) {
              return ScaleTransition(child: child, scale: anim);
            },
            duration: Duration(milliseconds: 200),
            child: IconButton(
              key: ValueKey(_actionIcon),
              icon: Icon(_actionIcon),
            ),
          ),
          RaisedButton(
            child: Text("切换图标"),
            onPressed: () {
              setState(() {
                if (_actionIcon == Icons.delete)
                  _actionIcon = Icons.done;
                else
                  _actionIcon = Icons.delete;
              });
            },
          ),
        ],
      ),
    );
  }
}
复制代码

AnimatedSwitcher 原理

其实原理很简单,一说就明白。由于 AnimatedSwitcher 其中的 child widget 的key 不同,那么每次 child widget 内容变化时都会被认为是有新的 widget 出现,会从新 build,咱们在 didUpdateWidget 中拿到新久 widget,对旧 widget 执行反向动画,对新 widget 执行正向动画,就是这样,下面是源码截取:

void didUpdateWidget(AnimatedSwitcher oldWidget) {
  super.didUpdateWidget(oldWidget);
  // 检查新旧child是否发生变化(key和类型同时相等则返回true,认为没变化)
  if (Widget.canUpdate(widget.child, oldWidget.child)) {
    // child没变化,...
  } else {
    //child发生了变化,构建一个Stack来分别给新旧child执行动画
   _widget= Stack(
      alignment: Alignment.center,
      children:[
        //旧child应用FadeTransition
        FadeTransition(
         opacity: _controllerOldAnimation,
         child : oldWidget.child,
        ),
        //新child应用FadeTransition
        FadeTransition(
         opacity: _controllerNewAnimation,
         child : widget.child,
        ),
      ]
    );
    // 给旧child执行反向退场动画
    _controllerOldAnimation.reverse();
    //给新child执行正向入场动画
    _controllerNewAnimation.forward();
  }
}
复制代码

AnimatedSwitcher 高级使用

AnimatedSwitcher 的特征你们应该都明白了,新旧 widget 之间会执行一个动画的前进和反转,奇特证就是从哪里来旧回到哪里,好比新文字从右边进来,那么老的文字就从右边出去,整体上动画的执行必须是顺序的

那么咱们能够实现本身想要的效果嘛,好比右边进,左边出。其实这样是能够的,AnimatedSwitcher 咱们也不用改,咱们能够改一改动画API FlideTransition,全部的动画都是在 AnimationWidget 基础上写的

针对这个需求,咱们仿照 FlideTransition 内部实现,把动画在反转时把 X 轴的值加个-号就是咱们要的效果啦,大多数时候咱们都是用这种思路实现的

这个例子是官方文档上面的,代码上我多少改了一下,主要是在使用上更方便一点,封装度高了一点,针对 X/Y 轴作了封装

  • 这是对 SlideTransition 的改造,内部试用了 SlideTransition 的原始实现,核心就是在动画执行反转时对 Offset 坐标数据进行响应的处理,记住这个套路,其余的也同样
enum FreeSlideTransitionMode {
  reverseX,
  reverseY,
}

class FreeSlideTransition extends AnimatedWidget {
  
  Animation<Offset> animation;
  var child;
  Offset begin;
  Offset end;
  FreeSlideTransitionMode type;

  // x,y 轴反转播放时不一样的数据处理,用 map 承载
  Map<FreeSlideTransitionMode, Function(Animation animation, Offset offset)> worksMap = {
    // x 轴反转操做,典型应用,右进左出
    FreeSlideTransitionMode.reverseX: (Animation animation, Offset offset) {
      if (animation.status == AnimationStatus.reverse) {
        return offset = Offset(-offset.dx, offset.dy);
      }
      return offset;
    },
    FreeSlideTransitionMode.reverseY: (Animation animation, Offset offset) {
      if (animation.status == AnimationStatus.reverse) {
        return offset = Offset(offset.dx, -offset.dy);
      }
      return offset;
    },
  };

  // 构造方法的多态,写着有点麻烦,看起来也不省事
  FreeSlideTransition(this.type, this.child,
      {Key key, Animation<double> animation, Offset begin, Offset end})
      : super(key: key, listenable: animation) {
    this.animation = Tween<Offset>(begin: begin, end: end).animate(animation);
  }

  FreeSlideTransition.reverseX(
      {Widget child,
      Animation<double> animation,
      Key key,
      Offset begin,
      Offset end})
      : this(FreeSlideTransitionMode.reverseX, child,
            key: key, animation: animation, begin: begin, end: end);

  FreeSlideTransition.reverseY(
      {Widget child,
      Animation<double> animation,
      Key key,
      Offset begin,
      Offset end})
      : this(FreeSlideTransitionMode.reverseY, child,
            key: key, animation: animation, begin: begin, end: end);

  @override
  Widget build(BuildContext context) {
    var offset = animation.value;
    offset = worksMap[type](animation, offset);

    // SlideTranslation 内部就是这么实现的
    return FractionalTranslation(
      translation: offset,
      child: child,
    );
  }
}
复制代码
  • 具体试用:在 Column 中有时候位置会失效,中间加上一个 padding 过分就行了
class Test3 extends State<TestWidget> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          AnimatedSwitcher(
            duration: const Duration(milliseconds: 500),
            transitionBuilder: (Widget child, Animation<double> animation) {
              //执行缩放动画
              return FreeSlideTransition.reverseY(
                animation: animation,
                begin: Offset(0, 1),
                end: Offset(0, 0),
                child: child,
              );
            },
            child: Text(
              '$_count',
              //显示指定key,不一样的key会被认为是不一样的Text,这样才能执行动画
              key: ValueKey<int>(_count),
              style: Theme.of(context).textTheme.display1,
            ),
          ),

// Padding(
// padding: EdgeInsets.all(20.0),
// ),
// Text("AAA"),

          RaisedButton(
            child: Text(
              '+1',
            ),
            onPressed: () {
              setState(() {
                _count += 1;
              });
            },
          ),
        ],
      ),
    );
  }
}
复制代码

好了就是这样,有的朋友可能看官方文档时挺简单的,可是仍是推荐你们本身写一下,文档的代码封装度不够,也有些繁琐,本身试试也能练习一下功能封装不是,至少我这个 FreeSlideTransition 是能够放到 lib 库里面的

有的朋友不理解为何还要在 AnimatedSwitcher 里面本身写 tween 呢。其实一开始我也是不理解的,后来一想啊,AnimationControl 的数值默认是 [0-1] 的,咱们要是想用本身的数据设置,可不就得本身写 Tween 啊


AnimatedCrossFade

AnimatedCrossFade 不一样布局切换时能够显示动画,可是不能本身设置动画,默认就是淡入淡出,而且在大小不通切换时显示很差

我走一个 gif,让你们看看 widget 在大小不一样切换时那种强烈的突兀感,小变大还行,可是大变小就不行了

AnimatedCrossFade 惋惜不能本身设置动画,默认也就是个渐变更画,限制挺大的

代码上呢,须要咱们指定显示 frist widget 仍是 second widget,咱们在 widget 外部写一个标记,setState 改变这个标记就能触发动画了

class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {
  var isFristShow = true;

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        AnimatedCrossFade(
          firstChild: Container(
            alignment: Alignment.center,
            width: 200,
            height: 200,
            decoration: BoxDecoration(
              shape: BoxShape.circle,
              color: Colors.blueAccent,
            ),
            child: Text("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
          ),
          secondChild: Container(
            alignment: Alignment.center,
            width: 300,
            height: 300,
            child: Text("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
            decoration:
                BoxDecoration(shape: BoxShape.rectangle, color: Colors.pinkAccent),
          ),
          crossFadeState: isFristShow
              ? CrossFadeState.showFirst
              : CrossFadeState.showSecond,
          duration: Duration(milliseconds: 300),
          reverseDuration: Duration(milliseconds: 300),
        ),
        RaisedButton(
          child: Text("AAA"),
          onPressed: () {
            setState(() {
              isFristShow = !isFristShow;
            });
          },
        ),
      ],
    );
  }
}
复制代码

DecoratedBoxTransition

DecoratedBoxTransition 是边框变化动画,只能进行边框变化的动画,但他是咱们一直所追求的,他能实现 widget 形状变化的天然过分,固然和咱们说 AnimatedContainer 时同样,起形状天然过分依然仍是依靠圆角矩形的圆角度数实现的

DecoratedBoxTransition 属性:

  • child -
  • decoration - 表示由外传递进来的动画属性值的变化,经过获取其值,填充到child的边框上 position - 表示边框动画的位置,能够是前景位置或者是背景位置,前景位置会盖住child元素

DecoratedBoxTransition 的 child 不用设置背景,DecorationTween 数值生成器会自动给 child 加上设定的 shape 背景的

_animation = DecorationTween(
      begin: BoxDecoration(
        borderRadius: BorderRadius.all(Radius.circular(0.0)),
        color: Colors.red,
      ),
      end: BoxDecoration(
        borderRadius: BorderRadius.all(Radius.circular(100.0)),
        color: Colors.green,
      ),
    )
复制代码

class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {
  Animation<Decoration> _animation;
  AnimationController _controller;
  Animation _curve;

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

    _controller = AnimationController(
      duration: const Duration(milliseconds: 1000),
      vsync: this,
    );
    _curve = CurvedAnimation(parent: _controller, curve: Curves.fastOutSlowIn);
    _animation = DecorationTween(
      begin: BoxDecoration(
        borderRadius: BorderRadius.all(Radius.circular(0.0)),
        color: Colors.red,
      ),
      end: BoxDecoration(
        borderRadius: BorderRadius.all(Radius.circular(100.0)),
        color: Colors.green,
      ),
    ).animate(_curve)
      ..addStatusListener((AnimationStatus state) {
        if (state == AnimationStatus.completed) {
          _controller.reverse();
        } else if (state == AnimationStatus.dismissed) {
          _controller.forward();
        }
      });
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Column(
      children: <Widget>[
        DecoratedBoxTransition(
          position: DecorationPosition.background,
          decoration: _animation,
          child: Container(
              child: Container(
            padding: EdgeInsets.all(50),
            child: Text("AAAAAA"),
          )),
        )
      ],
    );
  }
}
复制代码

AnimatedDefaultTextStyle

AnimatedDefaultTextStyle 文字样式改变时的切换动画,主要呈现的大小变换方面的动画,颜色的渐变过分不明显,可是体验很差的地方在于,大小字切换时字体粗细的变化真实有点辣眼,尤为是文字字号大的时候

吐槽一下,Google 团队的代码质量也在降低啊,在前有 AnimatedContainer 的前提下,AnimatedDefaultTextStyleAnimatedContainer 的设计,使用,命名套路彻底不同,Google 你是要闹哪样,有代码质量审查吗?AnimatedDefaultTextStyleAnimatedContainer 他们二者实际上是一种东西,可是开发者之间没有沟通,搞出2种东西,真是用着蛋疼啊

AnimatedDefaultTextStyle 使用上做为 text 的外层 widget 来用的,其实搞成 AnimatedContainer 那样直接代替 text 多好,其实 text 那些属性大多数 AnimatedDefaultTextStyle 也有,搞得不乱不类的,真让人火大

动画的触发同样仍是经过外层变量控制,经过 staState 来触发

class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {

  var _isSelected = true;
  var info1 = "Flutter !!!";
  var info2 = "is not you !!!";

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: <Widget>[
        AnimatedDefaultTextStyle(
          softWrap: false,
          textAlign: TextAlign.right,
          maxLines: 1,
          overflow: TextOverflow.ellipsis,
          curve: Curves.linear,
          duration: Duration(milliseconds: 300),
          child: Text( info2),
          style: _isSelected
              ? TextStyle(
                  fontSize: 10.0,
                  color: Colors.red,
                  fontWeight: FontWeight.bold,
                )
              : TextStyle(
                  fontSize: 30.0,
                  color: Colors.black,
                  fontWeight: FontWeight.w300,
                ),
        ),
        RaisedButton(
          child: Text("AA"),
          onPressed: () {
            setState(() {
              _isSelected = !_isSelected;
            });
          },
        ),
      ],
    );
  }
}
复制代码

文字如果切换先后行数不同的话,动画就比较难看了,你们看下面这个例子,在用的时候你们切记一行变多行


AnimatedModalBarrier

  • AnimatedModalBarrier 颜色改变的变换动画,特殊的地方在于其必须放到所操的 widget 的 child 中,有明确的应用场景,就是点击时改变背景色,好比 dialog 弹出时,背景变灰色

AnimatedModalBarrier 有几个参数,除了 color 外具体有啥用我也不知道:

  • color - 颜色值动画变化
  • dismissible - 是否触摸当前ModalBarrier将弹出当前路由,配合点击事件弹出路由使用
  • semanticsLabel - 语义化标签
  • barrierSemanticsDismissible - 语义树中是否包括ModalBarrier语义

color 这里接收一个 animation 动画对象,这样的话咱们能够本身设置先后颜色值,时长,添加控制等,API 自由度比其余同类变换动画 API 强多了

下面的例子里我设置了一个循环播放

class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {

  AnimationController _controller;
  Animation _curve;

  Animation<Color> animation;

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

    _controller = AnimationController(
      duration: const Duration(milliseconds: 1000),
      vsync: this,
    )
      ..addStatusListener((AnimationStatus state) {
        if (state == AnimationStatus.completed) {
          _controller.reverse();
        } else if (state == AnimationStatus.dismissed) {
          _controller.forward();
        }
      });

    animation = ColorTween(
      begin: Colors.blue,
      end: Colors.pinkAccent,
    ).animate(_controller);
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      alignment: Alignment.center,
      fit: StackFit.expand,
      children: <Widget>[
        Container(
          width: 300,
          height: 300,
          child: AnimatedModalBarrier(
            semanticsLabel: "StackBarrier",
            barrierSemanticsDismissible: true,
            dismissible: true,
            color: animation,
          ),
        ),
        Positioned(
          left: 20,
          top: 20,
          child: RaisedButton(
            child: Text("AA"),
            onPressed: () {
              _controller.forward();
            },
          ),
        ),
      ],
    );
  }
}
复制代码

AnimatedOpacity

AnimatedOpacity 透明度的变化动画,没什么可说的,看代码就是,下面我把颜色变换的动画一块儿加进来

class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {

  AnimationController _controller;
  Animation<Color> animation;
  double opacity = 1.0;

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

    _controller = AnimationController(
      duration: const Duration(seconds: 1),
      vsync: this,
    );

    animation = ColorTween(
      begin: Colors.blue,
      end: Colors.pinkAccent,
    ).animate(_controller);
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      alignment: Alignment.center,
      fit: StackFit.expand,
      children: <Widget>[
        AnimatedOpacity(
          curve: Curves.fastOutSlowIn,
          opacity: opacity,
          duration: Duration(seconds: 1),
          child: Container(
            width: 300,
            height: 300,
            child: AnimatedModalBarrier(
              semanticsLabel: "StackBarrier",
              barrierSemanticsDismissible: true,
              dismissible: true,
              color: animation,
            ),
          ),
        ),
        Positioned(
          left: 20,
          top: 20,
          child: RaisedButton(
            child: Text("AA"),
            onPressed: () {
              setState(() {
                opacity = 0.3;
                _controller.forward();
              });
            },
          ),
        ),
      ],
    );
  }
}
复制代码

AnimatedPhysicalModel

AnimatedPhysicalModel 属性以下,有点多,你们仔细看,看不懂的看下面 demo 就行:

  • shape:阴影的形状
  • clipBehavior:阴影的裁剪方式
    • Clip.none:无模式
    • Clip.hardEdge:裁剪速度稍快,但容易失真,有锯齿
    • Clip.antiAlias:裁剪边缘抗锯齿,使得裁剪更平滑,这种模式裁剪速度比antiAliasWithSaveLayer快,可是比hardEdge慢
    • Clip.antiAliasWithSaveLayer:裁剪后具备抗锯齿特性并分配屏幕缓冲区,全部后续操做在缓冲区进行
  • borderRadius:背景的边框
  • elevation:阴影颜色值的深度
  • color:背景色
  • animateColor:背景色是否用动画形式展现
  • shadowColor:阴影的动画值
  • animateShadowColor:阴影是否用动画形式展现

看着不少,可是你们不要懵,其实就一个有用,就是 shadowColor,咱们改 shadowColor 就会触发动画,不过 color 这个属性必须设置,要不报错

总体动画来讲,我是真不知道这个动画应用在哪里,是否是像上面那个同样,作点击后背景色的变化呢,谁知道呢...

class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {
 
  var isShadow = true;

  @override
  Widget build(BuildContext context) {
    return Stack(
      alignment: Alignment.center,
      fit: StackFit.loose,
      children: <Widget>[
        AnimatedPhysicalModel(
          curve: Curves.fastOutSlowIn,
          color: Colors.grey.withOpacity(0.2),
          clipBehavior: Clip.antiAliasWithSaveLayer,
          borderRadius: BorderRadius.circular(12.0),
          animateColor: true,
          animateShadowColor: true,
          shape: BoxShape.rectangle,
          shadowColor: isShadow ? _shadowColor1 : _shadowColor2,
          elevation: 5.0,
          duration: Duration(milliseconds: 300),
          child: Container(
            width: 200,
            height: 200,
            child: Text("AA"),
          ),
        ),
        Positioned(
          left: 20,
          top: 20,
          child: RaisedButton(
            child: Text("AA"),
            onPressed: () {
              setState(() {
                isShadow = !isShadow;
              });
            },
          ),
        ),
      ],
    );
  }
}
复制代码

AnimatedPositioned

AnimatedPositioned 这个你们看名字就知道了,就是严格按照以前的 API 命名的,这样才好嘛,这样才会一看就知道是干啥的,这里再次 diss 一下其余垃圾的起名和设计

这个我不想说了,你们看代码都清楚

class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {
 
  var isPosition = true;

  var top1 = 20.0;
  var left1 = 20.0;
  var width1 = 200.0;
  var height1 = 200.0;

  var top2 = 100.0;
  var left2 = 100.0;
  var width2 = 300.0;
  var height2 = 300.0;

  @override
  Widget build(BuildContext context) {
    return Stack(
      alignment: Alignment.center,
      fit: StackFit.expand,
      children: <Widget>[
        AnimatedPositioned(
          top: isPosition ? top1 : top2,
          left: isPosition ? left1 : left2,
          width: isPosition ? width1 : width2,
          height: isPosition ? height1 : height2,
          child: Container(
            color: Colors.blueAccent,
          ),
          duration: Duration(milliseconds: 300),
        ),
        Positioned(
          left: 20,
          top: 20,
          child: RaisedButton(
            child: Text("AA"),
            onPressed: () {
              setState(() {
                isPosition = !isPosition;
              });
            },
          ),
        ),
      ],
    );
  }
}
复制代码

AnimatedSize

上面看了这么多了,到这你们啥套路都知道了吧,就是效果不是很满意,看下面的 gif,你们能看到如果由于 widget 的大小变化而形成 widget 有位移的话,那么会进行位移动画,而大小就没动画了,这点挺不爽的

class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {
  double size1 = 200;
  double size2 = 300;

  var isSize = true;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        RaisedButton(
          child: Text("AA"),
          onPressed: () {
            setState(() {
              isSize = !isSize;
            });
          },
        ),
        AnimatedSize(
          alignment: Alignment.center,
          curve: Curves.fastOutSlowIn,
          vsync: this,
          duration: Duration(seconds: 1),
          reverseDuration: Duration(seconds: 2),
          child: Container(
            width: isSize ? size1 : size2,
            height: isSize ? size1 : size2,
            color: Colors.blueAccent,
          ),
        ),
      ],
    );
  }
}
复制代码
相关文章
相关标签/搜索