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,
          ),
        ),
      ],
    );
  }
}
複製代碼
相關文章
相關標籤/搜索