Flutter 隱式動畫
官方文件: flutter.dev/docs/codela…
一、什麼是隱式動畫
我們平時使用的 AnimatonController 來控制的動畫,需要我們指定動畫的執行時間,動畫的運動曲線,並手動控制動畫的開始和結束,這其實是顯式動畫,與之對應的就是隱式動畫。顧名思義,隱式動畫其實不需要我們對動畫做太多的干預,直接使用 Flutter 內部定義好的動畫元件,就可以實現簡單的動畫效果。
常見的隱式動畫元件有 AnimatedOpacity , AnimatedContainer, AnimatedPadding, AnimatedPositioned, AnimatedSwitcher 以及 AnimatedAlign 等,通過這些元件的名字也能看出來這些元件的作用,接下來簡單介紹一下如何使用隱式動畫元件。
二、AnimatedOpacity 實現漸隱效果
1、AnimatedOpacity 介紹
AnimatedOpacity 這個 Widget 可以實現透明度變化的動畫效果。
先看一下 AnimatedOpacity 的建構函式。
const AnimatedOpacity({
Key key,
this.child,
@required this.opacity,
Curve curve = Curves.linear,
@required Duration duration,
VoidCallback onEnd,
this.alwaysIncludeSemantics = false,
}) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0),
super(key: key, curve: curve, duration: duration, onEnd: onEnd);
複製程式碼
這些引數基本上看一眼就知道是幹嘛的了。
- child : 子控制元件,也就是動畫的作用物件
- opacity : 指定透明度
- curve: 動畫的變換曲線,預設是線性變換
- duration: 動畫時間
- onEnd : 結束回撥
可以看得出,和我們平時正常使用 Widget 其實沒有任何區別,但是卻大大簡化的動畫的使用。
2、示例
class FadeInDemo extends StatefulWidget {
_FadeInDemoState createState() => _FadeInDemoState();
}
class _FadeInDemoState extends State<FadeInDemo> {
double opacity = 0.0;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.network(
'https://picsum.photos/250?image=9',
),
MaterialButton(
child: Text(
'Show Details',
style: TextStyle(color: Colors.blueAccent),
),
onPressed: () => setState(() {
opacity = 1;
}),
),
AnimatedOpacity(
duration: Duration(seconds: 3),
opacity: opacity,
child: Column(
children: <Widget>[
Text('Type: Owl'),
Text('Age: 39'),
Text('Employment: None'),
],
),
)
]);
}
}
複製程式碼
使用 AnimatedOpcacity 需要指定初始的透明度值, 0 為不可見,當點選按鈕,設定為 1,同時 setState 將會觸發動畫效果。duration 指定了動畫時間為 3s 。
效果:
三、 AnimatedContainer 實現多屬性改變動畫
1、AnimatedContainer 介紹
AnimatedContainer 這個 Widget 和 Container 一樣,是同一個可以控制多個屬性(如 margin、borderRaidu、color)的 widget ,同時還能加上動畫。
建構函式如下:
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,
VoidCallback onEnd,
}) : assert(margin == null || margin.isNonNegative),
assert(padding == null || padding.isNonNegative),
assert(decoration == null || decoration.debugAssertIsValid()),
assert(constraints == null || constraints.debugAssertIsValid()),
assert(color == null || decoration == null,
'Cannot provide both a color and a decoration\n'
'The color argument is just a shorthand for "decoration: BoxDecoration(color: color)".'
),
decoration = decoration ?? (color != null ? BoxDecoration(color: color) : null),
constraints =
(width != null || height != null)
? constraints?.tighten(width: width, height: height)
?? BoxConstraints.tightFor(width: width, height: height)
: constraints,
super(key: key, curve: curve, duration: duration, onEnd: onEnd);
複製程式碼
和正常的 Container 使用沒有什麼區別,就是加上了動畫相關的幾個屬性。
2、示例
const _duration = Duration(milliseconds: 400);
double randomBorderRadius() {
return Random().nextDouble() * 64;
}
double randomMargin() {
return Random().nextDouble() * 64;
}
Color randomColor() {
return Color(0xFFFFFFFF & Random().nextInt(0xFFFFFFFF));
}
class AnimatedContainerDemo extends StatefulWidget {
_AnimatedContainerDemoState createState() => _AnimatedContainerDemoState();
}
class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
Color color;
double borderRadius;
double margin;
@override
void initState() {
super.initState();
color = Colors.deepPurple;
borderRadius = randomBorderRadius();
margin = randomMargin();
}
void change() {
setState(() {
color = randomColor();
borderRadius = randomBorderRadius();
margin = randomMargin();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(
width: 128,
height: 128,
child: AnimatedContainer(
margin: EdgeInsets.all(margin),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(borderRadius),
),
duration: _duration,
curve: Curves.easeInOutBack,
),
),
MaterialButton(
color: Theme.of(context).primaryColor,
child: Text(
'change',
style: TextStyle(color: Colors.white),
),
onPressed: () => change(),
),
],
),
),
);
}
}
複製程式碼
上面示例的程式碼可以動態的改變 Container 的 margin,borderRaidus 以及 color 屬性。 效果:
四、AnimatedSwitcher 實現切換動畫效果
AnimatedSwitcher 的詳細的介紹和原理可以參考 Flutter 中文網—通用“動畫切換”元件
1、AnimatedSwitcher 介紹
AnimatedSwitcher 可以同時對其新、舊子元素新增顯示、隱藏動畫。也就是說在AnimatedSwitcher的子元素髮生變化時,會對其舊元素和新元素。 其建構函式如下:
const AnimatedSwitcher({
Key key,
this.child,
@required this.duration, // 新child顯示動畫時長
this.reverseDuration,// 舊child隱藏的動畫時長
this.switchInCurve = Curves.linear, // 新child顯示的動畫曲線
this.switchOutCurve = Curves.linear,// 舊child隱藏的動畫曲線
this.transitionBuilder = AnimatedSwitcher.defaultTransitionBuilder, // 動畫構建器
this.layoutBuilder = AnimatedSwitcher.defaultLayoutBuilder, //佈局構建器
})
複製程式碼
當AnimatedSwitcher的child發生變化時(型別或Key不同),舊child會執行隱藏動畫,新child會執行執行顯示動畫。究竟執行何種動畫效果則由transitionBuilder引數決定,該引數接受一個AnimatedSwitcherTransitionBuilder型別的builder,定義如下:
typedef AnimatedSwitcherTransitionBuilder =
Widget Function(Widget child, Animation<double> animation);
複製程式碼
該builder在AnimatedSwitcher的child切換時會分別對新、舊child繫結動畫:
對舊child,繫結的動畫會反向執行(reverse) 對新child,繫結的動畫會正向指向(forward) 這樣一下,便實現了對新、舊child的動畫繫結。AnimatedSwitcher的預設值是AnimatedSwitcher.defaultTransitionBuilder :
Widget defaultTransitionBuilder(Widget child, Animation<double> animation) {
return FadeTransition(
opacity: animation,
child: child,
);
}
複製程式碼
可以看到,返回了FadeTransition物件,也就是說預設情況,AnimatedSwitcher會對新舊child執行“漸隱”和“漸顯”動畫。
2、示例
class AnimatedSwitcherCounterRoute extends StatefulWidget {
const AnimatedSwitcherCounterRoute({Key key}) : super(key: key);
@override
_AnimatedSwitcherCounterRouteState createState() => _AnimatedSwitcherCounterRouteState();
}
class _AnimatedSwitcherCounterRouteState extends State<AnimatedSwitcherCounterRoute> {
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);
///上下左右平移動畫
// return SlideTransitionX(
// child: child,
// direction: AxisDirection.up, //上入下出
// position: 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;
});
},
),
],
),
);
}
}
複製程式碼
上面的 transitionBuilder 通過指定不同的返回值型別,可以控制不同的動畫效果。
縮放:
平移:
推薦閱讀
歡迎關注「Flutter 程式設計開發」微信公眾號 。