利用Flutter制作一個(gè)會(huì)飛的菜單
簡(jiǎn)介
flutter中自帶了drawer組件,可以實(shí)現(xiàn)通用的菜單功能,那么有沒(méi)有一種可能,我們可以通過(guò)自定義動(dòng)畫(huà)來(lái)實(shí)現(xiàn)一個(gè)別樣的菜單呢?
答案是肯定的,一起來(lái)看看吧。
定義一個(gè)菜單項(xiàng)目
因?yàn)檫@里的主要目的是實(shí)現(xiàn)菜單的動(dòng)畫(huà),所以這里的菜單比較簡(jiǎn)單,我們的menu是一個(gè)StatefulWidget,里面就是一個(gè)Column組件,column中有四行詩(shī):
static const _menuTitles = [ '遲日江山麗', '春風(fēng)花草香', '泥融飛燕子', '沙暖睡鴛鴦', ]; Widget build(BuildContext context) { return Container( color: Colors.white, child:_buildContent() ); } Widget _buildContent() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 16), ..._buildListItems() ], ); } List<Widget> _buildListItems() { final listItems = <Widget>[]; for (var i = 0; i < _menuTitles.length; ++i) { listItems.add( Padding( padding: const EdgeInsets.symmetric(horizontal: 36.0, vertical: 16), child: Text( _menuTitles[i], textAlign: TextAlign.center, style: const TextStyle( fontSize: 24, fontWeight: FontWeight.w500, ), ), ) ); } return listItems; }
讓menu動(dòng)起來(lái)
怎么讓menu動(dòng)起來(lái)呢?我們需要給最外層的AnimateMenuApp添加一個(gè)AnimationController,所以需要在_AnimateMenuAppState添加SingleTickerProviderStateMixin的mixin,如下所示:
class _AnimateMenuAppState extends State<AnimateMenuApp> with SingleTickerProviderStateMixin { late AnimationController _drawerSlideController;
然后在initState中對(duì)_drawerSlideController進(jìn)行初始化:
void initState() { super.initState(); _drawerSlideController = AnimationController( vsync: this, duration: const Duration(milliseconds: 150), ); }
在讓menu動(dòng)起來(lái)之前,我們需要設(shè)計(jì)一下動(dòng)畫(huà)的樣式。假如我們的動(dòng)畫(huà)是讓menu從右向左飛出。那么我們可以使用FractionalTranslation來(lái)進(jìn)行offset進(jìn)行位置變換。
并且當(dāng)菜單沒(méi)有開(kāi)啟的時(shí)候,我們需要顯示一個(gè)空的組件,這里用SizedBox來(lái)替代。
當(dāng)菜單開(kāi)啟的時(shí)候,就執(zhí)行這個(gè)FractionalTranslation的動(dòng)畫(huà),所以我們的build方法需要這樣寫(xiě):
Widget _buildDrawer() { return AnimatedBuilder( animation: _drawerSlideController, builder: (context, child) { return FractionalTranslation( translation: Offset(1.0 - _drawerSlideController.value, 0.0), child: _isDrawerClosed() ? const SizedBox() : const Menu(), ); }, ); }
FractionalTranslation中的Offset是根據(jù)_drawerSlideController的value來(lái)進(jìn)行變化的。
那么_drawerSlideController的value怎么變化呢?
我們定義一個(gè)_toggleDrawer方法,在點(diǎn)擊菜單按鈕的時(shí)候來(lái)觸發(fā)這個(gè)方法,從而實(shí)現(xiàn)_drawerSlideController的value變化:
void _toggleDrawer() { if (_isDrawerOpen() || _isDrawerOpening()) { _drawerSlideController.reverse(); } else { _drawerSlideController.forward(); } }
同時(shí),我們定義下面幾個(gè)判斷菜單狀態(tài)的方法:
bool _isDrawerOpen() { return _drawerSlideController.value == 1.0; } bool _isDrawerOpening() { return _drawerSlideController.status == AnimationStatus.forward; } bool _isDrawerClosed() { return _drawerSlideController.value == 0.0; }
因?yàn)椴藛螆D標(biāo)需要根據(jù)菜單狀態(tài)來(lái)發(fā)生改變,菜單的狀態(tài)又是依賴(lài)于_drawerSlideController,所以,我們把IconButton放到一個(gè)AnimatedBuilder里面,從而實(shí)現(xiàn)動(dòng)態(tài)變化的效果:
PreferredSizeWidget _buildAppBar() { return AppBar( title: const Text( '動(dòng)畫(huà)菜單', style: TextStyle( color: Colors.black, ), ), backgroundColor: Colors.transparent, elevation: 0.0, automaticallyImplyLeading: false, actions: [ AnimatedBuilder( animation: _drawerSlideController, builder: (context, child) { return IconButton( onPressed: _toggleDrawer, icon: _isDrawerOpen() || _isDrawerOpening() ? const Icon( Icons.clear, color: Colors.black, ) : const Icon( Icons.menu, color: Colors.black, ), ); }, ), ], ); }
最后實(shí)現(xiàn)的效果如下:
添加菜單內(nèi)部的動(dòng)畫(huà)
上面的例子中整個(gè)菜單是作為一個(gè)整體來(lái)動(dòng)畫(huà)的,有沒(méi)有可能菜單里面的每一個(gè)item也有自己的動(dòng)畫(huà)呢?
答案當(dāng)然是肯定的。
我們只需要在上面的基礎(chǔ)上將menu組件添加動(dòng)畫(huà)支持即可:
class _MenuState extends State<Menu> with SingleTickerProviderStateMixin
動(dòng)畫(huà)中的位移我們選擇使用Transform.translate,同時(shí)還添加了淡入淡出的效果,也就是把上面例子中的Padding用AnimatedBuilder包裹起來(lái),如下所示:
List<Widget> _buildListItems() { final listItems = <Widget>[]; for (var i = 0; i < _menuTitles.length; ++i) { listItems.add( AnimatedBuilder( animation: _itemController, builder: (context, child) { final animationPercent = Curves.easeOut.transform( _itemSlideIntervals[i].transform(_itemController.value), ); final opacity = animationPercent; final slideDistance = (1.0 - animationPercent) * 150; return Opacity( opacity: opacity, child: Transform.translate( offset: Offset(slideDistance, 0), child: child, ), ); }, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 36.0, vertical: 16), child: Text( _menuTitles[i], textAlign: TextAlign.center, style: const TextStyle( fontSize: 24, fontWeight: FontWeight.w500, ), ), ), ), ); } return listItems; }
AnimatedBuilder中的builder返回的是一個(gè)Opacity對(duì)象,里面包含了opacity和child兩個(gè)屬性。其中最終要的一個(gè)變化值是animationPercent,這個(gè)值是根據(jù)_itemController的value和初始設(shè)置的各個(gè)item的變化時(shí)間來(lái)決定的。
每個(gè)item的值是不一樣的:
void _createAnimationIntervals() { for (var i = 0; i < _menuTitles.length; ++i) { final startTime = _initialDelayTime + (_staggerTime * i); final endTime = startTime + _itemSlideTime; _itemSlideIntervals.add( Interval( startTime.inMilliseconds / _animationDuration.inMilliseconds, endTime.inMilliseconds / _animationDuration.inMilliseconds, ), ); } }
最后運(yùn)行結(jié)果如下:
總結(jié)
在flutter中一切皆可動(dòng)畫(huà),我們只需要掌握動(dòng)畫(huà)創(chuàng)作的訣竅即可。
本文的例子:https://github.com/ddean2009/learn-flutter.git
到此這篇關(guān)于利用Flutter制作一個(gè)會(huì)飛的菜單的文章就介紹到這了,更多相關(guān)Flutter菜單內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android中ListView綁定CheckBox實(shí)現(xiàn)全選增加和刪除功能(DEMO)
本文通過(guò)實(shí)例給大家講解了Android中ListView綁定CheckBox實(shí)現(xiàn)全選增加和刪除功能(DEMO)的代碼,對(duì)android checkbox全選相關(guān)知識(shí)感興趣的朋友一起學(xué)習(xí)吧2016-08-08Android多渠道打包時(shí)獲取當(dāng)前渠道的方法
這篇文章主要介紹了Android多渠道打包時(shí)獲取當(dāng)前渠道的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01Android編程之代碼創(chuàng)建布局實(shí)例分析
這篇文章主要介紹了Android編程之代碼創(chuàng)建布局的方法,結(jié)合實(shí)例形式分析了Android通過(guò)代碼創(chuàng)建布局的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11Android自定義View之RadioGroup實(shí)現(xiàn)跨多行顯示
這篇文章主要介紹了Android自定義View之RadioGroup實(shí)現(xiàn)跨多行顯示,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11Android超詳細(xì)講解組件LinearLayout的使用
LinearLayout又稱(chēng)作線性布局,是一種非常常用的布局。正如它的名字所描述的一樣,這個(gè)布局會(huì)將它所包含的控件在線性方向上依次排列。既然是線性排列,肯定就不僅只有一個(gè)方向,這里一般只有兩個(gè)方向:水平方向和垂直方向2022-03-03Flutter進(jìn)階之實(shí)現(xiàn)動(dòng)畫(huà)效果(二)
這篇文章主要為大家詳細(xì)介紹了Flutter進(jìn)階之實(shí)現(xiàn)動(dòng)畫(huà)效果的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-08-08