Flutter實(shí)現(xiàn)簡(jiǎn)單的下載按鈕動(dòng)畫
簡(jiǎn)介
我們?cè)赼pp的開發(fā)過程中經(jīng)常會(huì)用到一些表示進(jìn)度類的動(dòng)畫效果,比如一個(gè)下載按鈕,我們希望按鈕能夠動(dòng)態(tài)顯示下載的進(jìn)度,這樣可以給用戶一些直觀的印象,那么在flutter中一個(gè)下載按鈕的動(dòng)畫應(yīng)該如何制作呢?
一起來看看吧。
定義下載的狀態(tài)
我們?cè)谡嬲_發(fā)下載按鈕之前,首先定義幾個(gè)下載的狀態(tài),因?yàn)椴煌南螺d狀態(tài)導(dǎo)致的按鈕展示樣子也是不一樣的,我們用下面的一個(gè)枚舉類來設(shè)置按鈕的下載狀態(tài):
enum DownloadStatus {
notDownloaded,
fetchingDownload,
downloading,
downloaded,
}基本上有4個(gè)狀態(tài),分別是沒有下載,準(zhǔn)備下載但是還沒有獲取到下載的資源鏈接,獲取到下載資源正在下載中,最后是下載完畢。
定義DownloadButton的屬性
這里我們需要自定義一個(gè)DownloadButton組件,這個(gè)組件肯定是一個(gè)StatelessWidget,所有的狀態(tài)信息都是由外部傳入的。
我們需要根據(jù)下載狀態(tài)來指定DownloadButton的樣式,所以需要一個(gè)status屬性。下載過程中還有一個(gè)下載的進(jìn)度條,所以我們需要一個(gè)downloadProgress屬性。
另外在點(diǎn)擊下載按鈕的時(shí)候會(huì)觸發(fā)onDownload事件,下載過程中可以觸發(fā)onCancel事件,下載完畢之后可以出發(fā)onOpen事件。
最后因?yàn)槭且粋€(gè)動(dòng)畫組件,所以還需要一個(gè)動(dòng)畫的持續(xù)時(shí)間屬性transitionDuration。
所以我們的DownloadButton需要下面一些屬性:
class DownloadButton extends StatelessWidget {
...
const DownloadButton({
super.key,
required this.status,
this.downloadProgress = 0.0,
required this.onDownload,
required this.onCancel,
required this.onOpen,
this.transitionDuration = const Duration(milliseconds: 500),
});讓DownloadButton的屬性可以動(dòng)態(tài)變化
上面提到了DownloadButton是一個(gè)StatelessWidget,所有的屬性都是由外部傳入的,但是對(duì)于一個(gè)動(dòng)畫的DownloadButton來說,status,downloadProgress這些信息都是會(huì)動(dòng)態(tài)變化的,那么怎么才能讓變化的屬性傳到DownloadButton中進(jìn)行組件的重繪呢?
因?yàn)樯婕暗綇?fù)雜的狀態(tài)變化,所以簡(jiǎn)單的AnimatedWidget已經(jīng)滿足不了我們的需求了,這里就需要用到flutter中的AnimatedBuilder組件了。
AnimatedBuilder是AnimatedWidget的子類,它有兩個(gè)必須的參數(shù),分別是animation和builder。
其中animation是一個(gè)Listenable對(duì)象,它可以是Animation,ChangeNotifier或者等。
AnimatedBuilder會(huì)通過監(jiān)聽animation的變動(dòng)情況,來重新構(gòu)建builder中的組件。buidler方法可以從animation中獲取對(duì)應(yīng)的變動(dòng)屬性。
這樣我們創(chuàng)建一個(gè)Listenable的DownloadController對(duì)象,然后把DownloadButton用AnimatedBuilder封裝起來,就可以實(shí)時(shí)監(jiān)測(cè)到downloadStatus和downloadProgress的變化了。
如下所示:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('下載按鈕')),
body: Center(
child: SizedBox(
width: 96,
child: AnimatedBuilder(
animation: _downloadController,
builder: (context, child) {
return DownloadButton(
status: _downloadController.downloadStatus,
downloadProgress: _downloadController.progress,
onDownload: _downloadController.startDownload,
onCancel: _downloadController.stopDownload,
onOpen: _downloadController.openDownload,
);
},
),
),
),
);
}定義downloadController
downloadController是一個(gè)Listenable對(duì)象,這里我們讓他實(shí)現(xiàn)ChangeNotifier接口,并且定義了兩個(gè)獲取下載狀態(tài)和下載進(jìn)度的方法,同時(shí)也定義了三個(gè)點(diǎn)擊觸發(fā)事件:
abstract class DownloadController implements ChangeNotifier {
DownloadStatus get downloadStatus;
double get progress;
void startDownload();
void stopDownload();
void openDownload();
}接下來我們來實(shí)現(xiàn)這個(gè)抽象方法:
class MyDownloadController extends DownloadController
with ChangeNotifier {
MyDownloadController({
DownloadStatus downloadStatus = DownloadStatus.notDownloaded,
double progress = 0.0,
required VoidCallback onOpenDownload,
}) : _downloadStatus = downloadStatus,
_progress = progress,
_onOpenDownload = onOpenDownload;startDownload,stopDownload這兩個(gè)方法是跟下載狀態(tài)和下載進(jìn)度相關(guān)的,先看下stopDownload:
void stopDownload() {
if (_isDownloading) {
_isDownloading = false;
_downloadStatus = DownloadStatus.notDownloaded;
_progress = 0.0;
notifyListeners();
}
}可以看到這個(gè)方法最后需要調(diào)用notifyListeners來通知AnimatedBuilder來進(jìn)行組件的重繪。
startDownload方法會(huì)復(fù)雜一點(diǎn),我們需要模擬下載狀態(tài)的變化和進(jìn)度的變化,如下所示:
Future<void> _doDownload() async {
_isDownloading = true;
_downloadStatus = DownloadStatus.fetchingDownload;
notifyListeners();
// fetch耗時(shí)1秒鐘
await Future<void>.delayed(const Duration(seconds: 1));
if (!_isDownloading) {
return;
}
// 轉(zhuǎn)換到下載的狀態(tài)
_downloadStatus = DownloadStatus.downloading;
notifyListeners();
const downloadProgressStops = [0.0, 0.15, 0.45, 0.8, 1.0];
for (final progress in downloadProgressStops) {
await Future<void>.delayed(const Duration(seconds: 1));
if (!_isDownloading) {
return;
}
//更新progress
_progress = progress;
notifyListeners();
}
await Future<void>.delayed(const Duration(seconds: 1));
if (!_isDownloading) {
return;
}
//切換到下載完畢狀態(tài)
_downloadStatus = DownloadStatus.downloaded;
_isDownloading = false;
notifyListeners();
}
}因?yàn)橄螺d是一個(gè)比較長(zhǎng)的過程,所以這里用的是異步方法,在異步方法中進(jìn)行通知。
定義DownloadButton的細(xì)節(jié)
有了可以動(dòng)態(tài)變化的狀態(tài)和進(jìn)度之后,我們就可以在DownloadButton中構(gòu)建具體的頁面展示了。
在未開始下載之前,我們希望downloadButton是一個(gè)長(zhǎng)條形的按鈕,按鈕上的文字顯示GET,下載過程中希望是一個(gè)類似CircularProgressIndicator的動(dòng)畫,可以根據(jù)下載進(jìn)度來動(dòng)態(tài)變化。
同時(shí),在下載過程中,我們希望能夠隱藏之前的長(zhǎng)條形按鈕。 下載完畢之后,再次展示長(zhǎng)條形按鈕,這時(shí)候按鈕上的文字顯示為OPEN。
因?yàn)閯?dòng)畫比較復(fù)雜,所以我們將動(dòng)畫組件分成兩部分,第一部分就是展示和隱藏長(zhǎng)條形的按鈕,這里我們使用AnimatedOpacity來實(shí)現(xiàn)文字的淡入淡出的效果,并將AnimatedOpacity封裝在AnimatedContainer中,實(shí)現(xiàn)decoration的動(dòng)畫效果:
return AnimatedContainer(
duration: transitionDuration,
curve: Curves.ease,
width: double.infinity,
decoration: shape,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: AnimatedOpacity(
duration: transitionDuration,
opacity: isDownloading || isFetching ? 0.0 : 1.0,
curve: Curves.ease,
child: Text(
isDownloaded ? 'OPEN' : 'GET',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.button?.copyWith(
fontWeight: FontWeight.bold,
color: CupertinoColors.activeBlue,
),
),
),
),
);實(shí)現(xiàn)效果如下所示:

