Android Flutter實(shí)現(xiàn)仿閑魚動(dòng)畫效果
前言
目前正在做的項(xiàng)目,為了增加用戶的體驗(yàn)度,準(zhǔn)備增加一些動(dòng)畫效果,其中底部欄中間按鈕的點(diǎn)擊事件參考了閑魚的動(dòng)效,便在此基礎(chǔ)上仿寫了該動(dòng)效,并增加了一些新的效果。
動(dòng)效
閑魚動(dòng)效
仿寫效果
思路
根據(jù)UI的設(shè)計(jì)圖,對(duì)每個(gè)模塊設(shè)計(jì)好動(dòng)畫效果,本人主要設(shè)計(jì)了以下四個(gè)效果。
1、底部返回鍵旋轉(zhuǎn)動(dòng)畫
底部返回按鈕動(dòng)畫其實(shí)就是個(gè)旋轉(zhuǎn)動(dòng)畫,利用Transform.rotate
設(shè)置angle
的值即可,這里使用了GetX來對(duì)angle進(jìn)行動(dòng)態(tài)控制。
//返回鍵旋轉(zhuǎn)角度,初始旋轉(zhuǎn)45度,使其初始樣式為 + var angle = (pi / 4).obs; ///關(guān)閉按鈕旋轉(zhuǎn)動(dòng)畫控制器 late final AnimationController closeController; late final Animation<double> closeAnimation; ///返回鍵旋轉(zhuǎn)動(dòng)畫 closeController = AnimationController( duration: const Duration(milliseconds: 300), vsync: provider, ); ///返回鍵旋轉(zhuǎn)動(dòng)畫 closeController = AnimationController( duration: const Duration(milliseconds: 300), vsync: provider, ); ///頁面渲染完才開始執(zhí)行,不然第一次打開不會(huì)啟動(dòng)動(dòng)畫 WidgetsBinding.instance.addPostFrameCallback((duration) { closeAnimation = Tween(begin: pi / 4, end: pi / 2).animate(closeController) ..addListener(() { angle.value = closeAnimation.value; }); closeController.forward(); }); ///關(guān)閉按鈕點(diǎn)擊事件 void close() { ///反轉(zhuǎn)動(dòng)畫,并關(guān)閉頁面 Future.delayed( const Duration(milliseconds: 120), () { Get.back(); }); closeController.reverse(); } IconButton( onPressed: null, alignment: Alignment.center, icon: Transform.rotate( angle: controller.angle.value, child: SvgPicture.asset( "assets/user/ic-train-car-close.svg", width: 18, height: 18, color: Colors.black, ), ))
2、底部四個(gè)欄目變速上移動(dòng)畫+漸變動(dòng)畫
四個(gè)欄目其實(shí)就是個(gè)平移動(dòng)畫,只不過閑魚是四個(gè)欄目一起平移,而我選擇了變速平移,這樣視覺效果上會(huì)好一點(diǎn)。
//透明度變化 List<AnimationController> opacityControllerList = []; //上移動(dòng)畫,由于每個(gè)欄目的移動(dòng)速度不一樣,需要用List保存四個(gè)AnimationController, //如果想像閑魚那種整體上移,則只用一個(gè)AnimationController即可。 List<AnimationController> offsetControllerList = []; List<Animation<Offset>> offsetAnimationList = []; //之所以用addIf,是因?yàn)轫?xiàng)目中這幾個(gè)欄目的顯示是動(dòng)態(tài)顯示的,這里就直接寫成true Column( children: [] ..addIf( true, buildItem('assets/user/ic-train-nomal-car.webp',"學(xué)車加練","自主預(yù)約,快速拿證")) ..addIf( true, buildItem('assets/user/ic-train-fuuxn-car.webp',"有證復(fù)訓(xùn)","優(yōu)質(zhì)陪練,輕松駕車")) ..addIf( true, buildItem('assets/user/ic-train-jiaxun-car.webp',"模擬加訓(xùn)","考前加訓(xùn),臨考不懼")) ..addIf( true, buildItem('assets/user/ic-train-jiakao-car.webp',"駕考報(bào)名","快捷報(bào)名無門檻")) ..add(playWidget()) ..addAll([ 17.space, ]), ) //僅僅是為了在offsetController全部初始化完后執(zhí)行play() Widget playWidget() { //執(zhí)行動(dòng)畫 play(); return Container(); } int i = 0; Widget buildItem(String img,String tab,String slogan) { //由于底部欄目是動(dòng)態(tài)顯示的,需要在創(chuàng)建Widget時(shí)一同創(chuàng)建offsetController和offsetAnimation i++; AnimationController offsetController = AnimationController( duration: Duration(milliseconds: 100 + i * 20), vsync: this, ); Animation<Offset> offsetAnimation = Tween<Offset>( begin: const Offset(0, 2.5), end: const Offset(0, 0), ).animate(CurvedAnimation( parent: offsetController, // curve: Curves.easeInOutSine, curve: const Cubic(0.12, 0.28, 0.48, 1), )); AnimationController opacityController = AnimationController( duration: const Duration(milliseconds: 500), lowerBound: 0.2, upperBound: 1.0, vsync: this); opacityControllerList.add(opacityController); offsetControllerList.add(offsetController); offsetAnimationList.add(offsetAnimation); return SlideTransition( position: offsetAnimation, child: FadeTransition( opacity: opacityController, child: Container( margin: EdgeInsets.only(bottom: 16), height: 62, decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(12)), color: const Color(0xfffafafa)), child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ 24.space, Image.asset(img, width: 44, height: 44), 12.space, Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text(tab, style: const TextStyle( color: Color(0XFF000000), fontSize: 16, fontWeight: FontWeight.bold)), Text(slogan, style: const TextStyle( color: Color(0XFF6e6e6e), fontSize: 12)), ]).expanded, Image.asset("assets/user/ic-train-arrow.webp", width: 44, height: 44), 17.space ])).inkWell( onTap: () {}, delayMilliseconds: 50)), ); } //執(zhí)行動(dòng)畫 void play() async { for (int i = 0; i < offsetControllerList.length; i++) { opacityControllerList[i].forward(); ///欄目正序依次延遲(40 + 2 * i) * i的時(shí)間,曲線速率 Future.delayed(Duration(milliseconds: (40 + 2 * i) * i), () { offsetControllerList[i] .forward() .whenComplete(() => offsetControllerList[i].stop()); }); } } ///關(guān)閉按鈕點(diǎn)擊事件 void close() { ///反轉(zhuǎn)動(dòng)畫,并關(guān)閉頁面 Future.delayed( const Duration(milliseconds: 120), () { Get.back(); }); for (int i = offsetControllerList.length - 1; i >= 0; i--) { ///欄目倒敘依次延遲(40 + 2 * (offsetControllerList.length-1-i)) * (offsetControllerList.length-1-i))的時(shí)間 Future.delayed( Duration( milliseconds: (40 + 2 * (offsetControllerList.length-1-i)) * (offsetControllerList.length-1-i)), () { offsetControllerList[i].reverse(); }); } opacityTopController.reverse(); }
3、中間圖片漸變動(dòng)畫
漸變動(dòng)畫使用FadeTransition
即可。
///圖片透明度漸變動(dòng)畫控制器 late final AnimationController imgController; ///圖片透明度漸變動(dòng)畫 imgController = AnimationController( duration: const Duration(milliseconds: 500), lowerBound: 0.0, upperBound: 1.0, vsync: provider); imgController.forward().whenComplete(() => imgController.stop()); ///漸變過渡 FadeTransition( opacity: imgController, child: Image.asset("assets/user/ic-traincar-guide.webp"), ), ///關(guān)閉按鈕點(diǎn)擊事件 void close() { imgController.reverse(); }
4、頂部文案漸變動(dòng)畫+下移動(dòng)畫
///頂部標(biāo)題下移動(dòng)畫控制器 late final AnimationController offsetTopController; late final Animation<Offset> offsetTopAnimation; ///頂部標(biāo)題漸變動(dòng)畫控制器 late final AnimationController opacityTopController; ///頂部標(biāo)題上移動(dòng)畫 offsetTopController = AnimationController( duration: const Duration(milliseconds: 300), vsync: provider, ); offsetTopController .forward() .whenComplete(() => offsetTopController.stop()); offsetTopAnimation = Tween<Offset>( begin: const Offset(0, -0.8), end: const Offset(0, 0), ).animate(CurvedAnimation( parent: offsetTopController, curve: Curves.easeInOutCubic, )); offsetTopController .forward() .whenComplete(() => offsetTopController.stop()); //UI SlideTransition( position: offsetTopAnimation, child: FadeTransition( opacity: opacityTopController, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ 80.space, const Text( '練車指南', style: TextStyle( color: Color(0XFF141414), fontSize: 32, fontWeight: FontWeight.w800, ), ), 2.space, const Text('易練只為您提供優(yōu)質(zhì)教練,為您的安全保駕護(hù)航', style: TextStyle( color: Color(0XFF141414), fontSize: 15)), ], ))), ///關(guān)閉按鈕點(diǎn)擊事件 void close() { offsetTopController.reverse(); opacityTopController.reverse(); }
5、注銷動(dòng)畫
最后,在關(guān)閉頁面的時(shí)候不要忘記注銷動(dòng)畫。
///關(guān)閉時(shí)注銷動(dòng)畫 void dispose() { for (int i = offsetControllerList.length - 1; i > 0; i--) { offsetControllerList[i].dispose(); } offsetTopController.dispose(); opacityTopController.dispose(); imgController.dispose(); closeController.dispose(); }
以上就是Android Flutter實(shí)現(xiàn)仿閑魚動(dòng)畫效果的詳細(xì)內(nèi)容,更多關(guān)于Android Flutter仿閑魚動(dòng)畫的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android圖片的Base64編碼與解碼及解碼Base64圖片方法
Base64是網(wǎng)絡(luò)上最常見的用于傳輸8Bit字節(jié)碼的編碼方式之一,Base64就是一種基于64個(gè)可打印字符來表示二進(jìn)制數(shù)據(jù)的方法。接下來通過本文給大家分享Android圖片的Base64編碼與解碼及解碼Base64圖片,需要的朋友參考下吧2017-12-12Android游戲開發(fā)實(shí)踐之人物移動(dòng)地圖的平滑滾動(dòng)處理
玩過rpg游戲的朋友應(yīng)該都知道RPG的游戲地圖一般都比較大 今天我和大家分享一下在RPG游戲中如何來處理超出手機(jī)屏幕大小的游戲地圖。2014-06-06Android GestureDetector用戶手勢(shì)檢測(cè)實(shí)例講解
這篇文章主要為大家詳細(xì)介紹了Android GestureDetector用戶手勢(shì)檢測(cè)實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03Service與Activity之間的通信(同一進(jìn)程)
這篇文章主要介紹了Service與Activity之間的通信(同一進(jìn)程)的相關(guān)資料,需要的朋友可以參考下2016-03-03