Flutter實(shí)現(xiàn)抽屜動(dòng)畫
這篇會(huì)深化View拖拽實(shí)例,利用Flutter Animation、插值器以及AnimatedBuilder教大家實(shí)現(xiàn)帶動(dòng)畫的抽屜效果。先來看效果:

通過構(gòu)思,我們可以設(shè)想到實(shí)現(xiàn)抽屜的方式就是用Stack控件將兩個(gè)Widget疊加顯示,用GestureDetector監(jiān)聽手勢滑動(dòng),動(dòng)態(tài)移動(dòng)頂層的Widget,當(dāng)監(jiān)聽到手勢結(jié)束的時(shí)候根據(jù)手勢滑動(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太簡單了,就不細(xì)說了。
實(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都是單純用來顯示的Widget,因此繼承了StatelessWidget。接下來我們需要根據(jù)手勢動(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對象會(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對象必須with SingleTickerProviderStateMixin或TickerProviderStateMixin;TickerProviderStateMixin適用于多AnimationController的情況。
? ? // 設(shè)置動(dòng)畫曲線,就是動(dòng)畫插值器
? ? // 通過這個(gè)鏈接可以了解更多差值器,https://docs.flutter.io/flutter/animation/Curves-class.html,我們這里使用帶回彈效果的bounceOut。
? ? CurvedAnimation curve =
? ? ? ? new CurvedAnimation(parent: controller, curve: Curves.bounceOut);
? ? // 增加動(dòng)畫監(jiān)聽,當(dāng)手勢結(jié)束的時(shí)候通過動(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) {
? ? ? ? ? // 打開
? ? ? ? ? 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()方法,我們在這里回收動(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;
? ? }
? ? // 邊緣檢測
? ? 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幀開始執(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)畫,通過addListener()的回調(diào)改變對應(yīng)數(shù)值之后調(diào)用setState(() {})方法刷新位置即可。
Flutter API還提供AnimatedBuilder用來簡化實(shí)現(xiàn)動(dòng)畫的復(fù)雜性,讓我們不用手動(dòng)調(diào)用addListener()方法。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
android 圖片操作(縮放移動(dòng)) 實(shí)例代碼
android 圖片操作(縮放移動(dòng)) 實(shí)例代碼,需要的朋友可以參考一下2013-06-06
Flutter利用SizeTransition實(shí)現(xiàn)組件飛入效果
本文將為大家介紹SizeTransition,SizeTransition用于更改子組件的尺寸來實(shí)現(xiàn)動(dòng)畫,支持垂直方向或水平方向修改動(dòng)畫。本文將利用其實(shí)現(xiàn)組件飛入效果,需要的可以參考一下2022-04-04
Android onActivityResult和setResult方法詳解及使用
這篇文章主要介紹了Android onActivityResult和setResult方法詳解及使用的相關(guān)資料,這里提供實(shí)例,幫助大家學(xué)習(xí)理解,需要的朋友可以參考下2016-12-12
Flutter?將Dio請求轉(zhuǎn)發(fā)原生網(wǎng)絡(luò)庫的實(shí)現(xiàn)方案
這篇文章主要介紹了Flutter?將Dio請求轉(zhuǎn)發(fā)原生網(wǎng)絡(luò)庫,需要注意添加NativeNetInterceptor,如果有多個(gè)攔截器,例如LogInterceptors等等,需要將NativeNetInterceptor放到最后,需要的朋友可以參考下2022-05-05
Android 第三方應(yīng)用接入微信平臺(tái)研究情況分享(一)
微信平臺(tái)開放后倒是挺火的,許多第三方應(yīng)用都想試下接入微信這個(gè)平臺(tái),畢竟可以利用微信建立起來的關(guān)系鏈來拓展自己的應(yīng)用還是挺不錯(cuò)的 最近由于實(shí)習(xí)需要也在研究這個(gè)東西,這里把我的整個(gè)研究情況給出來2013-01-01
Android編程實(shí)現(xiàn)攝像頭臨摹效果的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)攝像頭臨摹效果的方法,涉及Android權(quán)限控制、布局及攝像頭功能調(diào)用等相關(guān)操作技巧,需要的朋友可以參考下2017-09-09
一文理解Android系統(tǒng)中強(qiáng)指針的實(shí)現(xiàn)
因?yàn)锳ndroid中很多地方代碼是用C++編寫,為了能夠保證C++中指針能夠被正確的釋放,于是Android引入了其實(shí)在C++中已經(jīng)有的智能指針技術(shù)2021-10-10
Android改變ExpandableListView的indicator圖標(biāo)實(shí)現(xiàn)方法
這篇文章主要介紹了Android改變ExpandableListView的indicator圖標(biāo)實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了改變ExpandableListView的indicator圖標(biāo)相關(guān)步驟與實(shí)現(xiàn)技巧,涉及Android配置文件的修改,需要的朋友可以參考下2016-03-03
Android?Jetpack?組件LiveData源碼解析
這篇文章主要為大家介紹了Android?Jetpack?組件LiveData源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03

