Flutter開發日記——Flutter動畫&Motion Widget詳解(上)

YYDev發表於2019-11-07

本篇文章已授權微信公眾號 YYGeeker 獨家釋出轉載請標明出處

AnimatedContainer

1、簡介

  • AnimatedContainer表示一個動畫容器,只要更改容器的值,就能表現出對應的動畫效果

2、建構函式

AnimatedContainer({
    Key key,
    this.alignment,
    this.padding,
    Color color,
    Decoration decoration,
    this.foregroundDecoration,
    double width,
    double height,
    BoxConstraints constraints,
    this.margin,
    this.transform,
    this.child,
    Curve curve = Curves.linear,
    @required Duration duration,
})
複製程式碼
  • alignment
    • alignment表示子元素child相對於容器的對其方式
    • child在AnimatedContainer中預設位於居中位置
    • 用座標表示child當前的aligament方式,則為(0,0)
    • 用座標表示AnimatedContainer的四個頂點,則為(-1,-1)(-1,1)(1,-1)(1,1)
    • 通過AlignmentDirectional控制child在X、Y軸的偏移,
  • padding:可以對子元素child進行內邊距位置偏移
  • color:容器的背景色,通過decoration也能設定背景色,兩者不可共存
  • decoration:容器的邊框修飾,通過color也能設定背景色,兩者不可共存
  • foregroundDecoration:容器的前景邊框修飾,在這裡做邊框修飾,則會擋住decorationcolor的顏色
  • width:容器的寬度
  • height:容器的高度
  • constraints:容器的大小約束,可以指定最小寬高和最大寬高,整個容器遵循這個約束
  • margin:容器的外邊距
  • transform:容器的Matrix變換,可以進行矩陣的旋轉,縮放,運算等操作
  • child:子元素在容器中的位置預設是居中顯示
  • curve:容器的動畫插值器
  • duration:容器的動畫時長

3、例子

通過定時器改變容器的大小,邊框,邊距等屬性,讓容器動起來

var time = 0;
var _color = Colors.red[200];
var _borderColor = Colors.transparent;
var _width = 200.0;
var _height = 200.0;
var _borderWidth = 0.0;
var _scaleX = 1.0;
var _scaleY = 1.0;
var _rotate = 0.0;
var _padding = 0.0;
var _margin = 0.0;
var _alignmentY = 0.0;

