国产chinesehdxxxx野外,国产av无码专区亚洲av琪琪,播放男人添女人下边视频,成人国产精品一区二区免费看,chinese丰满人妻videos

Flutter實(shí)戰(zhàn) 動(dòng)畫(huà)過(guò)渡組件

2021-03-08 18:02 更新

為了表述方便,本書(shū)約定,將在 Widget 屬性發(fā)生變化時(shí)會(huì)執(zhí)行過(guò)渡動(dòng)畫(huà)的組件統(tǒng)稱為”動(dòng)畫(huà)過(guò)渡組件“,而動(dòng)畫(huà)過(guò)渡組件最明顯的一個(gè)特征就是它會(huì)在內(nèi)部自管理AnimationController。我們知道,為了方便使用者可以自定義動(dòng)畫(huà)的曲線、執(zhí)行時(shí)長(zhǎng)、方向等,在前面介紹過(guò)的動(dòng)畫(huà)封裝方法中,通常都需要使用者自己提供一個(gè)AnimationController對(duì)象來(lái)自定義這些屬性值。但是,如此一來(lái),使用者就必須得手動(dòng)管理AnimationController,這又會(huì)增加使用的復(fù)雜性。因此,如果也能將AnimationController進(jìn)行封裝,則會(huì)大大提高動(dòng)畫(huà)組件的易用性。

#9.7.1 自定義動(dòng)畫(huà)過(guò)渡組件

我們要實(shí)現(xiàn)一個(gè)AnimatedDecoratedBox,它可以在decoration屬性發(fā)生變化時(shí),從舊狀態(tài)變成新?tīng)顟B(tài)的過(guò)程可以執(zhí)行一個(gè)過(guò)渡動(dòng)畫(huà)。根據(jù)前面所學(xué)的知識(shí),我們實(shí)現(xiàn)了一個(gè)AnimatedDecoratedBox1組件:

class AnimatedDecoratedBox1 extends StatefulWidget {
  AnimatedDecoratedBox1({
    Key key,
    @required this.decoration,
    this.child,
    this.curve = Curves.linear,
    @required this.duration,
    this.reverseDuration,
  });


  final BoxDecoration decoration;
  final Widget child;
  final Duration duration;
  final Curve curve;
  final Duration reverseDuration;


  @override
  _AnimatedDecoratedBox1State createState() => _AnimatedDecoratedBox1State();
}


class _AnimatedDecoratedBox1State extends State<AnimatedDecoratedBox1>
    with SingleTickerProviderStateMixin {
  @protected
  AnimationController get controller => _controller;
  AnimationController _controller;


  Animation<double> get animation => _animation;
  Animation<double> _animation;


  DecorationTween _tween;


  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animation,
      builder: (context, child){
        return DecoratedBox(
          decoration: _tween.animate(_animation).value,
          child: child,
        );
      },
      child: widget.child,
    );
  }


  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: widget.duration,
      reverseDuration: widget.reverseDuration,
      vsync: this,
    );
    _tween = DecorationTween(begin: widget.decoration);
    _updateCurve();
  }


  void _updateCurve() {
    if (widget.curve != null)
      _animation = CurvedAnimation(parent: _controller, curve: widget.curve);
    else
      _animation = _controller;
  }




  @override
  void didUpdateWidget(AnimatedDecoratedBox1 oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.curve != oldWidget.curve)
      _updateCurve();
    _controller.duration = widget.duration;
    _controller.reverseDuration = widget.reverseDuration;
    if(widget.decoration!= (_tween.end ?? _tween.begin)){
      _tween
        ..begin = _tween.evaluate(_animation)
        ..end = widget.decoration;
      _controller
        ..value = 0.0
        ..forward();
    }
  }


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

下面我們來(lái)使用AnimatedDecoratedBox1來(lái)實(shí)現(xiàn)按鈕點(diǎn)擊后背景色從藍(lán)色過(guò)渡到紅色的效果:

Color _decorationColor = Colors.blue;
var duration = Duration(seconds: 1);
...//省略無(wú)關(guān)代碼
AnimatedDecoratedBox(
  duration: duration,
  decoration: BoxDecoration(color: _decorationColor),
  child: FlatButton(
    onPressed: () {
      setState(() {
        _decorationColor = Colors.red;
      });
    },
    child: Text(
      "AnimatedDecoratedBox",
      style: TextStyle(color: Colors.white),
    ),
  ),
)

