前言
前不久矮瘟,利用周末時間學(xué)習(xí)并完成一個簡單的 Flutter 項目 - 簡悅天氣庐扫,簡約不簡單吟孙,豐富不復(fù)雜澜倦,這是一款簡約風(fēng)格的 flutter 天氣項目聚蝶,提供實時杰妓、多日、24 小時碘勉、臺風(fēng)路徑巷挥、語音播報以及生活指數(shù)等服務(wù),支持定位验靡、刪除倍宾、搜索等操作。
下圖為主頁效果胜嗓,點擊下載 進行體驗:
項目中運用了大量的自定義繪制 widget高职,首頁豐富的 自定義 chart 效果和炫酷的天氣背景動效。天氣背景動效在不同的天氣氣象下展示不同的效果辞州。目前一共實現(xiàn)了 14 種類別怔锌,其中有,晴变过、晴晚埃元、多云、陰天媚狰、小中大雨岛杀、小中大雪、霧崭孤、霾类嗤、浮塵以及雷暴。背景動效一共分為三層:
- 背景顏色層辨宠。從上到下的漸變效果
- 云層遗锣。只有一種圖片,對其位移彭羹、數(shù)量黄伊、染色做不同變化達到不同效果
- 信息層。包括雨雪派殷、雷暴和晴晚流星效果
之前在 『Flutter-繪制篇』實現(xiàn)炫酷的雨雪特效 一文中介紹過雨雪的實現(xiàn)細節(jié)还最,今天我們聊一聊如何實現(xiàn) 夢幻的晴晚流星 效果,其實實現(xiàn)到不難毡惜,主要是實現(xiàn)的思路和方法拓轻。
話不多說,先看一下動態(tài)效果圖(為了更好預(yù)覽多樣的背景動效经伙,在右上角關(guān)于頁面加了入口扶叉,可以實時切換天氣類型勿锅,查看動態(tài)效果,感興趣的下載體驗):
陪你去看流星雨落在這地球上枣氧,讓你的淚落在我肩膀溢十,在我肩膀~
仔細觀察不難發(fā)現(xiàn),主要有兩部分組成:
- 不斷閃爍的星星效果
- 轉(zhuǎn)瞬即逝的流星效果
所以接下來會圍繞上面兩部分進行詳細講解达吞。
晴晚
Flutter 同 Android 的繪制有很多相似之處张弛,提供了 canvas 和 paint 類作為畫板和畫筆,可以繪制基礎(chǔ)的圖形文字和圖片酪劫,同樣有很多 api 為簡單的圖形添加漸變吞鸭、高斯模糊等炫酷的特效 。
初始化素材和參數(shù)
晴晚由群星組成覆糟,首先繪制一顆星星刻剥,簡單實現(xiàn),就不使用圖片滩字,直接通過 canvas 繪制造虏。Flutter 提供了 MaskFilter.blur(_style, _sigma)
,并通過 _paint.maskFilter
可以圖形設(shè)置模糊踢械,從而營造星星的發(fā)光效果酗电。
BlurStyle
一共有四種類別,分別為:normal内列、solid撵术、outer和outter,對應(yīng)下面的效果:
_sigma
模糊系數(shù)越大越模糊话瞧,在 normal 下分別設(shè)置 0嫩与、1、3交排、7 效果:
有了星星划滋,接下來就需要創(chuàng)建星星,并賦予其屬性埃篓,讓其展示在屏幕上处坪。
class _StarParam {
double x;
double y;
double alpha = 0.0;
double scale;
_StarParam();
void init() {
alpha = Random().nextDouble();
scale = Random().nextDouble() * 0.1 + 0.6;
x = Random().nextDouble() * 1.wp / scale;
y = Random().nextDouble() * 0.3.hp / scale;
}
}
除了坐標 x,y 屬性外,alpha 用于后面做動畫架专, scale 用于模擬遠近的效果同窘,我們創(chuàng)建 100 個隨機分布在 1*width
和 0.3*height
的區(qū)域內(nèi)。
繪制
有參數(shù)后部脚,直接開始繪制想邦,只需要調(diào)用 canvas.drawCircle()
即可:
void drawStar(_StarParam param, Canvas canvas) {
if (param == 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, param.alpha, 0,
]);
_paint.colorFilter = identity;
canvas.scale(param.scale);
canvas.drawCircle(Offset(param.x, param.y), 3, _paint);
canvas.restore();
}
實現(xiàn)效果如下:
動畫
接下來就是讓星星“動”起來。有點類似之前實現(xiàn)雨雪的邏輯委刘,創(chuàng)建一個持續(xù)運行的動畫丧没,在動畫回調(diào)中不斷的 setState()
鹰椒,對于星星對象,用一個屬性 alpha
字段呕童,不斷自增或自減來達到閃爍的效果漆际。
void move() {
if (reverse == true) {
alpha -= 0.01;
if (alpha < 0) {
reset();
}
} else {
alpha += 0.01;
if (alpha > 1.2) {
reverse = true;
}
}
}
這里,當(dāng) alpha 值達到閾值時拉庵,將 reverse 字段置為 true灿椅,此時開始自減,做消失動畫钞支,因為很多屬性都是隨機,最終就營造出群星閃爍的效果操刀。
流星
初始化素材和參數(shù)
一開始準備從各種圖片素材網(wǎng)上試著找一下流星的資源烁挟,結(jié)果要么效果不滿意,要么就是要各種收費和關(guān)注等等骨坑。轉(zhuǎn)念一想撼嗓,還不如自己實現(xiàn)來的快,無非就是一條漸變細長形的圓角矩形欢唾。
圓角矩形通過 canvas.drawRRect()
可以實現(xiàn)且警,為了實現(xiàn)流星的拖尾效果,借用 paint#shader
的屬性礁遣,達到漸變的效果斑芜。
var gradient = ui.Gradient.linear(
const Offset(0, 0),
Offset(_meteorWidth, 0),
<Color>[const Color(0xFFFFFFFF), const Color(0x00FFFFFF)],
);
_meteorPaint.shader = gradient;
我們隨機創(chuàng)建4組流星。
繪制
為了營造更真實的流星劃過效果祟霍,水平效果是不滿足的杏头,需要對其進行角度翻轉(zhuǎn)。
一開始沸呐,打算通過 y/x=tan(Θ) 方式進行繪制醇王,后來考慮到要做動畫,各種動態(tài)計算崭添,干脆直接使用 canvas.rotate()
和 canvas.translate()
的方法寓娩,更加方便,更加的好理解呼渣。
void drawMeteor(_MeteorParam param, Canvas canvas) {
canvas.save();
var gradient = ui.Gradient.linear(
const Offset(0, 0),
Offset(_meteorWidth, 0),
<Color>[const Color(0xFFFFFFFF), const Color(0x00FFFFFF)],
);
_meteorPaint.shader = gradient;
canvas.rotate(pi * param.radians);
canvas.translate(param.translateX, tan(pi * 0.1) *_meteorWidth + param.translateY);
canvas.drawRRect(
RRect.fromLTRBAndCorners(0, 0, _meteorWidth, _meteorHeight,
topLeft: _radius,
topRight: _radius,
bottomRight: _radius,
bottomLeft: _radius),
_meteorPaint);
canvas.restore();
}
這里 rotate 和 translate 的順序不能寫反棘伴,需要先旋轉(zhuǎn)再平移,因為旋轉(zhuǎn)的點在左側(cè)端徙邻,如果先平移再旋轉(zhuǎn)排嫌,會導(dǎo)致運動過程中,流星不再一條直線上運行缰犁。效果如下:
動畫
復(fù)用晴晚的 AnimationController,通過不斷的改變 translateX 值淳地,來完成劃過的動畫效果怖糊。
class _MeteorParam {
double translateX;
double translateY;
double radians;
void reset() {
translateX = 1.0.wp + Random().nextDouble() * 20.0.wp;
radians = -Random().nextDouble() * 0.07 - 0.05;
translateY = Random().nextDouble() * 0.5.hp;
}
void move() {
translateX -= 20;
if (translateX <= -1.0.wp) {
reset();
}
}
}
這里初始化 1.0.wp + Random().nextDouble() * 20.0.wp
,使其初始化位置分布在 [1, 20] * width 的位置颇象,而在的動畫過程中不斷 translateX -= 20伍伤,當(dāng)滑過一個屏幕則重新初始化位置。
到這遣钳,夢幻的晴晚和流星效果就實現(xiàn)了扰魂,預(yù)告一下下期-炫酷的雷暴效果。