前不久,利用週末時間學習並完成一個簡單的 Flutter 項目 - 簡悅天氣,簡約不簡單,豐富不復雜,這是一款簡約風格的 flutter 天氣項目,提供實時、多日、24 小時、颱風路徑、語音播報以及生活指數等服務,支持定位、刪除、搜索等操做。git
下圖爲主頁效果,點擊下載 進行體驗:github
項目中運用了大量的自定義繪製 widget,首頁豐富的 自定義 chart 效果和炫酷的天氣背景動效。天氣背景動效在不一樣的天氣氣象下展現不一樣的效果。目前一共實現了 15 種類別,其中有,晴、晴晚、多雲、多雲晚、陰天、小中大雨、小中大雪、霧、霾、浮塵以及雷暴。背景動效一共分爲三層:web
以前分別用兩篇文章介紹雨雪和晴晚流星效果的實現細節:canvas
今天咱們介紹背景動畫的最後一篇,如何實現炫酷的雷電特效,先看一下最終效果:api
根據實現效果進行分析,雨滴效果在以前文章有介紹過很少贅述,仔細觀察,其實就是對閃電圖片在繪製時控制其 alpha 以營造出這種霹靂的效果。markdown
首先準備幾張閃電的素材,UI 網站找了很長時間沒有找到滿意的效果,關鍵費時費錢。後來發現 oppo 最新版的天氣的雷暴效果停酷炫的,因而對其反編譯,找到他的資源目錄。其實 oppo 雷暴的動畫效果是經過 視頻+openGL 的方式實現,裏面有閃電的靜態資源、視頻資源和 openGL 代碼文件。咱們只需提取他的靜態資源文件便可。隨即在 initState() 方法中異步獲取加載圖片資源:dom
Future<void> fetchImages() async {
weatherPrint("開始獲取雷暴圖片");
var image1 = await ImageUtils.getImage('assets/images/lightning/lightning0.webp');
var image2 = await ImageUtils.getImage('assets/images/lightning/lightning1.webp');
var image3 = await ImageUtils.getImage('assets/images/lightning/lightning2.webp');
var image4 = await ImageUtils.getImage('assets/images/lightning/lightning3.webp');
var image5 = await ImageUtils.getImage('assets/images/lightning/lightning4.webp');
_images.add(image1);
_images.add(image2);
_images.add(image3);
_images.add(image4);
_images.add(image5);
weatherPrint("獲取雷暴圖片成功: ${_images?.length}");
}
複製代碼
有了圖片後,開始構建對象和參數列表。由上面分析可知,除了基本的座標 x,y 信息,只須要額外增長 alpha 屬性來達到效果。異步
class ThunderParams {
ui.Image image;
double x;
double y;
double alpha;
int get imgWidth => image.width;
int get imgHeight => image.height;
ThunderParams(this.image);
void reset() {
x = Random().nextDouble() * 0.5.wp - 1 / 3 * imgWidth;
y = Random().nextDouble() * -0.05.hp;
alpha = 0;
}
}
複製代碼
reset()
方法用於在當前雷暴結束時,從新初始化參數信息。async
參數配置好後,繪製很簡單。有了圖片有了位置信息和 alpha 信息,調用 canvas 的相關 api 進行繪製便可。ide
void drawThunder(ThunderParams params, Canvas canvas, Size size) {
if (params == null || params.image == null) {
return;
}
canvas.save();
var identity = ColorFilter.matrix(<double>[
1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, params.alpha, 0,
]);
_paint.colorFilter = identity;
canvas.drawImage(params.image, Offset(params.x, params.y), _paint);
canvas.restore();
}
複製代碼
繪製到屏幕中大概長這樣:
離炫酷就差最後一步 動畫。
首先咱們把單個閃電看作一個動畫對象,從消失到展現再顯示,落實到動畫上,alpha 由0到1,而後再到0。可是你可能發現,出現的速度要比消失的速度要快。咱們能夠藉助 TweenSequence 類來實現這個效果。
TweenSequence 是一個動畫序列,支持配置權重,以及對應的動畫 Tween。這樣,咱們能夠給 alpha 在 [0,1] 區間作動畫時權重設置低一點,[1,0] 時權重高一點。
var _animation = TweenSequence([
TweenSequenceItem(
tween: Tween(begin: 0.0, end: 1.0)
.chain(CurveTween(curve: Curves.easeIn)),
weight: 1),
TweenSequenceItem(
tween: Tween(begin: 1.0, end: 0.0)
.chain(CurveTween(curve: Curves.easeIn)),
weight: 3),
]);
複製代碼
實現後,效果以下:
而後,咱們用三個隨機的閃電做爲一組,作循環動畫,控制其序列幀,完成連續&不一樣&隨機的刪掉效果。
_controller = AnimationController(duration: Duration(seconds: 1), vsync: this);
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reset();
Future.delayed(Duration(milliseconds: 10)).then((value) {
initThunderParams();
_controller.forward();
});
}
});
var _animation = TweenSequence([
TweenSequenceItem(
tween: Tween(begin: 0.0, end: 1.0)
.chain(CurveTween(curve: Curves.easeIn)),
weight: 1),
TweenSequenceItem(
tween: Tween(begin: 1.0, end: 0.0)
.chain(CurveTween(curve: Curves.easeIn)),
weight: 3),
]).animate(CurvedAnimation(
parent: _controller,
curve: Interval(
0.0, 1.0,
curve: Curves.ease,
),
));
複製代碼
在以前說的一個閃電動畫後面,新增 .animate()
的配置,經過控制
Interval(
0.0, 0.3,
curve: Curves.ease,
)
複製代碼
配置該動畫執行序列幀的開始和結束,以及插值器。
經過在 addStatusListener
中監聽動畫的執行狀態,在觸發 AnimationStatus.completed
時隨機等待必定時間後,從新開始。
到此,一個炫酷的雷電特效就完成了,是否是很簡單,若是以爲還不錯,後面考慮把天氣動畫背景作成插件供有須要的小夥伴使用