class WeWidgetState extends State<WeWidget> {
  WeWidgetState() {
    Timer.periodic(Duration(milliseconds: 1000), (timer) {
      setState(() {
        switch (time % 10) {
          case 0:
            _width = 300;
            _height = 100;
            break;
          case 1:
            _width = 100;
            _height = 300;
            _borderWidth = 4.0;
            _borderColor = Colors.brown[200];
            break;
          case 2:
            _borderWidth = 8.0;
            _borderColor = Colors.pink[200];
            _color = Colors.blue[200];
            break;
          case 3:
            _width = 300;
            _height = 300;
            _color = Colors.deepPurple[200];
            break;
          case 4:
            _scaleX = 0.2;
            _scaleY = 0.2;
            break;
          case 5:
            _scaleX = 0.5;
            _scaleY = 0.5;
            _rotate = math.pi / 6;
            break;
          case 6:
            _scaleX = 1.0;
            _scaleY = 1.0;
            _rotate = 0.0;
            _padding = 200.0;
            break;
          case 7:
            _padding = 0.0;
            break;
          case 8:
            _margin = 80.0;
            _alignmentY = 0.5;
            break;
          case 9:
            _margin = 0.0;
            _alignmentY = 0.0;
            break;
        }
        time++;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("day6"),
      ),
      resizeToAvoidBottomPadding: false,
      body: _buildColumn(),
    );
  }

  Widget _buildColumn() {
    return Column(
      children: <Widget>[
        AnimatedContainer(
          transform: Matrix4.identity().scaled(_scaleX, _scaleY)
            ..rotateZ(_rotate),
          alignment: AlignmentDirectional(0.0, _alignmentY),
          constraints: BoxConstraints(
            minWidth: 0.0,
            minHeight: 0.0,
            maxWidth: 500.0,
            maxHeight: 500.0,
          ),
          margin: EdgeInsets.only(left: _margin),
          padding: EdgeInsets.only(left: _padding),
          width: _width,
          height: _height,
          duration: Duration(milliseconds: 1000),
          curve: Curves.fastOutSlowIn,
          child: Icon(
            Icons.android,
            color: Colors.lightGreenAccent,
            size: 40,
          ),
          foregroundDecoration: BoxDecoration(
            border: Border.all(
              color: _borderColor,
              width: _borderWidth,
            ),
          ),
          //color 和 decoration兩者不可共存
          //color: _color,
          decoration: BoxDecoration(
            color: _color,
            borderRadius: BorderRadius.circular(12),
            boxShadow: [
              BoxShadow(
                color: _color,
                offset: Offset(5.0, 5.0),
                blurRadius: 6.0,
              )
            ],
          ),
        ),
      ],
    );
  }
}
複製程式碼

在這裡插入圖片描述

AnimatedCrossFade

1、簡介

  • AnimatedCrossFade存放著兩個動畫的容器,只要切換動畫的狀態,就能表現出對應的動畫效果
  • AnimatedCrossFade本質就是一個Stack分別存放有兩個元件和兩個動畫

2、建構函式

const AnimatedCrossFade({
    Key key,
    @required this.firstChild,
    @required this.secondChild,
    this.firstCurve = Curves.linear,
    this.secondCurve = Curves.linear,
    this.sizeCurve = Curves.linear,
    this.alignment = Alignment.topCenter,
    @required this.crossFadeState,
    @required this.duration,
    this.layoutBuilder = defaultLayoutBuilder,
}) 
複製程式碼
  • firstChild:第一個動畫元素的控制元件
  • secondChild:第二個動畫元素的控制元件
  • firstCurve:第一個動畫元素的插值器
  • secondCurve:第二個動畫元素的插值器
  • sizeCurve:動畫切換時候的尺寸變化插值器
  • alignment:動畫在切換到第二個狀態的時候,當前alignment的引數會應用在第二個動畫元素中
  • crossFadeState:動畫當前的狀態,當狀態變化時,動畫也會隨之切換到對應的元素上
  • duration:動畫時長
  • layoutBuilder:動畫布局的構造器,可以構建兩個動畫元素之間的佈局關係

3、例子

通過定時器更改變數的值,讓控制元件切換佈局,從而開啟動畫

var time = 0;
var _first = true;

class WeWidgetState extends State<WeWidget> {
  WeWidgetState() {
    Timer.periodic(Duration(seconds: 1), (timer) {
      setState(() {
        switch (time % 4) {
          case 0:
            _first = false;
            break;
          case 2:
            _first = true;
            break;
        }
        time++;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("day7"),
      ),
      body: _buildColumn(),
    );
  }

  Widget _buildColumn() {
    return Column(
      children: <Widget>[
        AnimatedCrossFade(
          //layoutBuilder: ,
          alignment: AlignmentDirectional(0.0, 1.0),
          duration: Duration(seconds: 1),
          firstCurve: Curves.fastOutSlowIn,
          secondCurve: Curves.fastOutSlowIn,
          sizeCurve: Curves.fastOutSlowIn,
          firstChild: FlutterLogo(
            style: FlutterLogoStyle.horizontal,
            size: 100.0,
          ),
          secondChild: FlutterLogo(
            style: FlutterLogoStyle.stacked,
            size: 200.0,
          ),
          crossFadeState:
              _first ? CrossFadeState.showFirst : CrossFadeState.showSecond,
        ),
      ],
    );
  }
}
複製程式碼

在這裡插入圖片描述

Hero

1、簡介

  • Hero控制元件屬於Android裡的共享元素動畫,它可以在不同的頁面跳轉時,複用同一個控制元件,且帶有動畫效果

2、建構函式

const Hero({
    Key key,
    @required this.tag,
    this.createRectTween,
    this.flightShuttleBuilder,
    this.placeholderBuilder,
    this.transitionOnUserGestures = false,
    @required this.child,
})
複製程式碼
  • tag:共享元素的Tag
  • createRectTween:定義目標從起點到終點的邊界變化動畫
  • flightShuttleBuilder:自定義跳轉時候運動軌跡動畫的控制元件
  • placeholderBuilder:自定義跳轉時候的佔位符
  • transitionOnUserGestures:支援iOS的返回滑動手勢
  • child:子控制元件

3、例子

設定點選加號圖示跳轉到太陽圖示的頁面,在跳轉的時候用相機快門圖示做軌跡運動,同時會有載入佔位符

class WeWidgetState extends State<WeWidget> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("day8"),
      ),
      body: GestureDetector(
        child: _buildColumn(),
        onTap: () {
          _pushToNewPage();
        },
      ),
    );
  }