點(diǎn)擊前效果如圖9-8所示,點(diǎn)擊后截取了過(guò)渡過(guò)程的一幀如圖9-9所示: ![img] 點(diǎn)擊后,按鈕背景色會(huì)從藍(lán)色向紅色過(guò)渡,圖9-9是過(guò)渡過(guò)程中的一幀,有點(diǎn)偏紫色,整個(gè)過(guò)渡動(dòng)畫(huà)結(jié)束后背景會(huì)變?yōu)榧t色。

上面的代碼雖然實(shí)現(xiàn)了我們期望的功能,但是代碼卻比較復(fù)雜。稍加思考后,我們就可以發(fā)現(xiàn),AnimationController的管理以及 Tween 更新部分的代碼都是可以抽象出來(lái)的,如果我們這些通用邏輯封裝成基類(lèi),那么要實(shí)現(xiàn)動(dòng)畫(huà)過(guò)渡組件只需要繼承這些基類(lèi),然后定制自身不同的代碼(比如動(dòng)畫(huà)每一幀的構(gòu)建方法)即可,這樣將會(huì)簡(jiǎn)化代碼。

為了方便開(kāi)發(fā)者來(lái)實(shí)現(xiàn)動(dòng)畫(huà)過(guò)渡組件的封裝,F(xiàn)lutter 提供了一個(gè)ImplicitlyAnimatedWidget抽象類(lèi),它繼承自 StatefulWidget,同時(shí)提供了一個(gè)對(duì)應(yīng)的ImplicitlyAnimatedWidgetState類(lèi),AnimationController的管理就在ImplicitlyAnimatedWidgetState類(lèi)中。開(kāi)發(fā)者如果要封裝動(dòng)畫(huà),只需要分別繼承ImplicitlyAnimatedWidgetImplicitlyAnimatedWidgetState類(lèi)即可,下面我們演示一下具體如何實(shí)現(xiàn)。

我們需要分兩步實(shí)現(xiàn):

  1. 繼承ImplicitlyAnimatedWidget類(lèi)。

   class AnimatedDecoratedBox extends ImplicitlyAnimatedWidget {
     AnimatedDecoratedBox({
       Key key,
       @required this.decoration,
       this.child,
       Curve curve = Curves.linear, //動(dòng)畫(huà)曲線
       @required Duration duration, // 正向動(dòng)畫(huà)執(zhí)行時(shí)長(zhǎng)
       Duration reverseDuration, // 反向動(dòng)畫(huà)執(zhí)行時(shí)長(zhǎng)
     }) : super(
             key: key,
             curve: curve,
             duration: duration,
             reverseDuration: reverseDuration,
           );
     final BoxDecoration decoration;
     final Widget child;

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

其中curveduration、reverseDuration三個(gè)屬性在ImplicitlyAnimatedWidget中已定義。 可以看到AnimatedDecoratedBox類(lèi)和普通繼承自StatefulWidget的類(lèi)沒(méi)有什么不同。

  1. State類(lèi)繼承自AnimatedWidgetBaseState(該類(lèi)繼承自ImplicitlyAnimatedWidgetState類(lèi))。

   class _AnimatedDecoratedBoxState
       extends AnimatedWidgetBaseState<AnimatedDecoratedBox> {
     DecorationTween _decoration; //定義一個(gè)Tween

   
     @override
     Widget build(BuildContext context) {
       return DecoratedBox(
         decoration: _decoration.evaluate(animation),
         child: widget.child,
       );
     }

   
     @override
     void forEachTween(visitor) {
       // 在需要更新Tween時(shí),基類(lèi)會(huì)調(diào)用此方法
       _decoration = visitor(_decoration, widget.decoration,
           (value) => DecorationTween(begin: value));
     }
   }

可以看到我們實(shí)現(xiàn)了buildforEachTween兩個(gè)方法。在動(dòng)畫(huà)執(zhí)行過(guò)程中,每一幀都會(huì)調(diào)用build方法(調(diào)用邏輯在ImplicitlyAnimatedWidgetState中),所以在build方法中我們需要構(gòu)建每一幀的DecoratedBox狀態(tài),因此得算出每一幀的decoration 狀態(tài),這個(gè)我們可以通過(guò)_decoration.evaluate(animation) 來(lái)算出,其中animationImplicitlyAnimatedWidgetState基類(lèi)中定義的對(duì)象,_decoration是我們自定義的一個(gè)DecorationTween類(lèi)型的對(duì)象,那么現(xiàn)在的問(wèn)題就是它是在什么時(shí)候被賦值的呢?要回答這個(gè)問(wèn)題,我們就得搞清楚什么時(shí)候需要對(duì)_decoration賦值。我們知道_decoration是一個(gè) Tween,而 Tween的主要職責(zé)就是定義動(dòng)畫(huà)的起始狀態(tài)(begin)和終止?fàn)顟B(tài)(end)。對(duì)于AnimatedDecoratedBox來(lái)說(shuō),decoration的終止?fàn)顟B(tài)就是用戶傳給它的值,而起始狀態(tài)是不確定的,有以下兩種情況:

  1. AnimatedDecoratedBox首次 build,此時(shí)直接將其decoration值置為起始狀態(tài),即_decoration值為DecorationTween(begin: decoration)
  2. AnimatedDecoratedBoxdecoration更新時(shí),則起始狀態(tài)為_decoration.animate(animation),即_decoration值為DecorationTween(begin: _decoration.animate(animation),end:decoration)。

