利用Flutter制作一個(gè)摸魚桌面版App
Win10商店上架了一款名為《摸魚》的App,在下載打開之后,這個(gè)App會(huì)讓你的電腦進(jìn)入一個(gè)假更新的畫面,讓別人以為你的電腦正在升級(jí),這時(shí)候你就可以休息一下,優(yōu)雅地喝一杯咖啡。?
頓時(shí)這個(gè)念頭劃過(guò)了我的腦海:好東西,但是我用的是 MacBook,不能用這個(gè)應(yīng)用。但是貌似我可以自己寫一個(gè)?
準(zhǔn)備工作
年輕最需要的就是行動(dòng)力,想到就干,盡管我此刻正在理順 DevFest 的講稿,但絲毫不妨礙我用 10 分鐘寫一個(gè) App。于是我打出了一套組合拳:
flutter config --enable-macos-desktop flutter create --platforms=macos touch_fish_on_macos
一個(gè)支持 macOS 的 Flutter 項(xiàng)目就創(chuàng)建好了。(此時(shí)大約過(guò)去了 1 分鐘)
開始敲代碼
找到資源
我們首先需要一張高清無(wú)碼的 圖片,這里你可以在網(wǎng)上進(jìn)行搜尋,有一點(diǎn)需要注意的是,使用 LOGO 要注意使用場(chǎng)景帶來(lái)的版權(quán)問題。找到圖片后,丟到 assets/apple-logo.png,并在 pubspec.yaml 中加上資源引用:
flutter: use-material-design: true + assets: + - assets/apple-logo.png
思考布局
我們來(lái)觀察一下 macOS 的啟動(dòng)畫面,有幾個(gè)要點(diǎn):
LOGO 在屏幕中間,固定大小約為 100dp;
LOGO 與進(jìn)度條間隔約 100 dp;
進(jìn)度條高度約 5dp,寬度約 200dp,圓角幾乎完全覆蓋高度,值部分為白色,背景部分為填充色+淺灰色邊框。
(別問我為什么這些東西能觀察出來(lái),問就是天天教 UI 改 UI。)
確認(rèn)了大概的布局模式,接下來(lái)我們開始搭布局。(此時(shí)大約過(guò)去了 2 分鐘)
實(shí)現(xiàn)布局
首先將 LOGO 居中、著色、設(shè)定寬度為 100,上下間隔 100:
return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Spacer(), Padding( padding: const EdgeInsets.symmetric(vertical: 100), child: Image.asset( 'assets/apple-logo.png', color: CupertinoColors.white, // 使用 Cupertino 系列的白色著色 width: 100, ), ), const Spacer(), ], ), );
然后在下方放一個(gè)相對(duì)靠上的進(jìn)度條:
return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Spacer(), Padding( padding: const EdgeInsets.symmetric(vertical: 100), child: Image.asset( 'assets/apple-logo.png', color: CupertinoColors.white, // 使用 Cupertino 系列的白色 width: 100, ), ), Expanded( child: Container( width: 200, alignment: Alignment.topCenter, // 相對(duì)靠上中部對(duì)齊 child: DecoratedBox( border: Border.all(color: CupertinoColors.systemGrey), // 設(shè)置邊框 borderRadius: BorderRadius.circular(10), // 這里的值比高大就行 ), child: ClipRRect( borderRadius: BorderRadius.circular(10), // 需要進(jìn)行圓角裁剪 child: LinearProgressIndicator( value: 0.3, // 當(dāng)前的進(jìn)度值 backgroundColor: CupertinoColors.lightBackgroundGray.withOpacity(.3), color: CupertinoColors.white, minHeight: 5, // 設(shè)置進(jìn)度條的高度 ), ), ), ), ], ), );
到這里你可以直接 run,一個(gè)靜態(tài)的界面已經(jīng)做好了。(此時(shí)大約過(guò)去了 4 分鐘)
打開 App,你已經(jīng)可以放在一旁掛機(jī)了,老板走到你的身邊,可能會(huì)跟你閑聊更新的內(nèi)容。但是,更新界面不會(huì)動(dòng),能稱之為更新界面? 當(dāng)老板一而再再而三地從你身邊經(jīng)過(guò),發(fā)現(xiàn)還是這個(gè)進(jìn)度的時(shí)候,也許就已經(jīng)把你的工資劃掉了,或者第二天你因?yàn)檫M(jìn)辦公室在椅子上坐下而被辭退。
那么下一步我們就要思考如何讓它動(dòng)起來(lái)。
思考動(dòng)畫
來(lái)看看啟動(dòng)動(dòng)畫大概是怎么樣的:
開始是沒有進(jìn)度條的;
進(jìn)度條會(huì)逐級(jí)移動(dòng)、速度不一定相等。
基于以上兩個(gè)條件,我設(shè)計(jì)了一種動(dòng)畫處理方式:
- 構(gòu)造分段的時(shí)長(zhǎng) (Duration),可以自由組合由多個(gè)時(shí)長(zhǎng);
- 動(dòng)畫通過(guò)時(shí)長(zhǎng)的數(shù)量決定每個(gè)時(shí)長(zhǎng)最終的進(jìn)度;
- 每段時(shí)長(zhǎng)控制起始值到結(jié)束值的間隔。
只有三個(gè)條件,簡(jiǎn)單到起飛,開動(dòng)?。ù藭r(shí)大約過(guò)去了 5 分鐘)
實(shí)現(xiàn)動(dòng)畫
開局一個(gè) AnimationController:
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin { /// 巧用 late 初始化,節(jié)省代碼量 late final AnimationController _controller = AnimationController(vsync: this); /// 啟動(dòng)后等待的時(shí)長(zhǎng) Duration get _waitingDuration => const Duration(seconds: 5); /// 分段的動(dòng)畫時(shí)長(zhǎng) List<Duration> get _periodDurations { return <Duration>[ const Duration(seconds: 5), const Duration(seconds: 10), const Duration(seconds: 4), ]; } /// 當(dāng)前進(jìn)行到哪一個(gè)分段 final ValueNotifier<int> _currentPeriod = ValueNotifier<int>(1);
接下來(lái)實(shí)現(xiàn)動(dòng)畫方法,采用了遞歸調(diào)用的方式,減少調(diào)用鏈的控制:
@override void initState() { super.initState(); // 等待對(duì)應(yīng)秒數(shù)后,開始進(jìn)度條動(dòng)畫 Future.delayed(_waitingDuration).then((_) => _callAnimation()); } Future<void> _callAnimation() async { // 取當(dāng)前分段 final Duration _currentDuration = _periodDurations[currentPeriod]; // 準(zhǔn)備下一分段 currentPeriod++; // 如果到了最后一個(gè)分段,取空 final Duration? _nextDuration = currentPeriod < _periodDurations.length ? _periodDurations.last : null; // 計(jì)算當(dāng)前分段動(dòng)畫的結(jié)束值 final double target = currentPeriod / _periodDurations.length; // 執(zhí)行動(dòng)畫 await _controller.animateTo(target, duration: _currentDuration); // 如果下一分段為空,即執(zhí)行到了最后一個(gè)分段,重設(shè)當(dāng)前分段,動(dòng)畫結(jié)束 if (_nextDuration == null) { currentPeriod = 0; return; } // 否則調(diào)用下一分段的動(dòng)畫 await _callAnimation(); }
以上短短幾行代碼,就完美的實(shí)現(xiàn)了進(jìn)度條的動(dòng)畫操作。(此時(shí)大約過(guò)去了 8 分鐘)
最后一步,將動(dòng)畫、分段二者與進(jìn)度條綁定,在沒進(jìn)入分段前不展示進(jìn)度條,在動(dòng)畫開始后展示對(duì)應(yīng)的進(jìn)度:
ValueListenableBuilder<int>( valueListenable: _currentPeriod, builder: (_, int period, __) { // 分段為0時(shí),不展示 if (period == 0) { return const SizedBox.shrink(); } return DecoratedBox( decoration: BoxDecoration( border: Border.all(color: CupertinoColors.systemGrey), borderRadius: BorderRadius.circular(10), ), child: ClipRRect( borderRadius: BorderRadius.circular(10), child: AnimatedBuilder( // 使用 AnimatedBuilder,在動(dòng)畫進(jìn)行時(shí)觸發(fā)更新 animation: _controller, builder: (_, __) => LinearProgressIndicator( value: _controller.value, // 將 controller 的值綁定給進(jìn)度 backgroundColor: CupertinoColors.lightBackgroundGray.withOpacity(.3), color: CupertinoColors.white, minHeight: 5, ), ), ), ); }, )
大功告成,總共用時(shí) 10 分鐘,讓我們跑起來(lái)看看效果。(下圖 22.1 M)
打包發(fā)布
發(fā)布正式版的 macOS 應(yīng)用較為復(fù)雜,但我們可以打包給自己使用,只需要一行命令即可:flutter build macos。
成功后,產(chǎn)物將會(huì)輸出在 build/macos/Build/Products/Release/touch_fish_on_macos.app,雙擊即可使用
結(jié)語(yǔ)
可能大多數(shù)人都沒有想到,編寫一個(gè) Flutter 應(yīng)用,跑在 macOS 上,能有這么簡(jiǎn)單。當(dāng)然,看似短暫的 10 分鐘并沒有包括安裝環(huán)境、搜索素材、提交到 git 的時(shí)間,但在這個(gè)時(shí)間范圍內(nèi),完成相關(guān)的事情也是綽綽有余。
到此這篇關(guān)于利用Flutter制作一個(gè)摸魚桌面版App的文章就介紹到這了,更多相關(guān)Flutter摸魚App內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android在線更新SDK的方法(使用國(guó)內(nèi)鏡像)
這篇文章主要介紹了Android在線更新SDK的方法,分別介紹了修改hosts文件使用谷歌官方鏡像更新及使用國(guó)內(nèi)鏡像更新SDK的方法,非常簡(jiǎn)單實(shí)用,需要的朋友可以參考下2015-12-12android根據(jù)分辨率自動(dòng)調(diào)整字體大小的實(shí)例代碼
android根據(jù)分辨率自動(dòng)調(diào)整字體大小的實(shí)例代碼,需要的朋友可以參考一下2013-06-06Android Studio升級(jí)3.6 Build窗口出現(xiàn)中文亂碼問題解決方法
這篇文章主要介紹了Android Studio升級(jí)3.6 Build窗口出現(xiàn)中文亂碼問題解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03Android內(nèi)存優(yōu)化操作方法梳理總結(jié)
這篇文章主要介紹了Android 內(nèi)存優(yōu)化知識(shí)點(diǎn)梳理總結(jié),Android 操作系統(tǒng)給每個(gè)進(jìn)程都會(huì)分配指定額度的內(nèi)存空間,App 使用內(nèi)存來(lái)進(jìn)行快速的文件訪問交互,長(zhǎng)時(shí)間如此便需要優(yōu)化策略,文章分享優(yōu)化知識(shí)點(diǎn)總結(jié),需要的朋友可以參考一下2022-11-11android手機(jī)獲取gps和基站的經(jīng)緯度地址實(shí)現(xiàn)代碼
android手機(jī)如何獲取gps和基站的經(jīng)緯度地址,疑問,于是網(wǎng)上搜集整理一些,拿出來(lái)和大家分享下,希望可以幫助你們2012-12-12Kotlin 高階函數(shù)與Lambda表達(dá)式示例詳解
這篇文章主要為大家介紹了Kotlin 高階函數(shù)與Lambda表達(dá)式示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12Android那兩個(gè)你碰不到但是很重要的類之ViewRootImpl
這兩個(gè)類就是ActivityThread和ViewRootImpl,之所以說(shuō)碰不到是因?yàn)槲覀儫o(wú)法通過(guò)正常的方式引用這兩個(gè)類或者其類的對(duì)象,本文就嘗試從幾個(gè)我們經(jīng)常接觸的方面先談?wù)刅iewRootImpl,感興趣的可以參考閱讀下2023-05-05Android Notification使用方法總結(jié)
這篇文章主要介紹了Android Notification使用方法總結(jié)的相關(guān)資料,這里提供了四種使用方法,需要的朋友可以參考下2017-09-09Android Studio實(shí)現(xiàn)簡(jiǎn)單計(jì)算器APP
這篇文章主要為大家詳細(xì)介紹了Android Studio實(shí)現(xiàn)簡(jiǎn)單計(jì)算器APP,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-03-03