  Widget _buildColumn() {
    return Hero(
      tag: "mmm",
      transitionOnUserGestures: true,
      placeholderBuilder: (context, size, widget) {
        return CircularProgressIndicator();
      },
      flightShuttleBuilder: (flightContext, animation, flightDirection,
          fromHeroContext, toHeroContext) {
        return Icon(
          Icons.camera,
          size: 70.0,
        );
      },
      child: Icon(
        Icons.add,
        size: 70.0,
      ),
    );
  }

  void _pushToNewPage() {
    Navigator.of(context).push(
      MaterialPageRoute(builder: (context) {
        return Scaffold(
            appBar: AppBar(
              title: Text('Hero'),
            ),
            body: Center(
              child: Hero(
                tag: "mmm",
                child: Icon(
                  Icons.wb_sunny,
                  size: 70.0,
                ),
              ),
            ));
      }),
    );
  }
}
複製程式碼

AnimatedBuilder

1、簡介

  • AnimatedBuilder表示一個動畫的構造器,可以通過控制器去控制動畫值改變,從而控制動畫

2、建構函式

AnimatedBuilder({
    Key key,
    Listenable animation,
    this.builder,
    this.child,
})
複製程式碼
  • animation:表示由外傳遞進來的動畫屬性值的變化
  • builder:動畫的控制器
  • child:子控制元件

3、例子

我們可以將常用屬性包裝成一個控制元件

class AnimatorTransition extends StatelessWidget {
  final Widget child;
  final Animation<num> animation;

  AnimatorTransition({this.child, this.animation});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: AnimatedBuilder(
        child: this.child,
        animation: animation,
        builder: (BuildContext context, Widget child) {
          return Container(
            height: animation.value,
            width: animation.value,
            child: child,
          );
        },
      ),
    );
  }
}
複製程式碼

動畫屬性值的變化需要AnimationController來控制,通過CurvedAnimation設定其插值器,通過Tween設定其值的變化範圍

class WeWidgetState extends State<WeWidget>
    with SingleTickerProviderStateMixin {
    
  //嘗試擴充套件或實現num時,除int或double之外的任何型別都是編譯時錯誤
  Animation<num> _animation;
  AnimationController _controller;
  Animation _curve;

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

    //動畫控制器
    _controller = AnimationController(
      duration: const Duration(milliseconds: 3000),
      vsync: this,
    );
    //動畫插值器
    _curve = CurvedAnimation(parent: _controller, curve: Curves.fastOutSlowIn);
    //動畫變化範圍
    _animation = Tween(begin: 0.0, end: 300.0).animate(_curve);
    //啟動動畫
    _controller.forward();
  }
}
複製程式碼

通過addStatusListener監聽動畫狀態的變化和通過addListener監聽動畫值的變化

class WeWidgetState extends State<WeWidget>
    with SingleTickerProviderStateMixin {
  //嘗試擴充套件或實現num時,除int或double之外的任何型別都是編譯時錯誤
  Animation<num> _animation;
  AnimationController _controller;
  Animation _curve;

  double _animationValue;
  AnimationStatus _state;

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

    //動畫控制器
    _controller = AnimationController(
      duration: const Duration(milliseconds: 3000),
      vsync: this,
    );
    //動畫插值器
    _curve = CurvedAnimation(parent: _controller, curve: Curves.fastOutSlowIn);
    //動畫變化範圍
    _animation = Tween(begin: 0.0, end: 300.0).animate(_curve)
      ..addListener(() {
        setState(() {
          //記錄變化的值
          _animationValue = _animation.value;
        });
      })
      ..addStatusListener((AnimationStatus state) {
        //如果動畫已完成,就反轉動畫
        if (state == AnimationStatus.completed) {
          _controller.reverse();
        } else if (state == AnimationStatus.dismissed) {
        //如果動畫已經消失,則開始動畫
          _controller.forward();
        }

        setState(() {
          _state = state;
        });
      });
    //啟動動畫
    _controller.forward();
  }
}
複製程式碼

