本文主要介紹如何使用 simple_animations 實現漂亮的動畫效果。Demo 可見 :gsy_flutter_demo
這篇文章將會介紹一個很有意思的動畫效果,它能讓 Flutter 的頁面顯得更加友好,同時本文也將展示如何使用 simple_animations 庫,在 Flutter 上輕鬆地實現如下圖所示的動畫效果。
動畫所需要展示的效果是:由平滑過渡的漸變背景組成,並且在文字下面會有多個波從右向左滑動。
接下來首先從背景漸變開始介紹,在 Flutter 中內建的 BoxDecoration
就支援使用 LinearGradient
來實現漸變效果,程式碼如下所示:
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [color1, color2])),
);
複製程式碼
所以我們只需要在這個基礎上去設定動畫即可,這裡直接使用 simple_animations 來實現效果, 在 simple_animations 中我們可以使用這兩個物件來實現效果:
MultiTrackTween
(動畫處理物件,一次安排多個補間動畫的屬性)ControlledAnimation
(一個非常簡單的基於補間動畫的控制元件物件)
關於波形的實現在後面的文章中會介紹,這裡先背景漸變的程式碼:
class AnimatedBackground extends StatelessWidget {
@override
Widget build(BuildContext context) {
final tween = MultiTrackTween([
Track("color1").add(Duration(seconds: 3),
ColorTween(begin: Color(0xffD38312), end: Colors.lightBlue.shade900)),
Track("color2").add(Duration(seconds: 3),
ColorTween(begin: Color(0xffA83279), end: Colors.blue.shade600))
]);
return ControlledAnimation(
playback: Playback.MIRROR,
tween: tween,
duration: tween.duration,
builder: (context, animation) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [animation["color1"], animation["color2"]])),
);
},
);
}
}
複製程式碼
如上程式碼所示,在這裡這裡我們僅僅定義兩個的 Track
的顏色 color1 和 color2,並將它設定到 ControlledAnimation
中,最後在 LinearGradient
使用這兩個顏色。
是不是看起來很簡單,這裡甚至都沒有看到任何 StatefulWidget
或 AnimationController
,你可以將這段程式碼作為模板,並使用更復雜的顏色過渡對其進行擴充套件。
現在我們有了一個漸變背景的動畫,接著可以新增一些新的動畫效果來完善效果,如下圖所示是我們想要實現的最終效果:
實際上,這是三個波型相互重疊的效果,這裡我們需要確保它們彼此獨立,以便於最終可以產生波紋的疊加效果。
因此,我們需要定義一個 WaveAnimation
具備以下屬性的小控制元件:
speed
:控制波浪動畫的持續時間;height
:設定波浪作用的區域;offset
:x軸的偏移,以給出不同的波形“起始位置”;
接下來我們先要討論一個數學的問題,要如何實現一個週期性的迴圈弧形動畫效果呢?答案只有一個:三角函式。
首先我們需要為動畫設定一個 0.0 到 2 * pi 之間的值,並將該值放入到正弦函式中,接著我們在三個位置上取樣 y 的數值大小:左側、中間和末端。這樣從左到右就覆蓋了一個 pi 大小的間隔,因此我們始終可以看到一個完整的正弦波的一半。
我們為什麼要取樣三個位置?因為我們會根據這三個位置畫一段 Path,如下圖所示提供了這個視覺化效果:
我們從左上角開始(紫色),並在右上角(紅色)新增一個二次貝塞爾函式連線過去,然後我們可以通過指定一個“控制點”(綠色)來實現這個變化,最後利用了 Flutter 的 Canvas
路徑繪製的方法繪製出一個 Path
。
然後我們讓紅色的點往橙色移動,之後讓紫色的點往黃色點以後,最後我們只需要把這個 Path 路徑給連線起來就可以了。
當你只僅關注紫色、綠色和紅色點的時候,就可以看到我們的取樣後的路徑是一個正弦波的效果。
這個二次貝塞爾函式乍一看似乎有些詭異,但是它只是想畫一條從紫色到紅色的直線(藍色)。它和綠點之間的距離越長,線條就會受到某種類似重力因素的影響得到灰色的形狀,最終的結果是線逐漸彎曲。
class AnimatedWave extends StatelessWidget {
final double height;
final double speed;
final double offset;
AnimatedWave({this.height, this.speed, this.offset = 0.0});
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
return Container(
height: height,
width: constraints.biggest.width,
child: ControlledAnimation(
playback: Playback.LOOP,
duration: Duration(milliseconds: (5000 / speed).round()),
tween: Tween(begin: 0.0, end: 2 * pi),
builder: (context, value) {
return CustomPaint(
foregroundPainter: CurvePainter(value + offset),
);
}),
);
});
}
}
class CurvePainter extends CustomPainter {
final double value;
CurvePainter(this.value);
@override
void paint(Canvas canvas, Size size) {
final white = Paint()..color = Colors.white.withAlpha(60);
final path = Path();
final y1 = sin(value);
final y2 = sin(value + pi / 2);
final y3 = sin(value + pi);
final startPointY = size.height * (0.5 + 0.4 * y1);
final controlPointY = size.height * (0.5 + 0.4 * y2);
final endPointY = size.height * (0.5 + 0.4 * y3);
path.moveTo(size.width * 0, startPointY);
path.quadraticBezierTo(
size.width * 0.5, controlPointY, size.width, endPointY);
path.lineTo(size.width, size.height);
path.lineTo(0, size.height);
path.close();
canvas.drawPath(path, white);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
複製程式碼
對於這個控制元件,我們使用了一個 LayoutBuilder
來檢查可用的寬度再進行繪製,然後使用 ControlledAnimation
和 Playback.LOOP
實現從 0.0 到 2 * pi 的簡單補間資料,之後可以將當前動畫值傳遞到 CustomPainter
的 Canvas
進行動畫繪製。
最終這個 CustomPainter
可以實現我們想要的波形路徑,但需要注意的是,我們使用的波形與不透明度需要一層層減小,這樣多個波始重疊才能始終可見。
是不是用相當少的程式碼就實現了很炫酷的動畫?
最後如下程式碼所示,我們使用 Stack
將控制元件堆疊在一起。
class FancyBackgroundApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Positioned.fill(child: AnimatedBackground()),
onBottom(AnimatedWave(
height: 180,
speed: 1.0,
)),
onBottom(AnimatedWave(
height: 120,
speed: 0.9,
offset: pi,
)),
onBottom(AnimatedWave(
height: 220,
speed: 1.2,
offset: pi / 2,
)),
Positioned.fill(child: CenteredText()),
],
);
}
onBottom(Widget child) => Positioned.fill(
child: Align(
alignment: Alignment.bottomCenter,
child: child,
),
);
}
複製程式碼
Flutter 文章彙總地址:
資源推薦
- 本文 Demo 可見 :gsy_flutter_demo/anim_bg_demo_page.dart
- Github : github.com/CarGuo
- 開源 Flutter 完整專案:github.com/CarGuo/GSYG…
- 開源 Flutter 多案例學習型專案: github.com/CarGuo/GSYF…
- 開源 Fluttre 實戰電子書專案:github.com/CarGuo/GSYF…
- 開源 React Native 專案:github.com/CarGuo/GSYG…