接下來再處理CircularProgressIndicator的部分:
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: 1,
child: TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: downloadProgress),
duration: const Duration(milliseconds: 200),
builder: (context, progress, child) {
return CircularProgressIndicator(
backgroundColor: isDownloading
? CupertinoColors.lightBackgroundGray
: Colors.white.withOpacity(0),
valueColor: AlwaysStoppedAnimation(isFetching
? CupertinoColors.lightBackgroundGray
: CupertinoColors.activeBlue),
strokeWidth: 2,
value: isFetching ? null : progress,
);
},
),
);
}這里使用的是TweenAnimationBuilder來實(shí)現(xiàn)CircularProgressIndicator根據(jù)不同progress的動(dòng)畫效果。
因?yàn)樵谙螺d過程中,還有停止的功能,所以我們?cè)贑ircularProgressIndicator上再放一個(gè)stop icon,最后將這個(gè)stack封裝在AnimatedOpacity中,實(shí)現(xiàn)整體的一個(gè)淡入淡出功能:
Positioned.fill(
child: AnimatedOpacity(
duration: transitionDuration,
opacity: _isDownloading || _isFetching ? 1.0 : 0.0,
curve: Curves.ease,
child: Stack(
alignment: Alignment.center,
children: [
ProgressIndicatorWidget(
downloadProgress: downloadProgress,
isDownloading: _isDownloading,
isFetching: _isFetching,
),
if (_isDownloading)
const Icon(
Icons.stop,
size: 14,
color: CupertinoColors.activeBlue,
),
],
),
),總結(jié)
這樣,我們一個(gè)動(dòng)畫的下載按鈕就制作完成了,效果如下:

本文的例子:https://github.com/ddean2009/learn-flutter.git
到此這篇關(guān)于Flutter實(shí)現(xiàn)簡(jiǎn)單的下載按鈕動(dòng)畫的文章就介紹到這了,更多相關(guān)Flutter下載按鈕動(dòng)畫內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android應(yīng)用的Material設(shè)計(jì)中圖片的相關(guān)處理指南
這篇文章主要介紹了Android應(yīng)用的Material設(shè)計(jì)中圖片的相關(guān)處理指南,除了介紹新的方法外文中還給出了一些設(shè)計(jì)標(biāo)準(zhǔn)樣例僅供參考,需要的朋友可以參考下2016-04-04
Android應(yīng)用中使用SharedPreferences類存儲(chǔ)數(shù)據(jù)的方法
這篇文章主要介紹了Android應(yīng)用中使用SharedPreferences類存儲(chǔ)數(shù)據(jù)的方法,SharedPreferences使用鍵值對(duì)應(yīng)的方式進(jìn)行存儲(chǔ),使用于少量的數(shù)據(jù)保存,需要的朋友可以參考下2016-04-04
android通過bitmap生成新圖片關(guān)鍵性代碼
android通過bitmap生成新圖片具體實(shí)現(xiàn)如下,感興趣的朋友可以參考下哈,希望對(duì)你有所幫助2013-06-06
Android實(shí)現(xiàn)從相冊(cè)截圖的功能
這篇文章主要介紹了Android實(shí)現(xiàn)從相冊(cè)截圖的功能,簡(jiǎn)單介紹了Android實(shí)現(xiàn)從相冊(cè)截圖功能的步驟,供大家參考,感興趣的小伙伴們可以參考一下2016-01-01
Android kotlin語言實(shí)現(xiàn)刪除文件的解決方案
這篇文章主要介紹了Android kotlin語言實(shí)現(xiàn)刪除文件的解決方案,在項(xiàng)目開發(fā)過程中,尤其是需要跨平臺(tái)協(xié)作的項(xiàng)目,那么刪除用戶指定的文件的這種操作就顯得尤為重要了,需要的朋友可以參考下2025-02-02
android 版本檢測(cè) Android程序的版本檢測(cè)與更新實(shí)現(xiàn)介紹
做個(gè)網(wǎng)站的安卓客戶端,用戶安裝到自己手機(jī)上,如果我出了新版本怎么辦呢?要有版本更新功能,感興趣的朋友可以了解下2013-01-01