原始碼

class WeWidgetState extends State<WeWidget>
    with SingleTickerProviderStateMixin {
  //嘗試擴充套件或實現num時,除int或double之外的任何型別都是編譯時錯誤
  Animation<num> _animation;
  AnimationController _controller;
  Animation _curve;

  double _animationValue;
  AnimationStatus _state;

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

    _controller = AnimationController(
      duration: const Duration(milliseconds: 3000),
      vsync: this,
    );
    _curve = CurvedAnimation(parent: _controller, curve: Curves.fastOutSlowIn);
    _animation = Tween(begin: 0.0, end: 300.0).animate(_curve)
      ..addListener(() {
        setState(() {
          _animationValue = _animation.value;
        });
      })
      ..addStatusListener((AnimationStatus state) {
        if (state == AnimationStatus.completed) {
          _controller.reverse();
        } else if (state == AnimationStatus.dismissed) {
          _controller.forward();
        }

        setState(() {
          _state = state;
        });
      });
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("day10"),
      ),
      body: _buildColumn(),
    );
  }

  Widget _buildColumn() {
    return Column(
      children: <Widget>[
        AnimatorTransition(
          child: FlutterLogo(
            style: FlutterLogoStyle.horizontal,
          ),
          animation: _animation,
        ),
        Text("動畫值:" + _animationValue.toString()),
        Text("動畫狀態:" + _state.toString()),
      ],
    );
  }

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

class AnimatorTransition extends StatelessWidget {
  final Widget child;
  final Animation<num> animation;

  AnimatorTransition({this.child, this.animation});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: AnimatedBuilder(
        child: this.child,
        animation: animation,
        builder: (BuildContext context, Widget child) {
          return Container(
            height: animation.value,
            width: animation.value,
            child: child,
          );
        },
      ),
    );
  }
}
複製程式碼

在這裡插入圖片描述

DecoratedBoxTransition

1、簡介

  • DecoratedBoxTransition表示一個邊框動畫,可以通過控制器去控制動畫邊框值的改變,從而控制動畫邊框

2、建構函式

DecoratedBoxTransition({
    Key key,
    this.decoration,           
    this.position,             
    this.child,               
})
複製程式碼
  • decoration:動畫屬性值的變化,注意這裡的型別是Animation<Decoration>
  • position:動畫的控制器
  • child:子控制元件

3、例子

class AnimatorTransition extends StatelessWidget {
  final Widget child;
  final Animation<Decoration> animation;

  AnimatorTransition({this.child, this.animation});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: DecoratedBoxTransition(
        position: DecorationPosition.background,
        decoration: animation,
        child: Container(
          child: this.child,
        ),
      ),
    );
  }
}
複製程式碼

在這裡插入圖片描述

FadeTransition

1、簡介

  • FadeTransition表示一個透明度動畫,可以通過控制器去控制動畫透明度值的改變,從而控制動畫的透明度

2、建構函式

FadeTransition({
    Key key,
    this.opacity,
    this.alwaysIncludeSemantics,
    Widget child,
})
複製程式碼
  • opacity:動畫屬性值的變化,注意這裡的型別是Animation<num>
  • alwaysIncludeSemantics:是否包含子語義而不管不透明度
  • child:子控制元件

3、例子

class AnimatorTransition extends StatelessWidget {
  final Widget child;
  final Animation<num> animation;

  AnimatorTransition({this.child, this.animation});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: FadeTransition(
        //是否包含子語義而不管不透明度
        alwaysIncludeSemantics: false,
        opacity: animation,
        child: this.child,
      ),
    );
  }
}
複製程式碼

在這裡插入圖片描述

PositionedTransition

1、簡介

  • PositionedTransition表示一個矩形位置的動畫,可以通過控制器去控制動畫矩形值的改變,從而控制動畫的矩形位置

2、建構函式

PositionedTransition({
    Key key,
    this.rect,
    Widget child,
})
複製程式碼
  • rect:動畫屬性值的變化,注意這裡的型別是Animation<RelativeRect>
  • child:子控制元件

3、例子

這裡需要注意的是PositionedTransition的父控制元件必須是Stack

class AnimatorTransition extends StatelessWidget {
  final Widget child;
  final Animation<RelativeRect> animation;

