Flutter實現(xiàn)簡單的下載按鈕動畫
簡介
我們在app的開發(fā)過程中經(jīng)常會用到一些表示進(jìn)度類的動畫效果,比如一個下載按鈕,我們希望按鈕能夠動態(tài)顯示下載的進(jìn)度,這樣可以給用戶一些直觀的印象,那么在flutter中一個下載按鈕的動畫應(yīng)該如何制作呢?
一起來看看吧。
定義下載的狀態(tài)
我們在真正開發(fā)下載按鈕之前,首先定義幾個下載的狀態(tài),因為不同的下載狀態(tài)導(dǎo)致的按鈕展示樣子也是不一樣的,我們用下面的一個枚舉類來設(shè)置按鈕的下載狀態(tài):
enum DownloadStatus {
notDownloaded,
fetchingDownload,
downloading,
downloaded,
}基本上有4個狀態(tài),分別是沒有下載,準(zhǔn)備下載但是還沒有獲取到下載的資源鏈接,獲取到下載資源正在下載中,最后是下載完畢。
定義DownloadButton的屬性
這里我們需要自定義一個DownloadButton組件,這個組件肯定是一個StatelessWidget,所有的狀態(tài)信息都是由外部傳入的。
我們需要根據(jù)下載狀態(tài)來指定DownloadButton的樣式,所以需要一個status屬性。下載過程中還有一個下載的進(jìn)度條,所以我們需要一個downloadProgress屬性。
另外在點擊下載按鈕的時候會觸發(fā)onDownload事件,下載過程中可以觸發(fā)onCancel事件,下載完畢之后可以出發(fā)onOpen事件。
最后因為是一個動畫組件,所以還需要一個動畫的持續(xù)時間屬性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的屬性可以動態(tài)變化
上面提到了DownloadButton是一個StatelessWidget,所有的屬性都是由外部傳入的,但是對于一個動畫的DownloadButton來說,status,downloadProgress這些信息都是會動態(tài)變化的,那么怎么才能讓變化的屬性傳到DownloadButton中進(jìn)行組件的重繪呢?
因為涉及到復(fù)雜的狀態(tài)變化,所以簡單的AnimatedWidget已經(jīng)滿足不了我們的需求了,這里就需要用到flutter中的AnimatedBuilder組件了。
AnimatedBuilder是AnimatedWidget的子類,它有兩個必須的參數(shù),分別是animation和builder。
其中animation是一個Listenable對象,它可以是Animation,ChangeNotifier或者等。
AnimatedBuilder會通過監(jiān)聽animation的變動情況,來重新構(gòu)建builder中的組件。buidler方法可以從animation中獲取對應(yīng)的變動屬性。
這樣我們創(chuàng)建一個Listenable的DownloadController對象,然后把DownloadButton用AnimatedBuilder封裝起來,就可以實時監(jiān)測到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是一個Listenable對象,這里我們讓他實現(xiàn)ChangeNotifier接口,并且定義了兩個獲取下載狀態(tài)和下載進(jìn)度的方法,同時也定義了三個點擊觸發(fā)事件:
abstract class DownloadController implements ChangeNotifier {
DownloadStatus get downloadStatus;
double get progress;
void startDownload();
void stopDownload();
void openDownload();
}接下來我們來實現(xiàn)這個抽象方法:
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這兩個方法是跟下載狀態(tài)和下載進(jìn)度相關(guān)的,先看下stopDownload:
void stopDownload() {
if (_isDownloading) {
_isDownloading = false;
_downloadStatus = DownloadStatus.notDownloaded;
_progress = 0.0;
notifyListeners();
}
}可以看到這個方法最后需要調(diào)用notifyListeners來通知AnimatedBuilder來進(jìn)行組件的重繪。
startDownload方法會復(fù)雜一點,我們需要模擬下載狀態(tài)的變化和進(jìn)度的變化,如下所示:
Future<void> _doDownload() async {
_isDownloading = true;
_downloadStatus = DownloadStatus.fetchingDownload;
notifyListeners();
// fetch耗時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();
}
}因為下載是一個比較長的過程,所以這里用的是異步方法,在異步方法中進(jìn)行通知。
定義DownloadButton的細(xì)節(jié)
有了可以動態(tài)變化的狀態(tài)和進(jìn)度之后,我們就可以在DownloadButton中構(gòu)建具體的頁面展示了。
在未開始下載之前,我們希望downloadButton是一個長條形的按鈕,按鈕上的文字顯示GET,下載過程中希望是一個類似CircularProgressIndicator的動畫,可以根據(jù)下載進(jìn)度來動態(tài)變化。
同時,在下載過程中,我們希望能夠隱藏之前的長條形按鈕。 下載完畢之后,再次展示長條形按鈕,這時候按鈕上的文字顯示為OPEN。
因為動畫比較復(fù)雜,所以我們將動畫組件分成兩部分,第一部分就是展示和隱藏長條形的按鈕,這里我們使用AnimatedOpacity來實現(xiàn)文字的淡入淡出的效果,并將AnimatedOpacity封裝在AnimatedContainer中,實現(xiàn)decoration的動畫效果:
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,
),
),
),
),
);實現(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來實現(xiàn)CircularProgressIndicator根據(jù)不同progress的動畫效果。
因為在下載過程中,還有停止的功能,所以我們在CircularProgressIndicator上再放一個stop icon,最后將這個stack封裝在AnimatedOpacity中,實現(xiàn)整體的一個淡入淡出功能:
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é)
這樣,我們一個動畫的下載按鈕就制作完成了,效果如下:

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