現(xiàn)在forEachTween的作用就很明顯了,它正是用于來(lái)更新 Tween 的初始值的,在上述兩種情況下會(huì)被調(diào)用,而開(kāi)發(fā)者只需重寫(xiě)此方法,并在此方法中更新 Tween 的起始狀態(tài)值即可。而一些更新的邏輯被屏蔽在了visitor回調(diào),我們只需要調(diào)用它并給它傳遞正確的參數(shù)即可,visitor方法簽名如下:

   Tween visitor(
     Tween<dynamic> tween, //當(dāng)前的tween,第一次調(diào)用為null
     dynamic targetValue, // 終止?fàn)顟B(tài)
     TweenConstructor<dynamic> constructor,//Tween構(gòu)造器,在上述三種情況下會(huì)被調(diào)用以更新tween
   );

可以看到,通過(guò)繼承ImplicitlyAnimatedWidgetImplicitlyAnimatedWidgetState類(lèi)可以快速的實(shí)現(xiàn)動(dòng)畫(huà)過(guò)渡組件的封裝,這和我們純手工實(shí)現(xiàn)相比,代碼簡(jiǎn)化了很多。

如果讀者還有疑惑,建議查看ImplicitlyAnimatedWidgetState的源碼并結(jié)合本示例代碼對(duì)比理解。

#動(dòng)畫(huà)過(guò)渡組件的反向動(dòng)畫(huà)

在使用動(dòng)畫(huà)過(guò)渡組件,我們只需要在改變一些屬性值后重新 build 組件即可,所以要實(shí)現(xiàn)狀態(tài)反向過(guò)渡,只需要將前后狀態(tài)值互換即可實(shí)現(xiàn),這本來(lái)是不需要再浪費(fèi)筆墨的。但是ImplicitlyAnimatedWidget構(gòu)造函數(shù)中卻有一個(gè)reverseDuration屬性用于設(shè)置反向動(dòng)畫(huà)的執(zhí)行時(shí)長(zhǎng),這貌似在告訴讀者ImplicitlyAnimatedWidget本身也提供了執(zhí)行反向動(dòng)畫(huà)的接口,于是筆者查看了ImplicitlyAnimatedWidgetState源碼并未發(fā)現(xiàn)有執(zhí)行反向動(dòng)畫(huà)的接口,唯一有用的是它暴露了控制動(dòng)畫(huà)的controller。所以如果要讓reverseDuration生效,我們只能先獲取controller,然后再通過(guò)controller.reverse()來(lái)啟動(dòng)反向動(dòng)畫(huà),比如我們?cè)谏厦媸纠幕A(chǔ)上實(shí)現(xiàn)一個(gè)循環(huán)的點(diǎn)擊背景顏色變換效果,要求從藍(lán)色變?yōu)榧t色時(shí)動(dòng)畫(huà)執(zhí)行時(shí)間為 400ms,從紅變藍(lán)為2s,如果要使reverseDuration生效,我們需要這么做:

