Flutter實(shí)現(xiàn)心動(dòng)的動(dòng)畫特效
為了追求更好的用戶體驗(yàn),有時(shí)候我們需要一個(gè)類似心跳一樣跳動(dòng)著的控件來(lái)吸引用戶的注意力,這是一個(gè)小小的優(yōu)化需求,但是在 Flutter 里動(dòng)畫兩件套就像裹腳布一樣臭長(zhǎng),所以需要像封裝一個(gè) AnimatedWidget,解放生產(chǎn)力。
實(shí)現(xiàn)動(dòng)畫
混入 SingleTickerProviderStateMixin
當(dāng)創(chuàng)建一個(gè) AnimationController 時(shí),需要傳遞一個(gè)vsync
參數(shù),存在vsync
時(shí)會(huì)防止動(dòng)畫的UI不在當(dāng)前屏幕時(shí)消耗不必要的資源。 通過(guò)混入 SingleTickerProviderStateMixin 。
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin{}
創(chuàng)建動(dòng)畫
創(chuàng)建一個(gè)間隔將近一秒鐘的動(dòng)畫控制器:
late final AnimationController animController; @override void initState() { super.initState(); animController = AnimationController( duration: const Duration(milliseconds: 800), vsync: this, ); }
心跳動(dòng)畫是從小變大,再變小,所以需要一個(gè)值大小變化的動(dòng)畫:
late final Animation<double> animation; @override void initState() { super.initState(); animController = AnimationController( duration: const Duration(milliseconds: 800), vsync: this, ); animation = Tween<double>( begin: 0.9, end: 1.05, ); }
心跳是不間斷的,所以需要監(jiān)聽(tīng)動(dòng)畫完成時(shí)恢復(fù)動(dòng)畫,再繼續(xù)開(kāi)始動(dòng)畫:
animation = Tween<double>( begin: 0.9, end: 1.05, ).animate(animController) ..addListener(() { setState(() {}); }) ..addStatusListener((status) { if (status == AnimationStatus.completed) { animController.reverse(); } else if (status == AnimationStatus.dismissed) { animController.forward(); } });
使用縮放控件:
Transform.scale( scale: animation.value, child: const FlutterLogo( size: 80, ), ),
為了跳動(dòng)效果,突出跳動(dòng)動(dòng)畫,把縮回去的時(shí)間改短:
animController = AnimationController( reverseDuration: const Duration(milliseconds: 700), duration: const Duration(milliseconds: 800), vsync: this, );
最后別忘了釋放資源:
@override void dispose() { animController.dispose(); super.dispose(); }
抽離成小組件
為了每次用到類似的動(dòng)畫只需引入即可,需要分離動(dòng)畫和顯示的組件。新建一個(gè)BounceWidget
,包含動(dòng)畫,然后可以傳入U(xiǎn)I組件:
class BounceWidget extends StatefulWidget { final Widget child; const BounceWidget({ Key? key, required this.child, }) : super(key: key); @override State<BounceWidget> createState() => _BounceWidgetState(); }
繼續(xù)實(shí)現(xiàn)動(dòng)畫:
class _BounceWidgetState extends State<BounceWidget> with SingleTickerProviderStateMixin { late Animation<double> animation; late AnimationController animController; @override void initState() { super.initState(); animController = AnimationController( reverseDuration: const Duration(milliseconds: 700), duration: const Duration(milliseconds: 800), vsync: this, ); animation = Tween<double>( begin: 0.9, end: 1.05, ).animate(animController) ..addListener(() { setState(() {}); }) ..addStatusListener((status) { if (status == AnimationStatus.completed) { animController.reverse(); } else if (status == AnimationStatus.dismissed) { animController.forward(); } }); animController.forward(); } @override Widget build(BuildContext context) { return Transform.scale( scale: animation.value, child: widget.child, ); } @override void dispose() { animController.dispose(); super.dispose(); } }
去引入動(dòng)畫:
Center( child: BounceWidget( child: FlutterLogo( size: 80, ), ),
完整代碼
void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({Key? key, required this.title}) : super(key: key); final String title; @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Padding( padding: const EdgeInsets.only(top: 80, left: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: const <Widget>[ Text( "心動(dòng)的", style: TextStyle( fontSize: 28, color: Colors.black, ), ), Text( "感覺(jué)", style: TextStyle( fontSize: 48, color: Colors.black, ), ), Center( child: BounceWidget( child: FlutterLogo( size: 80, ), ), ), ], ), ), ); } }
以上就是Flutter實(shí)現(xiàn)心動(dòng)的動(dòng)畫特效的詳細(xì)內(nèi)容,更多關(guān)于Flutter動(dòng)畫特效的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android系列---JSON數(shù)據(jù)解析的實(shí)例
JSON(JavaScript Object Notation)和XML,并稱為客戶端和服務(wù)端交互解決方案的倚天劍和屠龍刀,這篇文章主要介紹了Android系列---JSON數(shù)據(jù)解析的實(shí)例,有興趣的可以了解一下。2016-11-11詳解Android數(shù)據(jù)存儲(chǔ)—使用SQLite數(shù)據(jù)庫(kù)
本篇文章主要介紹了詳解Android數(shù)據(jù)存儲(chǔ)—使用SQLite數(shù)據(jù)庫(kù),具有一定的參考價(jià)值,有興趣的可以了解一下。2017-03-03Android入門之Style與Theme用法實(shí)例解析
這篇文章主要介紹了Android入門之Style與Theme用法,非常實(shí)用的功能,需要的朋友可以參考下2014-08-08Android實(shí)現(xiàn)QQ新用戶注冊(cè)界面遇到問(wèn)題及解決方法
這篇文章主要介紹了Android實(shí)現(xiàn)QQ新用戶注冊(cè)界面遇到問(wèn)題及解決方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09Android提高之MediaPlayer播放網(wǎng)絡(luò)視頻的實(shí)現(xiàn)方法
這篇文章主要介紹了Android的MediaPlayer播放網(wǎng)絡(luò)視頻的實(shí)現(xiàn)方法,是一個(gè)非常實(shí)用的功能,需要的朋友可以參考下2014-08-08屏蔽RecyclerView單邊滑動(dòng)到頭陰影(fadingEdge)的方法
這篇文章主要給大家介紹了如何屏蔽RecyclerView單邊滑動(dòng)到頭陰影(fadingEdge)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-04-04Android開(kāi)發(fā)之AAR文件的生成與使用步驟
Android中的aar主要是針對(duì)于Android Library而言的,可以簡(jiǎn)單的理解為是對(duì)Android Library的打包,這個(gè)包的格式為.aar,下面這篇文章主要給大家介紹了關(guān)于Android開(kāi)發(fā)之AAR文件的生成與使用步驟的相關(guān)資料,需要的朋友可以參考下2022-07-07RxJava2和Retrofit2封裝教程(整潔、簡(jiǎn)單、實(shí)用)
這篇文章主要給大家介紹了關(guān)于RxJava2和Retrofit2封裝的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),整潔、簡(jiǎn)單、實(shí)用,非常適合大家學(xué)習(xí)使用,需要的朋友可以參考下2018-11-11android閱讀器長(zhǎng)按選擇文字功能實(shí)現(xiàn)代碼
本篇文章主要介紹了android閱讀器長(zhǎng)按選擇文字功能實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,有興趣的可以了解一下2017-07-07