Flutter實(shí)現(xiàn)抽屜動(dòng)畫
這篇會(huì)深化View拖拽實(shí)例,利用Flutter Animation、插值器以及AnimatedBuilder教大家實(shí)現(xiàn)帶動(dòng)畫的抽屜效果。先來(lái)看效果:
通過(guò)構(gòu)思,我們可以設(shè)想到實(shí)現(xiàn)抽屜的方式就是用Stack控件將兩個(gè)Widget疊加顯示,用GestureDetector監(jiān)聽(tīng)手勢(shì)滑動(dòng),動(dòng)態(tài)移動(dòng)頂層的Widget,當(dāng)監(jiān)聽(tīng)到手勢(shì)結(jié)束的時(shí)候根據(jù)手勢(shì)滑動(dòng)的距離動(dòng)態(tài)將頂部Widget利用動(dòng)畫效果滑動(dòng)到結(jié)束位置即可。
實(shí)現(xiàn)底部Widget
class DownDrawerWidget extends StatelessWidget { ? @override ? Widget build(BuildContext context) { ? ? return Container(child: Center(child: Text("底部Widget",),),); ? } }
這個(gè)Widget太簡(jiǎn)單了,就不細(xì)說(shuō)了。
實(shí)現(xiàn)頂部Widget
class UpDrawerWidget extends StatelessWidget { ? @override ? Widget build(BuildContext context) { ? ? return Container(child: Center(child: Text("頂部Widget",),),); ? } }
實(shí)現(xiàn)方式和底部是一樣的。
實(shí)現(xiàn)可以移動(dòng)的容器
上面兩個(gè)Widget都是單純用來(lái)顯示的Widget,因此繼承了StatelessWidget。接下來(lái)我們需要根據(jù)手勢(shì)動(dòng)態(tài)移動(dòng)頂部的Widget,因此需要繼承StatefulWidget。
// 頂部Widget class HomePageWidget extends StatefulWidget { ? @override ? State<StatefulWidget> createState() => HomePageState(); } class HomePageState extends State<HomePageWidget> ? ? with SingleTickerProviderStateMixin { ? @override ? void initState() {...} ? @override ? void dispose() {...} ? @override ? Widget build(BuildContext context) {...} ? void _onViewDragDown(DragDownDetails callback) {...} ? void _onViewDrag(DragUpdateDetails callback) {...} ? void _onViewDragUp(DragEndDetails callback) {...} }
初始化狀態(tài)initState()
這個(gè)方法是在Widget初始化的時(shí)候系統(tǒng)的回調(diào)函數(shù),我們需要在該函數(shù)中初始化動(dòng)畫
AnimationController controller; @override void initState() { ? ? // 初始化動(dòng)畫控制器,這里限定動(dòng)畫時(shí)常為200毫秒 ? ? controller = new AnimationController(vsync: this, duration: const Duration(milliseconds: 200)); ? ? // vsync對(duì)象會(huì)綁定動(dòng)畫的定時(shí)器到一個(gè)可視的widget,所以當(dāng)widget不顯示時(shí),動(dòng)畫定時(shí)器將會(huì)暫停,當(dāng)widget再次顯示時(shí),動(dòng)畫定時(shí)器重新恢復(fù)執(zhí)行,這樣就可以避免動(dòng)畫相關(guān)UI不在當(dāng)前屏幕時(shí)消耗資源。 ? ? // 當(dāng)使用vsync: this的時(shí)候,State對(duì)象必須with SingleTickerProviderStateMixin或TickerProviderStateMixin;TickerProviderStateMixin適用于多AnimationController的情況。 ? ? // 設(shè)置動(dòng)畫曲線,就是動(dòng)畫插值器 ? ? // 通過(guò)這個(gè)鏈接可以了解更多差值器,https://docs.flutter.io/flutter/animation/Curves-class.html,我們這里使用帶回彈效果的bounceOut。 ? ? CurvedAnimation curve = ? ? ? ? new CurvedAnimation(parent: controller, curve: Curves.bounceOut); ? ? // 增加動(dòng)畫監(jiān)聽(tīng),當(dāng)手勢(shì)結(jié)束的時(shí)候通過(guò)動(dòng)態(tài)計(jì)算到達(dá)目標(biāo)位置的距離實(shí)現(xiàn)動(dòng)畫效果。curve.value為當(dāng)前動(dòng)畫的值,取值范圍0~1。 ? ? curve.addListener(() { ? ? ? double animValue = curve.value; ? ? ? double offset = dragUpDownX - dragDownX; ? ? ? double toPosition; ? ? ? // 右滑 ? ? ? if (offset > 0) { ? ? ? ? if (offset > maxDragX / 5) { ? ? ? ? ? // 打開(kāi) ? ? ? ? ? toPosition = maxDragX; ? ? ? ? ? isOpenState = true; ? ? ? ? } else { ? ? ? ? ? if (isOpenState) { ? ? ? ? ? ? toPosition = maxDragX; ? ? ? ? ? ? isOpenState = true; ? ? ? ? ? } else { ? ? ? ? ? ? toPosition = 0.0; ? ? ? ? ? ? isOpenState = false; ? ? ? ? ? } ? ? ? ? } ? ? ? } else { ? ? ? ? if (offset < (-maxDragX / 2.0)) { ? ? ? ? ? // 關(guān) ? ? ? ? ? toPosition = 0.0; ? ? ? ? ? isOpenState = false; ? ? ? ? } else { ? ? ? ? ? if (isOpenState) { ? ? ? ? ? ? toPosition = maxDragX; ? ? ? ? ? ? isOpenState = true; ? ? ? ? ? } else { ? ? ? ? ? ? toPosition = 0.0; ? ? ? ? ? ? isOpenState = false; ? ? ? ? ? } ? ? ? ? } ? ? ? } ? ? ? dragOffset = (toPosition - dragUpDownX) * animValue + dragUpDownX; ? ? ? // 刷新位置 ? ? ? setState(() {}); ? ? }); ? }
結(jié)束Widget dispose()
當(dāng)Widget不可用將被回收的時(shí)候,系統(tǒng)會(huì)回調(diào)dispose()方法,我們?cè)谶@里回收動(dòng)畫。
@override void dispose() { ? ? controller.dispose(); }
記錄按下的位置
double dragDownX = 0.0; ? void _onViewDragDown(DragDownDetails callback) { ? ? dragDownX = callback.globalPosition.dx; ? }
拖動(dòng)的時(shí)候刷新View的位置
/** ? ?* 最大可拖動(dòng)位置 ? ?*/ ? final double maxDragX = 230.0; ? double dragOffset = 0.0; ? void _onViewDrag(DragUpdateDetails callback) { ? ? double tmpOffset = callback.globalPosition.dx - dragDownX; ? ? if (tmpOffset < 0) { ? ? ? tmpOffset += maxDragX; ? ? } ? ? // 邊緣檢測(cè) ? ? if (tmpOffset < 0) { ? ? ? tmpOffset = 0.0; ? ? } else if (tmpOffset >= maxDragX) { ? ? ? tmpOffset = maxDragX; ? ? } ? ? // 刷新 ? ? if (dragOffset != tmpOffset) { ? ? ? dragOffset = tmpOffset; ? ? ? setState(() {}); ? ? } ? }
離手的時(shí)候記錄位置并執(zhí)行動(dòng)畫
/** ? ?* 脫手時(shí)候的位置 ? ?*/ ? double dragUpDownX = 0.0; ? void _onViewDragUp(DragEndDetails callback) { ? ? dragUpDownX = dragOffset; ? ? // 執(zhí)行動(dòng)畫,每次都從第0幀開(kāi)始執(zhí)行 ? ? controller.forward(from: 0.0); ? }
支持移動(dòng)的Widget
@override ? Widget build(BuildContext context) { ? ? return Transform.translate( ? ? ? offset: Offset(dragOffset, 0.0), ? ? ? child: Container( ? ? ? ? child: GestureDetector( ? ? ? ? ? ? ? onHorizontalDragDown: _onViewDragDown, ? ? ? ? ? ? ? onVerticalDragDown: _onViewDragDown, ? ? ? ? ? ? ? onHorizontalDragUpdate: _onViewDrag, ? ? ? ? ? ? ? onVerticalDragUpdate: _onViewDrag, ? ? ? ? ? ? ? onHorizontalDragEnd: _onViewDragUp, ? ? ? ? ? ? ? onVerticalDragEnd: _onViewDragUp, ? ? ? ? ? ? ? child: Container( ? ? ? ? ? ? ? ? child: new UpDrawerWidget(), ? ? ? ? ? ),),),);}
Flutter動(dòng)畫
總結(jié)一下,想在Flutter中實(shí)現(xiàn)動(dòng)畫,需要先創(chuàng)建一個(gè)AnimationController控制器;如果有特殊的插值要求,再創(chuàng)建一個(gè)插值器,調(diào)用controller.forward()方法執(zhí)行動(dòng)畫,通過(guò)addListener()的回調(diào)改變對(duì)應(yīng)數(shù)值之后調(diào)用setState(() {})方法刷新位置即可。
Flutter API還提供AnimatedBuilder用來(lái)簡(jiǎn)化實(shí)現(xiàn)動(dòng)畫的復(fù)雜性,讓我們不用手動(dòng)調(diào)用addListener()方法。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android 中View.onDraw(Canvas canvas)的使用方法
這篇文章主要介紹了Android 中View.onDraw(Canvas canvas)的使用方法的相關(guān)資料,希望通過(guò)本文能幫助到大家,需要的朋友可以參考下2017-09-09Android MotionEvent中g(shù)etX()和getRawX()的區(qū)別實(shí)例詳解
這篇文章主要介紹了Android MotionEvent中g(shù)etX()和getRawX()的區(qū)別實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-03-03Android RecyclerView藝術(shù)般的控件使用完全解析
這篇文章主要介紹了Android RecyclerView藝術(shù)般的控件使用完全解析的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07android PopupWindow 和 Activity彈出窗口實(shí)現(xiàn)方式
本人小菜一個(gè)。目前只見(jiàn)過(guò)兩種彈出框的實(shí)現(xiàn)方式,第一種是最常見(jiàn)的PopupWindow,第二種也就是Activity的方式是前幾天才見(jiàn)識(shí)過(guò),需要的朋友可以參考下2012-11-11Android程序開(kāi)發(fā)仿新版QQ鎖屏下彈窗功能
最近做了一個(gè)項(xiàng)目,其中涉及到這樣一個(gè)功能:新版的qq能在鎖屏下彈窗顯示qq消息,下面小編抽時(shí)間把實(shí)現(xiàn)代碼分享給大家感興趣的朋友參考下吧2016-09-09Android使用Spinner實(shí)現(xiàn)城市級(jí)聯(lián)下拉框
這篇文章主要為大家詳細(xì)介紹了Android使用Spinner實(shí)現(xiàn)城市級(jí)聯(lián)下拉框,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12詳解Android平臺(tái)JSON預(yù)覽(JSON-handle)
這篇文章主要介紹了Android平臺(tái)JSON預(yù)覽(JSON-handle),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-09-09