Flutter實現(xiàn)牛頓擺動畫效果的示例代碼
前言
牛頓擺大家應(yīng)該都不陌生,也叫碰碰球、永動球(理論情況下),那么今天我們用Flutter實現(xiàn)這么一個理論中的永動球,可以作為加載Loading使用。
- 知識點:繪制、動畫曲線、多動畫狀態(tài)更新
效果圖:

實現(xiàn)步驟
1、繪制靜態(tài)效果
首先我們需要把線和小圓球繪制出來,對于看過我之前文章的小伙伴來說這個就很簡單了,效果圖:

關(guān)鍵代碼:
// 小圓球半徑
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,正向運動一次,反向再運動一次,這樣就是一個周期,右邊也是一樣,左邊運動完傳遞給右邊,右邊運動完傳遞給左邊,這樣就簡單實現(xiàn)了牛頓擺的效果。
兩個關(guān)鍵點
小球運動路徑: 小球的運動路徑是一個弧度,以豎線的起點為圓心,終點為半徑,那么我們只需要設(shè)置小球運動至最高點的角度即可,通過角度就可計算出小球的坐標點。
運動曲線: 當然我們知道牛頓擺小球的運動曲線并不是勻速的,他是有一個加速減速過程的,撞擊之后,小球先加速然后減速達到最高點速度為0,之后速度再從0慢慢加速進行撞擊小球,周而復(fù)始。
下面的運動曲線就是先加速再減速,大概符合牛頓擺的運動曲線。我們就使用這個曲線看看效果。

完整源碼
class OvalLoading extends StatefulWidget {
const OvalLoading({Key? key}) : super(key: key);
@override
_OvalLoadingState createState() => _OvalLoadingState();
}
class _OvalLoadingState extends State<OvalLoading>
with TickerProviderStateMixin {
// 左邊小球
late AnimationController _controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 300))
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reverse(); //反向執(zhí)行 1-0
} else if (status == AnimationStatus.dismissed) {
_controller2.forward();
}
})
..forward();
// 右邊小球
late AnimationController _controller2 =
AnimationController(vsync: this, duration: Duration(milliseconds: 300))
..addStatusListener((status) {
// dismissed 動畫在起始點停止
// forward 動畫正在正向執(zhí)行
// reverse 動畫正在反向執(zhí)行
// completed 動畫在終點停止
if (status == AnimationStatus.completed) {
_controller2.reverse(); //反向執(zhí)行 1-0
} else if (status == AnimationStatus.dismissed) {
// 反向執(zhí)行完畢左邊小球執(zhí)行
_controller.forward();
}
});
late var cure =
CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic);
late var cure2 =
CurvedAnimation(parent: _controller2, curve: Curves.easeOutCubic);
late Animation<double> animation = Tween(begin: 0.0, end: 1.0).animate(cure);
late Animation<double> animation2 =
Tween(begin: 0.0, end: 1.0).animate(cure2);
@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<double> animation;
final Animation<double> animation2;
final Listenable listenable;
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;
}
}去掉線的效果

總結(jié)
本文展示了實現(xiàn)牛頓擺的原理,其實并不復(fù)雜,關(guān)鍵點就是小球的運動軌跡和運動速度曲線,如果用到項目中當做Loading還有很多優(yōu)化的空間,比如加上小球影子、修改小球顏色或者把小球換成好玩的圖片等等操作會看起來更好看一點
到此這篇關(guān)于Flutter實現(xiàn)牛頓擺動畫效果的示例代碼的文章就介紹到這了,更多相關(guān)Flutter牛頓擺動畫內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android水波紋載入控件CircleWaterWaveView使用詳解
這篇文章主要為大家詳細介紹了Android水波紋載入控件CircleWaterWaveView使用方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-01-01
Android編程仿Iphone拖動相片特效Gallery的簡單應(yīng)用示例
這篇文章主要介紹了Android編程仿Iphone拖動相片特效Gallery的簡單應(yīng)用,結(jié)合實例形式分析了Android圖形拖動特效的實現(xiàn)步驟與相關(guān)操作技巧,需要的朋友可以參考下2016-10-10
Android中CheckBox復(fù)選框控件使用方法詳解
這篇文章主要為大家詳細介紹了Android中CheckBox復(fù)選框控件的使用方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-08-08
Android編程之絕對布局AbsoluteLayout和相對布局RelativeLayout實例詳解
這篇文章主要介紹了Android編程之絕對布局AbsoluteLayout和相對布局RelativeLayout實現(xiàn)方法,結(jié)合實例形式詳細分析了Android絕對布局AbsoluteLayout和相對布局RelativeLayout的原理與使用技巧,需要的朋友可以參考下2015-12-12
activitygroup 切換動畫效果如何實現(xiàn)
本文將詳細介紹activitygroup 切換動畫效果實現(xiàn)過程,需要聊解的朋友可以參考下2012-12-12
Android自定義View實現(xiàn)多邊形統(tǒng)計圖示例代碼
這篇文章主要給大家介紹了關(guān)于Android自定義View如何實現(xiàn)多邊形統(tǒng)計圖的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-01-01
詳解 android 光線傳感器 light sensor的使用
這篇文章主要介紹了詳解 android 光線傳感器 light sensor的使用的相關(guān)資料,需要的朋友可以參考下2017-06-06
Android編程實現(xiàn)在Activity中操作刷新另外一個Activity數(shù)據(jù)列表的方法
這篇文章主要介紹了Android編程實現(xiàn)在Activity中操作刷新另外一個Activity數(shù)據(jù)列表的方法,結(jié)合具體實例形式分析了2種常用的Activity交互實現(xiàn)技巧,需要的朋友可以參考下2017-06-06
Android Cocos Creator游戲開發(fā)平臺打包優(yōu)化實現(xiàn)方案
Cocos Creator是一款輕量、高效、免費開源的跨平臺游戲引擎,同時也是實時3D內(nèi)容創(chuàng)作平臺,不僅支持2D、3D的游戲開發(fā),同時在HMI、IoT、XR、虛擬人偶等領(lǐng)域,均可提供一套完善的行業(yè)解決方案2022-11-11

