Flutter實現一個牛頓擺
一起養成寫作習慣!這是我參與「掘金日新計劃 · 4 月更文挑戰」的第18天,點選檢視活動詳情。
前言
- 牛頓擺大家應該都不陌生,也叫碰碰球、永動球(理論情況下),那麼今天我們用Flutter實現這麼一個理論中的永動球,可以作為載入Loading使用。
- 知識點:繪製、動畫曲線、多動畫狀態更新
效果圖:
1、繪製靜態效果
首先我們需要把線和小圓球繪製出來,對於看過我之前文章的小夥伴來說這個就很簡單了,效果圖:
關鍵程式碼:
```dart // 小圓球半徑 double radius = 6;
/// 小球圓心和直線終點一致 //左邊小球圓心 Offset offset = Offset(20, 60); //右邊小球圓心 Offset offset2 = Offset(20 * 6 * 8, 60);
Paint paint = Paint() ..color = Colors.black87 ..strokeWidth = 2;
/// 繪製線 canvas.drawLine(Offset.zero, Offset(90, 0), paint); canvas.drawLine(Offset(20, 0), offset, paint); canvas.drawLine( Offset(20 + radius * 2, 0), Offset(20 + radius * 2, 60), paint); canvas.drawLine( Offset(20 + radius * 4, 0), Offset(20 + radius * 4, 60), paint); canvas.drawLine( Offset(20 + radius * 6, 0), Offset(20 + radius * 6, 60), paint); canvas.drawLine(Offset(20 + radius * 8, 0), offset2, paint);
/// 繪製小圓球 canvas.drawCircle(offset, radius, paint); canvas.drawCircle(Offset(20 + radius * 2, 60), radius, paint); canvas.drawCircle(Offset(20 + radius * 4, 60), radius, paint); canvas.drawCircle(Offset(20 + radius * 6, 60), radius, paint); canvas.drawCircle(offset2, radius, paint); ```
2、加入動畫
思路: 我們可以看到5個小球一共2個小球在運動,左邊小球運動一個來回之後傳遞給右邊小球,右邊小球開始運動,右邊一個來回再傳遞給左邊開始,也就是左邊運動週期是:0-1-0,正向運動一次,反向再運動一次,這樣就是一個週期,右邊也是一樣,左邊運動完傳遞給右邊,右邊運動完傳遞給左邊,這樣就簡單實現了牛頓擺的效果。
兩個關鍵點
小球運動路徑: 小球的運動路徑是一個弧度,以豎線的起點為圓心,終點為半徑,那麼我們只需要設定小球運動至最高點的角度即可,通過角度就可計算出小球的座標點。
運動曲線: 當然我們知道牛頓擺小球的運動曲線並不是勻速的,他是有一個加速減速過程的,撞擊之後,小球先加速然後減速達到最高點速度為0,之後速度再從0慢慢加速進行撞擊小球,周而復始。
下面的運動曲線就是先加速再減速,大概符合牛頓擺的運動曲線。我們就使用這個曲線看看效果。
完整原始碼:
```dart
class OvalLoading extends StatefulWidget {
const OvalLoading({Key? key}) : super(key: key);
@override _OvalLoadingState createState() => _OvalLoadingState(); }
class _OvalLoadingState extends State
late Animation
late Animation
@override Widget build(BuildContext context) { return Container( margin: EdgeInsetsDirectional.only(top: 300, start: 150), child: CustomPaint( size: Size(100, 100), painter: _OvalLoadingPainter( animation, animation2, Listenable.merge([animation, animation2])), ), ); }
@override void dispose() { _controller.dispose(); _controller2.dispose(); super.dispose(); } }
class _OvalLoadingPainter extends CustomPainter {
double radius = 6;
final Animation
late Offset offset; // 左邊小球圓心 late Offset offset2; // 右邊小球圓心
final double lineLength = 60; // 線長
_OvalLoadingPainter(this.animation, this.animation2, this.listenable) : super(repaint: listenable) { offset = Offset(20, lineLength); offset2 = Offset(20 * radius * 8, lineLength); }
// 擺動角度 double angle = pi / 180 * 30; // 30°
@override void paint(Canvas canvas, Size size) { Paint paint = Paint() ..color = Colors.black87 ..strokeWidth = 2;
// 左邊小球 預設座標 下方是90度 需要+pi/2
var dx = 20 + 60 * cos(pi / 2 + angle * animation.value);
var dy = 60 * sin(pi / 2 + angle * animation.value);
// 右邊小球
var dx2 = 20 + radius * 8 - 60 * cos(pi / 2 + angle * animation2.value);
var dy2 = 60 * sin(pi / 2 + angle * animation2.value);
offset = Offset(dx, dy);
offset2 = Offset(dx2, dy2);
/// 繪製線
canvas.drawLine(Offset.zero, Offset(90, 0), paint);
canvas.drawLine(Offset(20, 0), offset, paint);
canvas.drawLine(
Offset(20 + radius * 2, 0), Offset(20 + radius * 2, 60), paint);
canvas.drawLine(
Offset(20 + radius * 4, 0), Offset(20 + radius * 4, 60), paint);
canvas.drawLine(
Offset(20 + radius * 6, 0), Offset(20 + radius * 6, 60), paint);
canvas.drawLine(Offset(20 + radius * 8, 0), offset2, paint);
/// 繪製球
canvas.drawCircle(offset, radius, paint);
canvas.drawCircle(
Offset(20 + radius * 2, 60),
radius,
paint);
canvas.drawCircle(Offset(20 + radius * 4, 60), radius, paint);
canvas.drawCircle(Offset(20 + radius * 6, 60), radius, paint);
canvas.drawCircle(offset2, radius, paint);
}
@override
bool shouldRepaint(covariant _OvalLoadingPainter oldDelegate) {
return oldDelegate.listenable != listenable;
}
}
```
去掉線的效果:
總結
本文展示了實現牛頓擺的原理,其實並不複雜,關鍵點就是小球的運動軌跡和運動速度曲線,如果用到專案中當做Loading還有很多優化的空間,比如加上小球影子、修改小球顏色或者把小球換成好玩的圖片等等操作會看起來更好看一點,本篇只展示了實現的原理,希望對大家有一些幫助~
- Flutter3.7版本新增元件-Menu菜單系列介紹
- Flutter【繪製】製作一個掘金Logo元件
- Flutter【手勢&繪製】模擬紙質書籍翻頁
- Flutter【繪製&手勢】模擬紙質書翻頁2--外掛化
- Flutter【手勢&繪製】圍棋棋盤
- Flutter【手勢&繪製】手遊操縱桿移動解析
- Flutter實現訊飛線上語音合成(WebSocket流式版)
- 2022年中總結--時間過的好快
- Flutter實現訊飛線上語音聽寫(WebSocket流式版)
- Flutter更改輸入框內容,onChanged接受不到回撥?
- 【端午】會說話的粽子你見過嗎?
- Flutter入口中的runApp方法解析
- Flutter實現手勢密碼加密、解鎖
- Flutter 獲取狀態列高度等於0,為啥?
- Flutter仿網易App廣告卡片3D翻轉
- Flutter實現心碎的感覺
- Flutter實現一個牛頓擺
- Flutter實現掘金App點贊效果
- Flutter製作一個吃豆人載入Loading
- Flutter繪製之貝塞爾曲線畫一個小海豚