  AnimatorTransition({this.child, this.animation});

  @override
  Widget build(BuildContext context) {
    //絕對定位的動畫實現, 需要Stack包裹
    return Stack(
      children: <Widget>[
        PositionedTransition(
          rect: animation,
          child: this.child,
        ),
      ],
    );
  }
}
複製程式碼

在這裡插入圖片描述

RotationTransition

1、簡介

  • RotationTransition表示一個旋轉動畫,可以通過控制器去控制動畫旋轉值的改變,從而控制動畫的旋轉

2、建構函式

RotationTransition({
    Key key,
    this.turns,
    this.alignment,
    Widget child,
})
複製程式碼
  • turns:動畫旋轉值的變化,注意這裡的型別是Animation<num>
  • alignment:旋轉的錨定座標
  • child:子控制元件

3、例子

class AnimatorTransition extends StatelessWidget {
  final Widget child;
  final Animation<num> animation;

  AnimatorTransition({this.child, this.animation});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: RotationTransition(
        //旋轉的錨定座標
        alignment: Alignment.center,
        turns: animation,
        child: this.child,
      ),
    );
  }
}
複製程式碼

在這裡插入圖片描述

ScaleTransition

1、簡介

  • ScaleTransition表示一個縮放動畫,可以通過控制器去控制動畫縮放值的改變,從而控制動畫的縮放

2、建構函式

ScaleTransition({
    Key key,
    this.scale,
    this.alignment,
    Widget child,
})
複製程式碼
  • scale:動畫屬性值的變化,注意這裡的型別是Animation<num>
  • alignment:旋轉的錨定座標
  • child:子控制元件

3、例子

class AnimatorTransition extends StatelessWidget {
  final Widget child;
  final Animation<num> animation;

  AnimatorTransition({this.child, this.animation});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: ScaleTransition(
        //縮放的錨定座標
        alignment: Alignment.topLeft,
        scale: animation,
        child: this.child,
      ),
    );
  }
}
複製程式碼

在這裡插入圖片描述

SizeTransition

1、簡介

  • SizeTransition表示一個尺寸動畫,可以通過控制器去控制動畫尺寸值的改變,從而控制動畫的尺寸

2、建構函式

SizeTransition({
    Key key,
    this.sizeFactor,
    this.axis,
    this.axisAlignment,
    Widget child,
})
複製程式碼
  • sizeFactor:動畫屬性值的變化,注意這裡的型別是Animation<num>
  • axis:表示動畫出現的方式
    • Axis.vertical:垂直方向
    • Axis.horizontal:橫軸方向
  • axisAlignment:表示動畫出現的原始位置偏移量,如果是在垂直方向指的是y,如果是橫軸方向指的是x
  • child:子控制元件

3、例子

class AnimatorTransition extends StatelessWidget {
  final Widget child;
  final Animation<num> animation;
  final Axis axis;

  AnimatorTransition({this.child, this.animation, this.axis = Axis.vertical});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: SizeTransition(
        axisAlignment: 2.0,
        axis: axis,
        sizeFactor: animation,
        child: this.child,
      ),
    );
  }
}
複製程式碼

在這裡插入圖片描述

SlideTransition

1、簡介

  • SlideTransition表示一個平移動畫,可以通過控制器去控制動畫尺寸值的改變,從而控制動畫的平移位置

2、建構函式

SlideTransition({
    Key key,
    this.position,
    this.transformHitTests,
    this.textDirection,
    Widget child,
})
複製程式碼
  • position:動畫屬性值的變化,注意這裡的型別是Animation<Offset>
  • transformHitTests:表示點選事件是否落在動畫後的控制元件上
  • textDirection:表示動畫執行的位置關係
    • TextDirection.rtl:左到右
    • TextDirection.ltr:右到左
  • child:子控制元件
class AnimatorTransition extends StatelessWidget {
  final Widget child;
  final Animation<Offset> animation;

  AnimatorTransition({this.child, this.animation});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: SlideTransition(
        transformHitTests: true,
        textDirection: TextDirection.rtl,
        position: animation,
        child: this.child,
      ),
    );
  }
}
複製程式碼

在這裡插入圖片描述

作者

Flutter開發日記——Flutter動畫&Motion Widget詳解(上)
Hensen_

相關文章