在flutter中,咱們能夠經過 AnimationController 及各類 Animation 搭配使用的方式去實現 Widget 的動畫。git
實現的方式也很是方便,經過flutter內置好的模版代碼,在你建立的dart文件中輸入 sta
便可建立出基本的動畫模版類。github
那麼,咱們能夠經過這樣的Widget組合方式,實現出怎樣的動畫呢?bash
接下來,咱們就以上面的動畫爲例子,講一講Widget強大的組合性!dom
由簡到難,咱們依次開始組合出上面的效果。ide
晴天動畫是最簡單的,就是一個太陽360度不停旋轉的效果學習
首先,經過模版代碼 sta
建立出一個 WeatherSunny 類,初始化的 controller 和 animation 分別以下動畫
AnimationController _controller;
Animation _animation;
@override
void initState() {
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 60),
);
_animation = Tween(begin: 0.0, end: 1.0).animate(_controller);
...
}
複製代碼
爲了達到太陽不停旋轉的效果,咱們須要把動畫設置成循環的,因此須要監聽它的狀態ui
@override
void initState() {
...
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reset();
_controller.forward();
}
});
_controller.forward();
super.initState();
}
複製代碼
因爲動畫須要進行Widget的刷新,因此咱們一般須要進行下面的操做:this
_controller.addListener((){
setState(() {});
});
複製代碼
可是對於複雜度不高的動畫,咱們可使用 AnimatedBuilder
去下降代碼行數,因此在這裏上面的監聽刷新就沒有必要了spa
而後是將 Animation 應用在 Widget 上
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (ctx, child) {
return Container(
decoration: BoxDecoration(border: Border.all()),
child: Transform.rotate(
angle: pi * 2 * _animation.value * 5,
child: child,
),
);
},
child: Icon(
Icons.wb_sunny,
size: widget.sunnySize,
color: widget.sunColor,
),
);
}
複製代碼
這裏的太陽其實就是flutter默認提供的Icon,咱們讓它每60s旋轉 360 * 5 的度數,也就是每60s 轉5圈。
到這裏也許有同窗會問,爲何不將 Duration 設置成12s,旋轉度數設置成 360 ,效果不是同樣嗎?
效果確實同樣,不過靈活度是不同的,等你實際操做一遍就能夠體會到了。
晴天動畫很是簡單,實際上就是 旋轉動畫 + Icon 的組合
那麼陰天動畫如何實現呢,應該不少同窗已經知道了,就是 晴天動畫 + Stack 的組合
首先咱們將以前的 WeatherSunny 封裝好,讓它能夠從外部傳入某些參數
WeatherSunny({
this.sunnySize = 100,
this.sunColor = Colors.orange,
...
})
複製代碼
而後咱們建立一個 WeatherCloudy 去實現陰天動畫,這裏的陰天動畫不須要額外的動畫操做,因此不用將其建立成 StatefulWidget
@override
Widget build(BuildContext context) {
...
return Container(
width: width,
height: height,
child: Stack(
children: <Widget>[
Positioned(
left: sunOrigin.dx + cloudSize / 6,
top: sunOrigin.dy - cloudSize / 6,
child: WeatherSunny(
sunnySize: sunSize,
sunColor: sunColor,
),
),
Positioned(
left: cloudOrigin.dx,
top: cloudOrigin.dy,
child: Icon(
Icons.cloud,
size: cloudSize,
color: cloudColor,
),
),
],
),
);
}
複製代碼
上面省去了不少細節代碼,能夠看到陰天的動畫就是經過 Stack 組合 晴天動畫 與另一個 雲朵Icon,只不過咱們須要計算各個對象的相對座標
落雨的動畫稍微要複雜一些,由於雨點的生成都是隨機的,因此須要使用到 Random()
在實現以前能夠先思考一下,雨點是用什麼去實現的?
也許有小夥伴早就知道了,就是經過 Container
去實現的雨點
Container(
width: randomWidth,
height: randomHeight,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(randomWidth / 2)),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.white, Theme.of(context).primaryColor,
])),
)
複製代碼
Container能夠實現的效果很豐富,冒充雨點也是不在話下
接下來,就是如何展現出這麼多的雨點。
顯然,是經過 Stack + N個Position 的結合方式
咱們能夠建立出隨機數量的 Container 雨點展現,而後在 Position 中設置他們的隨機座標
//雨滴隨機大小
final randomWidth = Random().nextDouble() * width / 50 + 1;
final randomHeight = Random().nextDouble() * height / 10;
//雨滴隨機座標
double randomL = Random().nextDouble() * width - randomWidth;
double randomT = Random().nextDouble() * height + randomHeight;
複製代碼
不過又有一個問題來了,如何實現雨滴動畫無限向下移動呢?
首先確定是須要讓動畫無限循環的
_controller.reset();
_controller.forward();
複製代碼
讓雨滴移動經過 Transform.translate 便可
Transform.translate(
offset: Offset(
0,
_animation.value * widget.droppingHeight,
),
child: child,
),
);
複製代碼
實際上的動畫應該上這個樣子
因此還剩下一個問題,如何保證雨滴不出邊界?
這裏就須要用到另外一個控件 ClipRect
經過 ClipRect 的 clipper 屬性,咱們能夠對顯示區域進行限制,接下來自定義一個 CustomClipper
class CustomRect extends CustomClipper<Rect> {
@override
Rect getClip(Size size) {
Rect rect = Rect.fromLTRB(0.0, 0.0, size.width, size.height);
return rect;
}
@override
bool shouldReclip(CustomRect oldClipper) {
return false;
}
}
複製代碼
這樣,咱們就能夠把顯示內容限制在 rect 的範圍內
大概的代碼以下
Widget build(BuildContext context) {
final children =
getDroppingWidget(widget.droppingHeight, widget.droppingWidth, context);
return Container(
width: widget.droppingWidth,
height: widget.droppingHeight,
decoration: BoxDecoration(border: Border.all()),
child: AnimatedBuilder(
animation: _animation,
builder: (ctx, child) {
return ClipRect(
clipper: CustomRect(),
child: Transform.translate(
offset: Offset(
0,
_animation.value * widget.droppingHeight,
),
child: child,
),
);
},
child: Stack(
children: [
Transform.translate(
offset: Offset(0, -widget.droppingHeight),
child: Stack(
children: children,
),
),
Stack(
children: children,
),
],
),
),
);
}
複製代碼
下雪的動畫與下雨的動畫是同樣的,只是將實現 雨滴 的Widget替換爲 飄雪 的Widget
Container(
width: width,
height: width,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.white,
Theme.of(context).primaryColor,
])),
);
複製代碼
最後還有 雨雪 + 雲 的動畫,具體實現方式與 晴 + 雲 的效果是差很少的,只是須要進行位置的計算有所不一樣
那麼,經過 widget 組合實現一些動畫效果就到此爲止,能夠看到在flutter 中 萬物基於widget 絕非空口無憑,
demo地址以下:
(ps:demo中我將控件進行了封裝,能夠很方便的調用,原本是打算寫成一個dart package的,後來以爲效果比較簡單,仍是用做學習素材最爲合適!
封裝後,經過 droppingType 參數來控制降低的是與仍是雪,經過 droppingLevel 參數控制雨雪的數量。 也能夠經過 droppingWidget 參數來自定義下落的控件。 )