動畫的本質
動畫,即,動起來的畫。
Flutter,達到60FPS,即16ms一幀,每一幀即一個畫面。
如一個Image,初始時width=300,height=300
第1幀:width=300,height=300
第2幀:width=295,height=295
第3幀:width=290,height=290
。。。。。。
。。。。。。
第58幀:width=10,height=10
第59幀:width=5,height=5
第60幀:width=0,height=0
在一秒內,從第1幀刷到60幀,你看到的就是Image的縮小效果。這就是動畫。
Image的寬高屬性在1秒內連續發生了變化,是縮放;
Image的位置屬性,在1秒內連續發生變化,是移動效果;
Text的字型大小屬性,在一段時間內發生連續變化,是字型大小的縮放效果;
。。。。。。
也就是說,widget的某個或某幾個屬性,在一段時間內發生連續變化,就是該widget對應屬性的動畫效果。
關鍵點:
- 作用在widget的屬性上
- 一段時間
- 連續變化的屬性值
上面的都是思路,那flutter中具體如何實現的呢?
flutter動畫的幾個類:
- AnimationController,設定在一個時間段內,會持續的產生0-1之間的值,可以控制動畫的執行,如正向執行、反向執行、重複等,也可以新增狀態監聽,如每一幀的回撥、整個動畫狀態的回撥等。
- Tween,專門設定取值範圍,AnimationController預設是0-1,通過Tween可以將這個取值範圍改變,如0-100,紅色-綠色等
- Curve,AnimationController會持續的產生一個區間的值,Curve控制的是在這個區間內,值變化的規律,比如上面一開始的例子,是線性變化,每一次固定減小5;我們也可以改變這個演算法,如依次減少60、55、50.。。。,這是一個變化放緩的效果。Flutter內建了一些效果,後面我們一一看。
你是否注意到,上面的內容,說的都是在一段時間段內如何天花亂墜的產生天花亂墜的值,跟widget一點關係都沒有。
怎麼建立關聯?
在build中:
build(){
return Widget( xxx屬性:_AnimationController.value);
}
複製程式碼
每次AnimationController產生的值發生變化後,都會最終呼叫到build方法,讓介面發生變化,最終形成連續的效果,也就是動畫。
上面的示例程式碼:
import 'package:flutter/material.dart';
class Test01AnimationController extends StatefulWidget {
@override
_Test01AnimationControllerState createState() =>
_Test01AnimationControllerState();
}
class _Test01AnimationControllerState extends State<Test01AnimationController>
with SingleTickerProviderStateMixin {
int _count = 0;
double _width = 0; // 圖片寬
double _height = 0; // 圖片高
AnimationController _controller;
@override
void initState() {
super.initState();
_count = 0; // addListener回撥的次數
// 建立一個動畫,設定持續時間duration為2000ms
_controller = new AnimationController(
vsync: this, duration: const Duration(milliseconds: 2000));
// 每次值發生變化就回撥該方法
_controller.addListener(() {
// 每次值發生變化後,就讓圖片寬高也發生變化
setState(() {
_count++;
_width = 300 * _controller.value;
_height = 300 * _controller.value;
});
});
// 動畫開始執行
_controller.forward();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("AnimationController"),
centerTitle: true,
),
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
"images/a.JPG",
width: _width,
height: _height,
),
Text("${_count} ${_controller.value}")
],
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
複製程式碼
簡短總結:
- 動畫AnimationnController,持續產生一系列的值
- 每產生一次值,就setState一次,讓該值作用到widget的屬性上
- 每次setState後,build方法重新執行
- 最終看到一個變化的widget,即動畫效果
重要的類
AnimationController
構造方法
AnimationController
AnimationController({
double? value,
this.duration,
this.reverseDuration,
this.debugLabel,
this.lowerBound = 0.0,
this.upperBound = 1.0,
this.animationBehavior = AnimationBehavior.normal,
required TickerProvider vsync,
}) : assert(lowerBound != null),
assert(upperBound != null),
assert(upperBound >= lowerBound),
assert(vsync != null),
_direction = _AnimationDirection.forward {
_ticker = vsync.createTicker(_tick);
_internalSetValue(value ?? lowerBound);
}
複製程式碼
此構造方法特點:
- 有上下值邊界
- 在動畫執行過程中持續產生上下邊界範圍內的值,通過controller.value考驗獲取到該值。
AnimationController.unbounded
AnimationController.unbounded({
double value = 0.0,
this.duration,
this.reverseDuration,
this.debugLabel,
required TickerProvider vsync,
this.animationBehavior = AnimationBehavior.preserve,
}) : assert(value != null),
assert(vsync != null),
lowerBound = double.negativeInfinity,
upperBound = double.infinity,
_direction = _AnimationDirection.forward {
_ticker = vsync.createTicker(_tick);
_internalSetValue(value);
}
複製程式碼
此構造方法特點:
- lowerBound與upperBound是無限的
- 通過controller.value 獲取到的值是Infinity.
- 因此該構造方法的意義在於定義了一個持續的時間段,但是該時間段內的動畫需要自己定義。通常用於物理模擬。
屬性
value
有兩個作用:
- 在動畫執行過程中,通過controller.value獲取當前時間點動畫產生的值。
- 在建立AnimationController時設定初始值,比如value=0.5,AnimationController預設取值範圍是0-1,那麼在動畫執行時,是從0.5開始取值,直到1。如果你設定了duration為2秒,那麼實際執行時間約為1秒。如果你設定value=1,那麼動畫不執行。也就是說,AnimationController會按照duration去產生持續變化的值,設定value後,會取value-1的這一段。
```
_controller = new AnimationController(
vsync: this,
duration: const Duration(milliseconds: 2000),
value: 0.9
);
```
複製程式碼
還有一個關鍵點,value取值在lowerBound與upperBound之間時才有效果,如果value小於lowerBound,取值從lowerBound開始;如果value大於upperBound,那麼動畫相當於直接結束,不再執行。value預設值是lowerBound。
duration
動畫持續時間 #####reverseDuration 當動畫執行revrse時的持續時間,如果不設定,那麼時間按duration計算。
lowerBound
預設0.0,設定時,lowerBound要小upperBounnd,否則報錯
upperBound
預設1.0
設定了lowerBound與upperBound的例子:
import 'package:flutter/material.dart';
class Test02AnimationController extends StatefulWidget {
@override
_Test02AnimationControllerState createState() =>
_Test02AnimationControllerState();
}
class _Test02AnimationControllerState extends State<Test02AnimationController>
with SingleTickerProviderStateMixin {
int _count = 0;
double _width = 0; // 圖片寬
double _height = 0; // 圖片高
AnimationController _controller;
@override
void initState() {
super.initState();
_count = 0; // addListener回撥的次數
// 建立一個動畫,設定持續時間duration為2000ms
_controller = new AnimationController(
vsync: this,
duration: const Duration(milliseconds: 2000),
value: 0,
lowerBound: 0,
upperBound: 20,
animationBehavior: AnimationBehavior.normal
);
// 每次值發生變化就回撥該方法
_controller.addListener(() {
// 每次值發生變化後,就讓圖片寬高也發生變化
setState(() {
_count++;
_width = 30 * _controller.value;
_height = 30 * _controller.value;
});
});
// 動畫開始執行
_controller.forward();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("AnimationController"),
centerTitle: true,
),
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
"images/a.JPG",
width: _width,
height: _height,
),
Text("${_count} ${_controller.value}")
],
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
複製程式碼
animationBehavior
注意:此處限個人理解,做個記錄,後續研究具體情形後再來更正。目前可略過此屬性不看
flutter支援很多平臺,但是有些動畫在某些平臺就是不支援或要求簡化,而AccessibilityFeatures.disableAnimations欄位就是返回的平臺支援情況,當為true時,裝置會要求flutter儘快減少或禁用動畫。有兩種方式控制:
- AnimationBehavior.normal,當使用new AnimationController()時預設此值,通過減少動畫持續時間來讓簡化動畫,讓其儘早結束。
- AnimationBehavior.preserve,當使用new AnimationController.unbounded()時預設此值,AnimationController會保留其行為,我理解的是慣性。對於重複性動畫,當不考慮AccessibilityFeatures.disableAnimations時,預設就是這種行為。
vsync
註冊螢幕的每一幀回撥監聽,防止螢幕外動畫,如鎖屏時不回撥,避免消耗不必要的資源。建立AnimationController時比須傳入此值,而AnimationController通常是在State中建立,讓State with 一個SingleTickerProviderStateMixin,傳vsync時,傳入this。
Curve
先看個效果:
Curve描述動畫過程中速度變化的過程,AnimationController預設效果是勻速的,Curve可以改變這種效果,如實現加速、減速、反彈等。實際控制的是AnimationController在duration內產生值的規則。
如何使用:
AnimationController _controller;
CurvedAnimation _curvedAnimation;
@override
void initState() {
super.initState();
_controller = new AnimationController(
vsync: this, duration: const Duration(milliseconds: 3000));
_curvedAnimation =
new CurvedAnimation(parent: _controller, curve: Curves.ease);
// 動畫開始執行
_controller.repeat();
}
複製程式碼
- 依然建立AnimationController
- 建立CurvedAnimation時,將AnimationController 和 curve傳入,通過Curses.xxx,可以獲取flutter內建的效果,當前時間點有41種。
- 依然是使用AnimationController進行動畫的控制,如repeat、forward、reverse等。
- 以後取值改為從CurvedAnimation獲取,如_curvedAnimation.value。
上面效果的程式碼:
import 'package:flutter/material.dart';
class Test05CurveImage extends StatefulWidget {
@override
_Test05CurveImageState createState() =>
_Test05CurveImageState();
}
class _Test05CurveImageState extends State<Test05CurveImage>
with SingleTickerProviderStateMixin {
double _width = 0; // 圖片寬
double _height = 0; // 圖片高
AnimationController _controller;
CurvedAnimation _curvedAnimation;
@override
void initState() {
super.initState();
_controller = new AnimationController(
vsync: this, duration: const Duration(milliseconds: 5000));
_curvedAnimation = new CurvedAnimation(parent: _controller, curve: Curves.bounceInOut);
_controller.addListener(() {
setState(() {
_width = 300 * _curvedAnimation.value;
_height = 300 * _curvedAnimation.value;
});
});
// 動畫開始執行
_controller.forward();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("AnimationController"),
centerTitle: true,
),
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
"images/a.JPG",
width: _width,
height: _height,
),
Text("_curvedAnimation.value=${_curvedAnimation.value}")
],
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
複製程式碼
Curve來源:
- flutter內建提供
- 自定義
Flutter提供的Curve效果
當前是41種,見效果:
程式碼如下:
import 'package:flutter/material.dart';
import 'dart:math';
class Test04Curve extends StatefulWidget {
@override
_Test04CurveState createState() => _Test04CurveState();
}
class _Test04CurveState extends State<Test04Curve>
with SingleTickerProviderStateMixin {
List<ItemModel> curveList;
double size = 120;
@override
void initState() {
super.initState();
curveList = [
ItemModel('linear', Curves.linear),
ItemModel('bounceIn', Curves.bounceIn),
ItemModel('bounceOut', Curves.bounceOut),
ItemModel('bounceInOut', Curves.bounceInOut),
ItemModel('ease', Curves.ease),
ItemModel('easeIn', Curves.easeIn),
ItemModel('easeInBack', Curves.easeInBack),
ItemModel('easeInCirc', Curves.easeInCirc),
ItemModel('easeInCubic', Curves.easeInCubic),
ItemModel('easeInExpo', Curves.easeInExpo),
ItemModel('easeInQuad', Curves.easeInQuad),
ItemModel('easeInQuart', Curves.easeInQuart),
ItemModel('easeInQuint', Curves.easeInQuint),
ItemModel('easeInSine', Curves.easeInSine),
ItemModel('easeInToLinear', Curves.easeInToLinear),
ItemModel('easeOut', Curves.easeOut),
ItemModel('easeOutBack', Curves.easeOutBack),
ItemModel('easeOutCirc', Curves.easeOutCirc),
ItemModel('easeOutCubic', Curves.easeOutCubic),
ItemModel('easeOutExpo', Curves.easeOutExpo),
ItemModel('easeOutQuad', Curves.easeOutQuad),
ItemModel('easeOutQuart', Curves.easeOutQuart),
ItemModel('easeOutQuint', Curves.easeOutQuint),
ItemModel('easeOutSine', Curves.easeOutSine),
ItemModel('easeInOut', Curves.easeInOut),
ItemModel('easeInOutBack', Curves.easeInOutBack),
ItemModel('easeInOutCirc', Curves.easeInOutCirc),
ItemModel('easeInOutCubic', Curves.easeInOutCubic),
ItemModel('easeInOutExpo', Curves.easeInOutExpo),
ItemModel('easeInOutQuad', Curves.easeInOutQuad),
ItemModel('easeInOutQuart', Curves.easeInOutQuart),
ItemModel('easeInOutQuint', Curves.easeInOutQuint),
ItemModel('easeInOutSine', Curves.easeInOutSine),
ItemModel('decelerate', Curves.decelerate),
ItemModel('elasticIn', Curves.elasticIn),
ItemModel('elasticOut', Curves.elasticOut),
ItemModel('elasticInOut', Curves.elasticInOut),
ItemModel('slowMiddle', Curves.slowMiddle),
ItemModel('fastLinearToSlowEaseIn', Curves.fastLinearToSlowEaseIn),
ItemModel('fastOutSlowIn', Curves.fastOutSlowIn),
ItemModel('linearToEaseOut', Curves.linearToEaseOut),
];
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Curve"),
centerTitle: true,
),
body: Container(
padding: EdgeInsets.all(10),
child: GridView.builder(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
mainAxisExtent: size + 20,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
maxCrossAxisExtent: size + 10),
itemBuilder: (context, index) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 5),
child: Column(
children: [
MyCurveWidget(curveList[index].curve, size),
Text(curveList[index].name, style: TextStyle(fontSize: 10)),
],
),
);
},
itemCount: curveList.length,
),
),
);
}
}
class MyCurveWidget extends StatefulWidget {
Curve _curve;
double _size;
MyCurveWidget(this._curve, this._size);
@override
_MyCurveWidgetState createState() => _MyCurveWidgetState();
}
class _MyCurveWidgetState extends State<MyCurveWidget>
with SingleTickerProviderStateMixin {
AnimationController _controller;
CurvedAnimation _curvedAnimation;
@override
void initState() {
super.initState();
// 建立一個動畫,設定持續時間duration為2000ms
_controller = new AnimationController(
vsync: this, duration: const Duration(milliseconds: 6000));
_curvedAnimation =
new CurvedAnimation(parent: _controller, curve: widget._curve);
// 動畫開始執行
_controller.repeat();
}
@override
Widget build(BuildContext context) {
return Container(
height: widget._size,
width: widget._size,
child: Center(
child: CustomPaint(
size: Size(widget._size - 10, widget._size - 10),
painter: MyCurvePainter(_curvedAnimation),
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
class MyCurvePainter extends CustomPainter {
Animation<double> _animation;
MyCurvePainter(this._animation) : super(repaint: _animation);
@override
void paint(Canvas canvas, Size size) {
translateToCenter(canvas, size);
drawBg(canvas, size);
drawBoll(canvas, size);
}
void drawBg(Canvas canvas, Size size) {
Paint paint = Paint()
..color = Colors.black12
..strokeWidth = 1
..style = PaintingStyle.stroke;
Offset p1 = Offset(-size.width / 2, 0);
Offset p2 = Offset(size.width / 2, 0);
canvas.drawLine(p1, p2, paint);
canvas.drawCircle(Offset.zero, size.width / 2, paint);
}
void drawBoll(Canvas canvas, Size size) {
double radius = size.width / 2;
Paint paint = Paint()
..color = Colors.orangeAccent
..style = PaintingStyle.fill;
canvas.drawCircle(
Offset(-radius + size.width * _animation.value, 0), 5, paint);
double sweepAngle = pi + pi * 2 * _animation.value;
canvas.drawCircle(
Offset(radius * cos(sweepAngle), radius * sin(sweepAngle)), 5, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
void translateToCenter(Canvas canvas, Size size) {
canvas.translate(size.width / 2, size.height / 2);
}
}
class ItemModel {
String name;
Curve curve;
ItemModel(this.name, this.curve);
}
複製程式碼
自定義Curve
繼承Curve,然後重寫transformInternal方法,自己定義變化的規律。 如:
class MyCurve extends Curve {
@override
double transformInternal(double t) {
return sin(pi / 2 * t);
}
}
複製程式碼
- t的範圍在0-1之間
- sin(pi / 2 * t) 應用t做了一個正弦函式曲線
效果如下;
程式碼如下:
import 'package:flutter/material.dart';
import 'dart:math';
class Test06CustomCurve extends StatefulWidget {
@override
_Test06CustomCurveState createState() => _Test06CustomCurveState();
}
class _Test06CustomCurveState extends State<Test06CustomCurve>
with SingleTickerProviderStateMixin {
double _width = 0; // 圖片寬
double _height = 0; // 圖片高
AnimationController _controller;
CurvedAnimation _curvedAnimation;
@override
void initState() {
super.initState();
_controller = new AnimationController(
vsync: this, duration: const Duration(milliseconds: 5000));
_curvedAnimation =
new CurvedAnimation(parent: _controller, curve: MyCurve());
_controller.addListener(() {
setState(() {
_width = 300 * _curvedAnimation.value;
_height = 300 * _curvedAnimation.value;
});
});
// 動畫開始執行
_controller.forward();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("AnimationController"),
centerTitle: true,
),
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
"images/a.JPG",
width: _width,
height: _height,
),
Text("_curvedAnimation.value=${_curvedAnimation.value}")
],
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
class MyCurve extends Curve {
@override
double transformInternal(double t) {
return sin(pi / 2 * t);
}
}
複製程式碼
Tween
AnimationController可以設定取值範圍lowerBound-upperBound,型別是double的。Tween擴充套件了取值範圍和型別,可以是顏色、int、double等。
有兩個重要的方法:
- evaluate
- animate
使用evaluate
程式碼如下;
import 'package:flutter/material.dart';
class Test07Tween extends StatefulWidget {
@override
_Test07TweenState createState() => _Test07TweenState();
}
class _Test07TweenState extends State<Test07Tween>
with SingleTickerProviderStateMixin {
double _width = 0; // 圖片寬
double _height = 0; // 圖片高
AnimationController _controller;
Tween sizeTween = new Tween(begin: 0.0, end: 300.0);
@override
void initState() {
super.initState();
_controller = new AnimationController(
vsync: this, duration: const Duration(milliseconds: 5000),upperBound: 1);
_controller.addListener(() {
setState(() {
_width = sizeTween.evaluate(_controller);
_height = sizeTween.evaluate(_controller);
});
});
// 動畫開始執行
_controller.forward();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("AnimationController"),
centerTitle: true,
),
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
"images/a.JPG",
width: _width,
height: _height,
),
Row(
children: [
Text("sizeTween.evaluate(_controller)="),
Text("${sizeTween.evaluate(_controller)}")
],
)
],
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
複製程式碼
關鍵點:
- new Tween(begin: 0.0, end: 300.0),建立Tween物件,設定取值範圍
- 正常建立controller
- sizeTween.evaluate(_controller),evaluate方法獲取當前時間點的插值
其實到這裡有個疑問,lowerBound、upperBound與Tween同時存在會怎麼樣?上面的例子upperBound=1,現在我們改成2看下效果:
相比upperBound=1,圖片放大了一倍。因此推斷是相乘的關係。
使用animate
使用Tween提供的animate方法,可以獲取一個新的Animation ,通過此也可以獲取value值,如下程式碼:
import 'package:flutter/material.dart';
class Test08TweenAnimate extends StatefulWidget {
@override
_Test08TweenAnimateState createState() => _Test08TweenAnimateState();
}
class _Test08TweenAnimateState extends State<Test08TweenAnimate>
with SingleTickerProviderStateMixin {
double _width = 0; // 圖片寬
double _height = 0; // 圖片高
AnimationController _controller;
Animation<dynamic> _animation;
Tween sizeTween = new Tween(begin: 0.0, end: 300.0);
@override
void initState() {
super.initState();
_controller = new AnimationController(
vsync: this, duration: const Duration(milliseconds: 5000));
_animation = sizeTween.animate(_controller);
_controller.addListener(() {
setState(() {
_width = _animation.value;
_height = _animation.value;
});
});
// 動畫開始執行
_controller.forward();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("AnimationController"),
centerTitle: true,
),
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
"images/a.JPG",
width: _width,
height: _height,
),
Row(
children: [
Text("_animation.value="),
Text("${_animation.value}")
],
)
],
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
複製程式碼
關鍵點:
- 建立Tween:Tween sizeTween = new Tween(begin: 0.0, end: 300.0)
- 與controller關聯: _animation = sizeTween.animate(_controller);
- 使用:_animation.value獲取插值
- 注意依然是使用_controller來控制動畫的開始、重複等
Interval 組合動畫
Interval是Curve的子類,定義了一個區間範圍[0,1]內取值,可以是[0.1,0.3],也可以是[0.4,0.8]。我們都知道,AnimationController整體描述的是一個動畫過程,看做是[0,1],Interval則在[0,1]內選擇了一塊區域,如[0.3,0.5],當Tween在animate時指定該Interval後,表示該Tween在0.3時開始,0.5時結束。
舉個例子,一個正方形,在動畫的0-0.6完成大小變化,0.3-1.0完成顏色變化。 效果:
程式碼:
import 'package:flutter/material.dart';
class Test29Interval extends StatefulWidget {
@override
_State createState() => _State();
}
class _State extends State<Test29Interval>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Tween _sizeTween = new Tween<double>(begin: 0.0, end: 200.0);
Tween _colorTween = new ColorTween(begin: Colors.orangeAccent, end: Colors.green);
Animation<double> _animationSize;
Animation<Color> _animationColor;
@override
void initState() {
super.initState();
_controller = new AnimationController(
vsync: this,
duration: const Duration(milliseconds: 6000),
);
_animationSize = _sizeTween.animate(new CurvedAnimation(
parent: _controller, curve: new Interval(0.0, 0.6)));
_animationColor = _colorTween.animate(new CurvedAnimation(
parent: _controller, curve: new Interval(0.3, 1.0)));
_controller.addListener(() {
setState(() {});
});
// 動畫開始執行
_controller.forward();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Test29Interval"),
centerTitle: true,
),
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: _animationSize.value,
height: _animationSize.value,
margin: EdgeInsets.all(10),
color: _animationColor.value,
),
Container(
width: 400,
child: Text("_animationSize.value = ${_animationSize.value}",style: TextStyle(fontSize: 14),),
)
],
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
複製程式碼
AnimatedWidget
大家應該注意到了,上面所有的動畫變化都是通過setState來維護的,對於一個通用功能,如果每次都使用setState,顯然比較繁瑣。因此,Flutter提供了AnimatedWidget,在使用時就不用顯示的呼叫setState了。
看個例子,效果:
程式碼:
import 'package:flutter/material.dart';
class Test30AnimatiedWidget extends StatefulWidget {
@override
_State createState() => _State();
}
class _State extends State<Test30AnimatiedWidget>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> animation;
@override
void initState() {
// TODO: implement initState
super.initState();
controller = new AnimationController(
duration: const Duration(milliseconds: 6000), vsync: this);
animation = new Tween(begin: 0.0, end: 300.0).animate(controller);
controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
});
controller.forward();
}
@override
Widget build(BuildContext context) {
return ImageAnimatiedWidget(
animation: animation,
);
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
controller.dispose();
}
}
class ImageAnimatiedWidget extends AnimatedWidget {
@override
Widget build(BuildContext context) {
Animation animation = listenable;
return Center(
child: Container(
child: Image.asset(
"images/a2.png",
width: animation.value,
height: animation.value,
),
// width: animation.value,
// height: animation.value,
),
);
}
ImageAnimatiedWidget({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
}
複製程式碼
AnimatedBuilder
同AnimatedWidget類似,隱了setState,提供了另一種widget組織方式。
同上面的例子,效果:
程式碼:
import 'package:flutter/material.dart';
class Test31AnimateBuilder extends StatefulWidget {
@override
_State createState() => _State();
}
class _State extends State<Test31AnimateBuilder>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> animation;
@override
void initState() {
// TODO: implement initState
super.initState();
controller = new AnimationController(
duration: const Duration(milliseconds: 6000), vsync: this);
animation = new Tween(begin: 0.0, end: 300.0).animate(controller);
controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
});
controller.forward();
}
@override
Widget build(BuildContext context) {
return ImageAnimateBuilder(animation, MyImage());
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
controller.dispose();
}
}
class ImageAnimateBuilder extends StatelessWidget {
final Animation animation;
final Widget child;
ImageAnimateBuilder(this.animation, this.child);
@override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: animation,
child: child,
builder: (BuildContext context, Widget child) {
return Container(
width: animation.value,
height: animation.value,
child: child,
);
},
),
);
}
}
class MyImage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Container(
child: Image.asset(
"images/a2.png",
),
// width: animation.value,
// height: animation.value,
),
);
}
}
複製程式碼
AnimatedSwitcher
這是flutter提供的一個特殊的widget,效果是對於AnimatedSwitcher的child,當發生變化時,舊值執行去的動畫,新值執行來的動畫。去和來,指的是同一個動畫的forward與reverse。
看一個例子,數字發生變化,當前數字逐漸縮小隱藏,新的數字是放大的過程。
效果:
程式碼:
import 'package:flutter/material.dart';
class Test32AnimatedSwitcher extends StatefulWidget {
const Test32AnimatedSwitcher({Key key}) : super(key: key);
@override
_State createState() => _State();
}
class _State extends State<Test32AnimatedSwitcher> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedSwitcher(
duration: const Duration(milliseconds: 1000),
transitionBuilder: (Widget child, Animation<double> animation) {
//執行縮放動畫
return ScaleTransition(child: child, scale: animation);
},
child: Text(
'$_count',
//顯示指定key,不同的key會被認為是不同的Text,這樣才能執行動畫
key: ValueKey<int>(_count),
style: TextStyle(fontSize: 30),
),
),
RaisedButton(
child: const Text('+1',),
onPressed: () {
setState(() {
_count += 1;
});
},
),
],
),
);
}
}
複製程式碼