AnimatedDecoratedBox(
  duration: Duration( milliseconds: 400),
  decoration: BoxDecoration(color: _decorationColor),
  reverseDuration: Duration(seconds: 2),
  child: Builder(builder: (context) {
    return FlatButton(
      onPressed: () {
        if (_decorationColor == Colors.red) {
          ImplicitlyAnimatedWidgetState _state =
              context.findAncestorStateOfType<ImplicitlyAnimatedWidgetState>();
           // 通過(guò)controller來(lái)啟動(dòng)反向動(dòng)畫(huà)
          _state.controller.reverse().then((e) {
            // 經(jīng)驗(yàn)證必須調(diào)用setState來(lái)觸發(fā)rebuild,否則狀態(tài)同步會(huì)有問(wèn)題
            setState(() {
              _decorationColor = Colors.blue;
            });
          });
        } else {
          setState(() {
            _decorationColor = Colors.red;
          });
        }
      },
      child: Text(
        "AnimatedDecoratedBox toggle",
        style: TextStyle(color: Colors.white),
      ),
    );
  }),
)

上面的代碼實(shí)際上是非常糟糕且沒(méi)必要的,它需要我們了解ImplicitlyAnimatedWidgetState內(nèi)部實(shí)現(xiàn),并且要手動(dòng)去啟動(dòng)反向動(dòng)畫(huà)。我們完全可以通過(guò)如下代碼實(shí)現(xiàn)相同的效果:

AnimatedDecoratedBox(
  duration: Duration(
      milliseconds: _decorationColor == Colors.red ? 400 : 2000),
  decoration: BoxDecoration(color: _decorationColor),
  child: Builder(builder: (context) {
    return FlatButton(
      onPressed: () {
        setState(() {
          _decorationColor = _decorationColor == Colors.blue
              ? Colors.red
              : Colors.blue;
        });
      },
      child: Text(
        "AnimatedDecoratedBox toggle",
        style: TextStyle(color: Colors.white),
      ),
    );
  }),
)

這樣的代碼是不是優(yōu)雅的多!那么現(xiàn)在問(wèn)題來(lái)了,為什么ImplicitlyAnimatedWidgetState要提供一個(gè)reverseDuration參數(shù)呢?筆者仔細(xì)研究了ImplicitlyAnimatedWidgetState的實(shí)現(xiàn),發(fā)現(xiàn)唯一的解釋就是該參數(shù)并非是給ImplicitlyAnimatedWidgetState用的,而是給子類(lèi)用的!原因正如我們前面說(shuō)的,要使reverseDuration 有用就必須得獲取controller 屬性來(lái)手動(dòng)啟動(dòng)反向動(dòng)畫(huà),ImplicitlyAnimatedWidgetState中的controller 屬性是一個(gè)保護(hù)屬性,定義如下:

 @protected
  AnimationController get controller => _controller;

而保護(hù)屬性原則上只應(yīng)該在子類(lèi)中使用,而不應(yīng)該像上面示例代碼一樣在外部使用。綜上,我們可以得出兩條結(jié)論:

  1. 使用動(dòng)畫(huà)過(guò)渡組件時(shí)如果需要執(zhí)行反向動(dòng)畫(huà)的場(chǎng)景,應(yīng)盡量使用狀態(tài)互換的方法,而不應(yīng)該通過(guò)獲取ImplicitlyAnimatedWidgetStatecontroller的方式。

  1. 如果我們自定義的動(dòng)畫(huà)過(guò)渡組件用不到reverseDuration ,那么最好就不要暴露此參數(shù),比如我們上面自定義的AnimatedDecoratedBox定義中就可以去除reverseDuration 可選參數(shù),如:

   class AnimatedDecoratedBox extends ImplicitlyAnimatedWidget {
     AnimatedDecoratedBox({
       Key key,
       @required this.decoration,
       this.child,
       Curve curve = Curves.linear,
       @required Duration duration,
     }) : super(
             key: key,
             curve: curve,
             duration: duration,
           );

#9.7.2 Flutter 預(yù)置的動(dòng)畫(huà)過(guò)渡組件

Flutter SDK 中也預(yù)置了很多動(dòng)畫(huà)過(guò)渡組件,實(shí)現(xiàn)方式和大都和AnimatedDecoratedBox差不多,如表9-1所示:

組件名 功能
AnimatedPadding 在 padding 發(fā)生變化時(shí)會(huì)執(zhí)行過(guò)渡動(dòng)畫(huà)到新?tīng)顟B(tài)
AnimatedPositioned 配合 Stack 一起使用,當(dāng)定位狀態(tài)發(fā)生變化時(shí)會(huì)執(zhí)行過(guò)渡動(dòng)畫(huà)到新的狀態(tài)。
AnimatedOpacity 在透明度 opacity 發(fā)生變化時(shí)執(zhí)行過(guò)渡動(dòng)畫(huà)到新?tīng)顟B(tài)
AnimatedAlign 當(dāng)alignment發(fā)生變化時(shí)會(huì)執(zhí)行過(guò)渡動(dòng)畫(huà)到新的狀態(tài)。
AnimatedContainer 當(dāng) Container 屬性發(fā)生變化時(shí)會(huì)執(zhí)行過(guò)渡動(dòng)畫(huà)到新的狀態(tài)。
AnimatedDefaultTextStyle 當(dāng)字體樣式發(fā)生變化時(shí),子組件中繼承了該樣式的文本組件會(huì)動(dòng)態(tài)過(guò)渡到新樣式。

