基于Flutter制作一個(gè)吃豆人加載動(dòng)畫(huà)
效果圖
國(guó)際慣例,先看效果圖:
具體效果就是吃豆人會(huì)根據(jù)吃不同顏色的豆子改變身體的顏色。
繪制靜態(tài)吃豆人、豆豆、眼睛
首先,我們需要將這個(gè)靜態(tài)的吃豆人繪制出來(lái),我們可以把吃豆人看做是一個(gè)實(shí)心圓弧,豆豆和眼睛就是一個(gè)圓。
關(guān)鍵代碼:
//畫(huà)頭 _paint ..color = color.value ..style = PaintingStyle.fill; var rect = Rect.fromCenter( center: Offset(0, 0), width: size.width, height: size.height); /// 起始角度 var a = 40 / 180 * pi; // 繪制圓弧 canvas.drawArc(rect, 0, 2 * pi - a * 2, true, _paint); // 畫(huà)豆豆 canvas.drawOval( Rect.fromCenter( center: Offset( size.width / 2 + ddSize - angle2.value * (size.width / 2 + ddSize), 0), width: ddSize, height: ddSize), _paint..color = color2.value); //畫(huà)眼睛 canvas.drawOval( Rect.fromCenter( center: Offset(0, -size.height / 3), width: 8, height: 8), _paint..color = Colors.black87);
動(dòng)畫(huà)屬性: 嘴巴的張合:通過(guò)圓弧的角度不斷改變實(shí)現(xiàn),豆豆移動(dòng):從頭的右側(cè)源源不斷的有豆子向左移動(dòng),改變豆豆x軸的坐標(biāo)即可,接下來(lái)我們讓吃豆人動(dòng)起來(lái)吧。
加入動(dòng)畫(huà)屬性
這里我們需要?jiǎng)?chuàng)建2個(gè)動(dòng)畫(huà)控制器,一個(gè)控制頭,一個(gè)控制豆豆,我們看到因?yàn)轭^部一開(kāi)一合屬于動(dòng)畫(huà)正向執(zhí)行一次然后再反向執(zhí)行一次,相當(dāng)于執(zhí)行了兩次,豆豆的從右邊到嘴巴只執(zhí)行了一次,所以頭的執(zhí)行時(shí)間是豆豆執(zhí)行時(shí)間的兩倍,嘴巴一張一合才能吃豆子嘛,吃豆完畢,將豆子顏色賦值給頭改變顏色,豆子隨機(jī)獲取另一個(gè)顏色,不斷的吃豆。 這里的繪制狀態(tài)有多種情況,嘴巴的張合、豆子的平移、顏色的改變都需要進(jìn)行重新繪制,這里我們可以使用 Listenable.merge
方法來(lái)進(jìn)行監(jiān)聽(tīng),接受一個(gè)Listenable
數(shù)組,可以將我們需要改變的狀態(tài)放到這個(gè)數(shù)組里,返回一個(gè) Listenable
賦值給CustomPainter
構(gòu)造函數(shù)repaint
屬性即可,然后在監(jiān)聽(tīng)只需判斷這個(gè)Listenable
即可。
factory Listenable.merge(List<Listenable?> listenables) = _MergingListenable;
關(guān)鍵代碼: 動(dòng)畫(huà)執(zhí)行相關(guān)。
late Animation<double> animation; // 吃豆人 late Animation<double> animation2; // 豆豆 late AnimationController _controller = AnimationController( vsync: this, duration: Duration(milliseconds: 500)); //1s late AnimationController _controller2 = AnimationController( vsync: this, duration: Duration(milliseconds: 1000)); //2s //初始化吃豆人、豆豆顏色 ValueNotifier<Color> _color = ValueNotifier<Color>(Colors.yellow.shade800); ValueNotifier<Color> _color2 = ValueNotifier<Color>(Colors.redAccent.shade400); // 動(dòng)畫(huà)軌跡 late CurvedAnimation cure = CurvedAnimation( parent: _controller, curve: Curves.easeIn); // 動(dòng)畫(huà)運(yùn)行的速度軌跡 速度的變化 @override void initState() { super.initState(); animation = Tween(begin: 0.2, end: 1.0).animate(_controller) ..addStatusListener((status) { // dismissed 動(dòng)畫(huà)在起始點(diǎn)停止 // forward 動(dòng)畫(huà)正在正向執(zhí)行 // reverse 動(dòng)畫(huà)正在反向執(zhí)行 // completed 動(dòng)畫(huà)在終點(diǎn)停止 if (status == AnimationStatus.completed) { _controller.reverse(); //反向執(zhí)行 100-0 } else if (status == AnimationStatus.dismissed) { _color.value = _color2.value; // 獲取一個(gè)隨機(jī)彩虹色 _color2.value = getRandomColor(); _controller.forward(); //正向執(zhí)行 0-100 // 豆子已經(jīng)被吃了 從新加載豆子動(dòng)畫(huà) _controller2.forward(from: 0); //正向執(zhí)行 0-100 } }); animation2 = Tween(begin: 0.2, end: 1.0).animate(_controller2); // 啟動(dòng)動(dòng)畫(huà) 正向執(zhí)行 _controller.forward(); // 啟動(dòng)動(dòng)畫(huà) 0-1循環(huán)執(zhí)行 _controller2.forward(); // 這里這樣重復(fù)調(diào)用會(huì)導(dǎo)致兩次動(dòng)畫(huà)執(zhí)行時(shí)間不一致 時(shí)間長(zhǎng)了就不對(duì)應(yīng)了 // _controller2.repeat(); } @override void dispose() { _controller.dispose(); _controller2.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Center( child: CustomPaint( size: Size(50, 50), painter: Pain2Painter( _color, _color2, animation, animation2, Listenable.merge([ animation, animation2, _color, ]), ddSize: 8), )); } // 獲取一個(gè)隨機(jī)顏色 Color getRandomColor() { Random random = Random.secure(); int randomInt = random.nextInt(6); var colors = <Color>[ Colors.red, Colors.orange, Colors.yellow, Colors.green, Colors.blue, Colors.indigo, Colors.purple, ]; Color color = colors[randomInt]; while (color == _color2.value) { // 重復(fù)再選一個(gè) color = colors[random.nextInt(6)]; } return color; }
繪制吃豆人源碼:
class Pain2Painter extends CustomPainter { final ValueNotifier<Color> color; // 吃豆人的顏色 final ValueNotifier<Color> color2; // 豆子的的顏色 final Animation<double> angle; // 吃豆人 final Animation<double> angle2; // 豆 final double ddSize; // 豆豆大小 final Listenable listenable; Pain2Painter( this.color, this.color2, this.angle, this.angle2, this.listenable, {this.ddSize = 6}) : super(repaint: listenable); Paint _paint = Paint(); @override void paint(Canvas canvas, Size size) { canvas.clipRect(Offset.zero & size); canvas.translate(size.width / 2, size.height / 2); // 畫(huà)豆豆 canvas.drawOval( Rect.fromCenter( center: Offset( size.width / 2 + ddSize - angle2.value * (size.width / 2 + ddSize), 0), width: ddSize, height: ddSize), _paint..color = color2.value); //畫(huà)頭 _paint ..color = color.value ..style = PaintingStyle.fill; var rect = Rect.fromCenter( center: Offset(0, 0), width: size.width, height: size.height); /// 起始角度 /// angle.value 動(dòng)畫(huà)控制器的值 0.2~1 0是完全閉合就是 起始0~360° 1是完全張開(kāi) 起始 40°~ 280° 順時(shí)針 var a = angle.value * 40 / 180 * pi; // 繪制圓弧 canvas.drawArc(rect, a, 2 * pi - a * 2, true, _paint); //畫(huà)眼睛 canvas.drawOval( Rect.fromCenter( center: Offset(0, -size.height / 3), width: 8, height: 8), _paint..color = Colors.black87); canvas.drawOval( Rect.fromCenter( center: Offset(-1.5, -size.height / 3 - 1.5), width: 3, height: 3), _paint..color = Colors.white); } @override bool shouldRepaint(covariant Pain2Painter oldDelegate) { return oldDelegate.listenable != listenable; } }
至此,一個(gè)簡(jiǎn)單的吃豆人加載Loading就完成啦。再也不要到處都是菊花轉(zhuǎn)的樣式了。。。
總結(jié)
通過(guò)這個(gè)加載Loading動(dòng)畫(huà)可以重新復(fù)習(xí)下Flutter中繪制、動(dòng)畫(huà)的使用的聯(lián)動(dòng)使用、還有多狀態(tài)重繪機(jī)制,通過(guò)動(dòng)畫(huà)還可以改變吃豆的速度和吃豆的時(shí)間運(yùn)動(dòng)軌跡,有興趣可以試試哦。
以上就是基于Flutter制作一個(gè)吃豆人加載動(dòng)畫(huà)的詳細(xì)內(nèi)容,更多關(guān)于Flutter加載動(dòng)畫(huà)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android 使用【AIDL】調(diào)用外部服務(wù)的解決方法
本篇文章是對(duì)Android中使用AIDL調(diào)用外部服務(wù)的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06android異步請(qǐng)求服務(wù)器數(shù)據(jù)示例
這篇文章主要介紹了android異步請(qǐng)求服務(wù)器數(shù)據(jù)示例,需要的朋友可以參考下2014-03-03android 使用XStream解析xml的實(shí)例
下面小編就為大家分享一篇android 使用XStream解析xml的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01Android基準(zhǔn)配置文件Baseline?Profile方案提升啟動(dòng)速度
這篇文章主要為大家介紹了Android基準(zhǔn)配置文件Baseline?Profile方案提升啟動(dòng)速度示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02Android開(kāi)發(fā)筆記之Android中數(shù)據(jù)的存儲(chǔ)方式(二)
我們?cè)趯?shí)際開(kāi)發(fā)中,有的時(shí)候需要儲(chǔ)存或者備份比較復(fù)雜的數(shù)據(jù)。這些數(shù)據(jù)的特點(diǎn)是,內(nèi)容多、結(jié)構(gòu)大,比如短信備份等,通過(guò)本文給大家介紹Android開(kāi)發(fā)筆記之Android中數(shù)據(jù)的存儲(chǔ)方式(二),對(duì)android數(shù)據(jù)存儲(chǔ)方式相關(guān)知識(shí)感興趣的朋友一起學(xué)習(xí)吧2016-01-01listview的上滑下滑監(jiān)聽(tīng),上下滑監(jiān)聽(tīng)隱藏頂部選項(xiàng)欄的實(shí)例
下面小編就為大家分享一篇listview的上滑下滑監(jiān)聽(tīng),上下滑監(jiān)聽(tīng)隱藏頂部選項(xiàng)欄的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01基于Android引入IjkPlayer無(wú)法播放mkv格式視頻的解決方法
下面小編就為大家分享一篇基于Android引入IjkPlayer無(wú)法播放mkv格式視頻的解決方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01