開頭
在flutter中,我們可以通過 AnimationController 及各種 Animation 搭配使用的方式去實現 Widget 的動畫。
實現的方式也非常方便,通過flutter內建好的模版程式碼,在你建立的dart檔案中輸入 sta
即可建立出基本的動畫模版類。
那麼,我們可以通過這樣的Widget組合方式,實現出怎樣的動畫呢?
接下來,我們就以上面的動畫為例子,講一講Widget強大的組合性!
Widget 組合
由簡到難,我們依次開始組合出上面的效果。
晴
晴天動畫是最簡單的,就是一個太陽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);
...
}
複製程式碼
為了達到太陽不停旋轉的效果,我們需要把動畫設定成迴圈的,所以需要監聽它的狀態
@override
void initState() {
...
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reset();
_controller.forward();
}
});
_controller.forward();
super.initState();
}
複製程式碼
由於動畫需要進行Widget的重新整理,所以我們通常需要進行下面的操作:
_controller.addListener((){
setState(() {});
});
複製程式碼
但是對於複雜度不高的動畫,我們可以使用 AnimatedBuilder
去降低程式碼行數,所以在這裡上面的監聽重新整理就沒有必要了
然後是將 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 引數來自定義下落的控制元件。 )