表9-1:Flutter 預(yù)置的動(dòng)畫(huà)過(guò)渡組件

下面我們通過(guò)一個(gè)示例來(lái)感受一下這些預(yù)置的動(dòng)畫(huà)過(guò)渡組件效果:

import 'package:flutter/material.dart';


class AnimatedWidgetsTest extends StatefulWidget {
  @override
  _AnimatedWidgetsTestState createState() => _AnimatedWidgetsTestState();
}


class _AnimatedWidgetsTestState extends State<AnimatedWidgetsTest> {
  double _padding = 10;
  var _align = Alignment.topRight;
  double _height = 100;
  double _left = 0;
  Color _color = Colors.red;
  TextStyle _style = TextStyle(color: Colors.black);
  Color _decorationColor = Colors.blue;


  @override
  Widget build(BuildContext context) {
    var duration = Duration(seconds: 5);
    return SingleChildScrollView(
      child: Column(
        children: <Widget>[
          RaisedButton(
            onPressed: () {
              setState(() {
                _padding = 20;
              });
            },
            child: AnimatedPadding(
              duration: duration,
              padding: EdgeInsets.all(_padding),
              child: Text("AnimatedPadding"),
            ),
          ),
          SizedBox(
            height: 50,
            child: Stack(
              children: <Widget>[
                AnimatedPositioned(
                  duration: duration,
                  left: _left,
                  child: RaisedButton(
                    onPressed: () {
                      setState(() {
                        _left = 100;
                      });
                    },
                    child: Text("AnimatedPositioned"),
                  ),
                )
              ],
            ),
          ),
          Container(
            height: 100,
            color: Colors.grey,
            child: AnimatedAlign(
              duration: duration,
              alignment: _align,
              child: RaisedButton(
                onPressed: () {
                  setState(() {
                    _align = Alignment.center;
                  });
                },
                child: Text("AnimatedAlign"),
              ),
            ),
          ),
          AnimatedContainer(
            duration: duration,
            height: _height,
            color: _color,
            child: FlatButton(
              onPressed: () {
                setState(() {
                  _height = 150;
                  _color = Colors.blue;
                });
              },
              child: Text(
                "AnimatedContainer",
                style: TextStyle(color: Colors.white),
              ),
            ),
          ),
          AnimatedDefaultTextStyle(
            child: GestureDetector(
              child: Text("hello world"),
              onTap: () {
                setState(() {
                  _style = TextStyle(
                    color: Colors.blue,
                    decorationStyle: TextDecorationStyle.solid,
                    decorationColor: Colors.blue,
                  );
                });
              },
            ),
            style: _style,
            duration: duration,
          ),
          AnimatedDecoratedBox(
            duration: duration,
            decoration: BoxDecoration(color: _decorationColor),
            child: FlatButton(
              onPressed: () {
                setState(() {
                  _decorationColor = Colors.red;
                });
              },
              child: Text(
                "AnimatedDecoratedBox",
                style: TextStyle(color: Colors.white),
              ),
            ),
          )
        ].map((e) {
          return Padding(
            padding: EdgeInsets.symmetric(vertical: 16),
            child: e,
          );
        }).toList(),
      ),
    );
  }
}

運(yùn)行后效果如圖9-10所示:

圖9-10

讀者可以點(diǎn)擊一下相應(yīng)組件來(lái)查看一下實(shí)際的運(yùn)行效果。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)