Flutter開發(fā)實(shí)戰(zhàn)詳解_第1頁
Flutter開發(fā)實(shí)戰(zhàn)詳解_第2頁
Flutter開發(fā)實(shí)戰(zhàn)詳解_第3頁
Flutter開發(fā)實(shí)戰(zhàn)詳解_第4頁
Flutter開發(fā)實(shí)戰(zhàn)詳解_第5頁
已閱讀5頁,還剩429頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

Flutter開發(fā)實(shí)戰(zhàn)詳解目錄TOC\h\h第1章跨平臺開發(fā)的發(fā)展\h1.1跨平臺開發(fā)的演進(jìn)\h1.2Cordova\h1.3ReactNative\h1.4Flutter\h1.5綜合對比\h1.5.1開發(fā)環(huán)境\h1.5.2實(shí)現(xiàn)原理\h1.5.3開發(fā)語言\h1.5.4界面開發(fā)\h1.5.5插件開發(fā)\h1.5.6編譯和產(chǎn)物\h1.5.7熱更新和支持平臺\h第2章走進(jìn)Flutter的世界\h2.1開發(fā)環(huán)境\h2.1.1前置準(zhǔn)備\h2.1.2安裝Flutter開發(fā)環(huán)境\h2.1.3配置編輯器\h2.2Dart語言\h2.2.1基礎(chǔ)語法\h2.2.2setter/getter\h2.2.3final/const\h2.2.4import\h2.2.5基礎(chǔ)數(shù)據(jù)類型\h2.2.6邏輯語句與操作符\h2.2.7var與dynamic\h2.2.8函數(shù)方法\h2.2.9類、接口和繼承\(zhòng)h2.2.10mixins\h2.2.11構(gòu)造方法\h2.2.12異常處理\h2.2.13Isolate\h2.2.14Zone\h2.2.15異步執(zhí)行\(zhòng)h2.2.16拓展方法\h2.3Flutter控件介紹\h2.3.1無狀態(tài)控件(StatelessWidget)\h2.3.2有狀態(tài)控件(StatefulWidget)\h2.3.3Flutter常用控件\h2.3.4Flutter頁面\h2.3.5路由跳轉(zhuǎn)\h2.4Flutter常見開發(fā)技巧\h2.4.1常見的問題處理\h2.4.2WidgetKey\h2.4.3獲取狀態(tài)欄高度和字體縮放\h2.4.4狀態(tài)欄顏色和圖標(biāo)顏色\h2.4.5控件圓角裁剪\h2.4.6懶加載\h2.4.7ChangeNotifier\h第3章Flutter的靈魂:Widget\h3.1配置文件Widget\h3.2大腦倉庫Element\h3.2.1Element的大腦中樞\(zhòng)h3.2.2Element的倉庫存儲\h3.2.3Element的分類\h3.2.4Element橋接連通\h3.3繪制實(shí)例RenderObject\h3.3.1RenderObject的子類\h3.3.2RenderPadding\h3.3.3RenderObject的繪制\h3.4渲染圖層Layer\h第4章FlutterWidget分類對比\h4.1Widget的狀態(tài)分類\h4.2Element分類\h4.3RenderObject分類\h4.3.1RenderBox\h4.3.2RenderSliver\h4.3.3ViewPort\h4.4單元素與多元素分類\h4.4.1自定義MultiChildRenderObjectWidget\h4.4.2CustomMultiChildLayout\h4.5InheritedWidget共享狀態(tài)\h4.6ErrorWidget異常處理\h第5章FlutterFramework\h5.1線程模型\h5.2動(dòng)畫實(shí)現(xiàn)\h5.2.1路由動(dòng)畫\h5.2.2Hero動(dòng)畫\h5.2.3Flare動(dòng)畫\h5.3手勢與觸摸\h5.3.1事件流程\h5.3.2hitTest\h5.3.3dispatchEvent\h5.3.4事件競爭\h5.3.5PointerDownEvent\h5.3.6開始競爭\h5.3.7滑動(dòng)事件\h5.4滑動(dòng)Physic\h5.4.1ScrollConfiguration\h5.4.2ScrollPhysics的工作原理\h5.4.3applyPhysicsToUserOffset\h5.4.4applyBoundaryConditions\h5.4.5createBallisticSimulation\h5.4.6Simulation\h5.5圖片加載\h5.6網(wǎng)絡(luò)請求\h第6章Flutter狀態(tài)管理\h6.1Flutter中的狀態(tài)管理\h6.2Stream\h6.2.1Stream的簡單使用\h6.2.2Stream的工作流程\h6.2.3Stream中的同步和異步\h6.2.4Stream中的廣播和非廣播\h6.2.5Stream的變換\h6.2.6StreamBuilder\h6.2.7RxDart\h6.3BLoC\h6.4scoped_model\h6.5flutter_redux\h6.6Provider\h6.7總結(jié)對比\h第7章混合開發(fā)\h7.1Flutter混合開發(fā)\h7.2Flutter工程集成原生插件\h7.2.1MethodChannel\h7.2.2PlatformView\h7.2.3新版AndroidPlugin\h7.3原生工程集成Flutter項(xiàng)目\h7.4FlutterBoost\h第8章Flutter開發(fā)實(shí)戰(zhàn)\h8.1Flutter開發(fā)中的入口\h8.1.1程序入口\h8.1.2應(yīng)用入口\h8.1.3頁面入口\h8.2基礎(chǔ)控件\h8.2.1TabWidget\h8.2.2AppBar\h8.2.3下拉刷新和底部加載更多\h8.2.4輸入框\h8.2.5矢量圖庫\h8.2.6自定義繪制\h8.2.7Align和Positioned\h8.2.8控件小技巧\h8.3路由跳轉(zhuǎn)\h8.4狀態(tài)管理\h8.4.1BLoC另類的實(shí)現(xiàn)\h8.4.2redux的攔截處理\h8.4.3scoped_model的局部共享數(shù)據(jù)\h8.5網(wǎng)絡(luò)請求\h8.5.1序列化\h8.5.2built_value序列化\h8.6多語言與主題\h8.6.1主題\h8.6.2多語言\h8.7多環(huán)境配置\h8.8完整項(xiàng)目實(shí)戰(zhàn)\h第9章調(diào)試打包\h9.1JIT與AOT\h9.2Android打包\h9.3iOS打包\h9.4Web和PC打包\h9.5性能調(diào)試\h9.6開發(fā)工具\(yùn)h9.6.1DartPad\h9.6.2Supernova\h9.6.3HotUI\h9.6.4LayoutExplorer第1章跨平臺開發(fā)的發(fā)展在走進(jìn)Flutter世界之前,先介紹跨平臺開發(fā)的發(fā)展歷程。了解整個(gè)跨平臺框架的發(fā)展歷史和背景,有利于理解Flutter誕生的前因后果。通過對比,讓讀者明白Flutter是什么樣的框架,對比其他跨平臺框架有什么不同之處,它們之間各有什么優(yōu)劣,而Flutter又為什么是移動(dòng)端跨平臺開發(fā)的新里程碑,它憑借什么進(jìn)入GitHub排行榜前十?本章不影響后續(xù)章節(jié)的學(xué)習(xí),如果已經(jīng)了解跨平臺開發(fā)的發(fā)展歷程,可以直接從第2章開始學(xué)起。但如果對于為什么選擇Flutter,以及Flutter的優(yōu)勢在哪里感興趣,建議從本章開始閱讀。1.1跨平臺開發(fā)的演進(jìn)跨平臺開發(fā)一直是老生常談的話題,隨著用戶終端種類的不斷涌現(xiàn),跨平臺開發(fā)已然成為移動(dòng)領(lǐng)域的熱點(diǎn),例如Cordova、Ionic、ReactNative、Weex、Flutter等跨平臺框架等,百花齊放。移動(dòng)端跨平臺開發(fā)技術(shù)的發(fā)展,也代表著開發(fā)者對于性能、復(fù)用、高效等方面的不斷追求。在整個(gè)歷史進(jìn)程中,移動(dòng)端的跨平臺開發(fā)主要經(jīng)歷了三個(gè)階段,這些階段的代表框架主要有Cordova、ReactNative、Flutter,如圖1-1所示。圖1-1移動(dòng)端的跨平臺歷史進(jìn)程代表為什么要學(xué)習(xí)跨平臺?有開發(fā)者表示直接學(xué)Java/Kotlin、Object-C/Swift、JavaScript/CSS去寫各平臺的原生代碼不好嗎?答案肯定是好的,這樣的性能肯定是最有保證的。但拋開學(xué)習(xí)成本不說,跨平臺的主要優(yōu)勢在于代碼邏輯的復(fù)用,降低各平臺同一邏輯因人而異的開發(fā)成本。同時(shí),大前端的發(fā)展趨勢,讓更多的開發(fā)者不得不接觸跨平臺。一般情況下,各平臺開發(fā)者容易局限在自身的技術(shù)領(lǐng)域,而作為應(yīng)用開發(fā)者,跨平臺也是接觸另一平臺或技術(shù)領(lǐng)域的過渡機(jī)會(huì)。1.2CordovaCordova作為跨平臺領(lǐng)域應(yīng)用最廣泛的框架,它被早期的前端人員所熟知,其主要原理是:將Web代碼打包到本地,利用平臺的WebView進(jìn)行加載,通過內(nèi)部約定好的JavaScript(以下簡稱JS)協(xié)議進(jìn)行通信,從而調(diào)用具備平臺原生能力的插件。Cordova框架圖如圖1-2所示。圖1-2Cordova框架圖Cordova讓前端開發(fā)人員可以快速地構(gòu)建移動(dòng)應(yīng)用,對于原有的Web代碼只需做少量適配,就可以快速獲取平臺的應(yīng)用入口,而對早期Web所欠缺的如攝像機(jī)、本地緩存、文件讀寫等能力,也能進(jìn)行快速的支持。另外,在早期的移動(dòng)開發(fā)市場,除了Android和iOS,還有WindowsPhone、黑莓等其他平臺,Cordova簡單又實(shí)用的理念,使得它成為早期熱門的跨平臺框架,至今仍在更新的Ionic框架,也是在其基礎(chǔ)上進(jìn)行封裝后發(fā)展的。但是如上所述可以看到,此時(shí)的跨平臺框架所能提供的能力較弱,更多是實(shí)現(xiàn)打包腳手架的功能,本質(zhì)上還是Web代碼。1.3ReactNativeCordova雖然方便實(shí)用,但是受制于WebView的性能瓶頸,無法滿足某些需要高性能交互的場景需求,而這時(shí)候由Facebook開源的ReactNative框架打開了新思路。ReactNative讓JavaScript代碼運(yùn)行在框架內(nèi)置的JS引擎(JavaScriptCore)上,利用JS引擎實(shí)現(xiàn)了跨平臺能力,之后利用JSBridge將JavaScript的標(biāo)簽控件對應(yīng)解析為平臺原生控件進(jìn)行渲染,從而實(shí)現(xiàn)性能的優(yōu)化與提升。ReactNative框架圖如圖1-3所示。圖1-3ReactNative框架圖在ReactNative開源之前,由于React框架的盛行,給ReactNative打下了良好的用戶基礎(chǔ),“Learnonce,writeanywhere”代表著Facebook對ReactNative的定義:學(xué)習(xí)React,同時(shí)掌握Web與App兩種開發(fā)技能。ReactNative也開始成為React開發(fā)人員將自身能力拓展到應(yīng)用開發(fā)的選擇之一。這個(gè)設(shè)計(jì)思路也在后來阿里開源的Weex框架中有所體現(xiàn),不同之處在于Weex利用了V8引擎實(shí)現(xiàn)跨平臺,使用了Vue的設(shè)計(jì)理念。ReactNative和React的不同之處在于:ReactNative中所有的標(biāo)簽都不是真實(shí)控件,JS代碼中所寫控件的作用類似于Map中的key值,JS端通過這個(gè)key值組合出來的DOM,最后會(huì)在Native端被解析成對應(yīng)的Native控件進(jìn)行渲染。例如<view>標(biāo)簽這個(gè)key對應(yīng)Android平臺的ViewGroup控件,也就是說ReactNative中的控件依賴于原生平臺實(shí)現(xiàn)。但是ReactNative的JS引擎和JSBridge同樣存在性能限制,F(xiàn)acebook也在著力優(yōu)化這一問題。而JS控件轉(zhuǎn)化為平臺控件進(jìn)行渲染的設(shè)計(jì),也導(dǎo)致了ReactNative框架和平臺控件耦合過多。在版本兼容和系統(tǒng)升級等歷史因素的困擾下,框架的維護(hù)越發(fā)困難,不少ReactNative的開發(fā)者會(huì)選擇一個(gè)框架版本后不再升級。1.4Flutter2017年谷歌開源了Flutter,如果說ReactNative是為開發(fā)者做了平臺兼容統(tǒng)一,那么Flutter更像是為開發(fā)者屏蔽了平臺的概念。Flutter作為跨平臺的UI框架,只需平臺提供一個(gè)Surface和一個(gè)Canvas就可以完成界面的工作。如圖1-4所示,F(xiàn)lutter中絕大部分的Widget都與平臺無關(guān),開發(fā)者基于Framework開發(fā)App,而Framework運(yùn)行在FlutterEngine之上,由Engine進(jìn)行適配和提供跨平臺支持。這個(gè)跨平臺的支持過程,其實(shí)就是將FlutterFramework中的Widget進(jìn)行“數(shù)據(jù)化”處理,然后通過Engine上的Skia直接繪制到屏幕上。圖1-4Flutter框架圖Flutter可以實(shí)現(xiàn)跨平臺且內(nèi)部控件與平臺無關(guān)的原因,就在于Skia圖形引擎。Skia是谷歌的圖形引擎,在Chrome瀏覽器、Android等系統(tǒng)內(nèi)均使用Skia作為繪圖處理引擎。Android平臺本身就內(nèi)置了Skia,將NativeCode代碼渲染成界面,所以相比ReactNative,F(xiàn)lutter在繪制性能上少了中間代理,可以更接近原生的交互體驗(yàn)。如圖1-5所示,因?yàn)镕lutter屬于跨平臺UI框架,在需要用到平臺硬件或者API的時(shí)候,會(huì)通過PlatformChannels進(jìn)行通信交互,補(bǔ)全Flutter框架非UI相關(guān)的能力。圖1-5Flutter跨平臺實(shí)現(xiàn)架構(gòu)Flutter本身還有一大特色,那就是Dart語言。事實(shí)上,Dart早在2011年就被曝光,豪門出生的它“心懷壯志”,最后卻上陣就“冷場”,連帶著DartVM內(nèi)置到Chrome的計(jì)劃都被放棄,直到與Flutter相遇才重振雄風(fēng),背靠Flutter又再次回歸到Web的懷抱。Flutter選擇Dart語言也讓部分前端開發(fā)者頗有微詞。1.5綜合對比之所以會(huì)有“綜合對比”這一節(jié),完全是為了幫助讀者更好地選擇跨平臺框架,因?yàn)闆]有十全十美的框架,只有最合適的業(yè)務(wù)場景的選擇。通過對比,可以讓讀者更深入地理解Flutter的實(shí)現(xiàn)原理,也可以更直觀地體現(xiàn)Flutter作為跨平臺框架的優(yōu)劣。因?yàn)镃ordova更多是提供打包腳手架的功能,所以這里的對比排除了Cordova,直接對比ReactNative和Flutter,如表1-1所示。表1-1ReactNative和Flutter的對比從表1-1中可以很直觀地看到,GSY項(xiàng)目是開源的GSYGithubApp項(xiàng)目,它們在ReactNative與Flutter都實(shí)現(xiàn)了類似的功能,所以有一定的對比價(jià)值。Flutter與ReactNative在各自的應(yīng)用場景下互有優(yōu)劣,但是有一點(diǎn)Flutter一定比ReactNative優(yōu)秀,那就是Flutter的版本號早就超越了ReactNative。1.5.1開發(fā)環(huán)境從開發(fā)角度考慮,跨平臺開發(fā)首選macOS,因?yàn)闊o論是ReactNative還是Flutter,都需要Android和iOS的開發(fā)環(huán)境,也就是JDK、AndroidSDK、Xcode等環(huán)境配置。而不同點(diǎn)在于:●ReactNative需要npm、node、react-native-cli等配置;●Flutter需要FlutterSDK和AndroidStudio/VSCode等IDE支持。從配置環(huán)境來看,F(xiàn)lutter的環(huán)境配置相對簡單,ReactNative的環(huán)境配置相對復(fù)雜。受到ReactNative的node_module內(nèi)部依賴復(fù)雜度等因素的影響,在首次配置運(yùn)行成功率上,F(xiàn)lutter會(huì)高于ReactNative。1.5.2實(shí)現(xiàn)原理在Android和iOS上,默認(rèn)情況下Flutter和ReactNative都需要一個(gè)原生平臺的Activity/ViewController支持,且在原生層面屬于一個(gè)“單頁面應(yīng)用”。這里的Activity和ViewController可以理解為原生平臺的一個(gè)獨(dú)立原生頁面,常用于組成多頁堆棧,而默認(rèn)情況下在Android和iOS上只會(huì)有一個(gè)Activity或ViewController做原生承載,所以對于原生端都屬于“單頁面應(yīng)用”。而其實(shí)它們之間最大的不同點(diǎn)在于界面構(gòu)建,分別介紹如下。●ReactNative在默認(rèn)情況下會(huì)在原生頁面下加載JSBundle文件,然后JS端的DOM布局會(huì)被解析成原生平臺的控件,如<View>標(biāo)簽對應(yīng)ViewGroup/UIView,<ScrollView>標(biāo)簽對應(yīng)ScrollView/UIScrollView,<Image>標(biāo)簽對應(yīng)ImageView/UIImageView等,最終堆疊出一系列的原生控件進(jìn)行渲染?!穸鳩lutter在默認(rèn)情況下會(huì)在原生頁面中加載一個(gè)FlutterView,用于提供Surface,之后FlutterEnigne會(huì)將FlutterFramework的控件通過Skia繪制到Surface上。從以上對比可以看出:ReactNative“Learnonce,writeanywhere”的思路就是只要你會(huì)React,那么就可以用寫React的方式,再去開發(fā)一個(gè)性能不錯(cuò)的App;而Flutter則是讓你忘掉平臺,專注于FlutterUI就夠了。1.5.3開發(fā)語言ReactNative的開發(fā)語言是JavaScript,而Flutter則是Dart。從語言的角度考慮,JavaScript豐富的第三方庫和ES標(biāo)準(zhǔn)的支持,讓ReactNative在這一項(xiàng)上毫無懸念地勝出了。Dart作為后來者,在語言上和JavaScript也有著不少的相似之處,這里簡單對比它們的異同。如下面代碼所示,它們都支持通過var聲明變量,支持async/await語法糖,支持Promise(JS)/Future(Dart)等異步鏈?zhǔn)教幚?,甚至?/yield標(biāo)注的語法糖都類似。雖然這個(gè)對比不大準(zhǔn)確,但可以看出它們確實(shí)存在類似的語言設(shè)計(jì)邏輯。但是它們之間的差異也很多,而最大的區(qū)別就是:JavaScript是動(dòng)態(tài)語言,而Dart是偽動(dòng)態(tài)語言的強(qiáng)類型語言。例如,在以下代碼中的Dart中可以直接聲明name為String類型,同時(shí)otherName雖然是通過var語法糖聲明的,但在賦值時(shí)會(huì)自推導(dǎo)出類型也為String,而dynamic聲明的變量才是真的動(dòng)態(tài)變量,在運(yùn)行時(shí)才檢測類型。動(dòng)態(tài)語言和非動(dòng)態(tài)語言都有各自的優(yōu)缺點(diǎn),例如JavaScript作為動(dòng)態(tài)語言,開發(fā)的便捷度明顯會(huì)高于Dart,而Dart在類型安全和重構(gòu)代碼等方面又會(huì)比JavaScript更穩(wěn)健。1.5.4界面開發(fā)ReactNative和Flutter都是響應(yīng)式開發(fā)設(shè)計(jì),但是它們的布局編排又不大一樣。ReactNative在界面開發(fā)上延續(xù)了React的開發(fā)風(fēng)格,如可拓展支持scss/sass,樣式代碼分離,在0.59版本開始支持ReactHook函數(shù)式編程等。而與React的不同之處就是更換了控件標(biāo)簽名,并且樣式和屬性支持因?yàn)槠脚_兼容做了刪減。如下面代碼所示,是一個(gè)普通ReactNative組件的常見實(shí)現(xiàn)方式:繼承Component類,通過props傳遞參數(shù),然后在render方法中返回需要的布局,布局中每個(gè)控件通過style設(shè)置樣式等。在界面開發(fā)方面,對于前端開發(fā)者基本沒有太高的學(xué)習(xí)成本。Flutter最大的特點(diǎn)在于:Flutter是一套與平臺無關(guān)的UI框架,在Flutter中萬物皆是Widget。如下面代碼所示,在Flutter開發(fā)中一般通過繼承無狀態(tài)控件(StatelessWidget)或者有狀態(tài)控件(StatefulWidget)來實(shí)現(xiàn)頁面,然后在對應(yīng)的Widgetbuild(BuildContextcontext)方法內(nèi)實(shí)現(xiàn)布局,利用child/children去做界面嵌套,通過控件的構(gòu)造方法傳遞參數(shù),最后對布局里的每個(gè)控件設(shè)置樣式等。對于Flutter控件開發(fā)而言,相比ReactNative,它可能在控件嵌套和樣式代碼不分離上不占優(yōu)勢。至于Flutter為什么會(huì)是這樣的Widget設(shè)計(jì)嵌套,這個(gè)問題會(huì)在后續(xù)的章節(jié)中進(jìn)行詳細(xì)的分析。但是Flutter的代碼組織模式在UI實(shí)現(xiàn)速度上的優(yōu)勢是很明顯的。因?yàn)镕lutter控件與平臺無關(guān)的特點(diǎn),可以讓Flutter開發(fā)人員在一個(gè)平臺上開發(fā)的界面,在另一平臺上也能無差別地顯示,與ReactNative依賴于平臺控件渲染相比,有著得天獨(dú)厚的優(yōu)勢。如圖1-6所示,F(xiàn)lutter的界面繪制比ReactNative更加直接,所以性能也會(huì)比ReactNative更好,兼容性更穩(wěn)定。圖1-6Flutter實(shí)現(xiàn)和ReactNative實(shí)現(xiàn)的對比1.5.5插件開發(fā)ReactNative和Flutter都支持插件化開發(fā),不同之處在于ReactNative開發(fā)的是npm插件,而Flutter開發(fā)的是package插件,兩者之間都可以通過插件實(shí)現(xiàn)原生平臺的功能支持。ReactNative使用npm插件的好處在于:可以使用豐富的npm插件生態(tài),同時(shí)降低前端開發(fā)者的學(xué)習(xí)成本。但是使用npm的問題在于后期太容易出狀況,因?yàn)閚pm包容易被依賴的復(fù)雜度和依賴深度迷惑,以至于開發(fā)者可能都不清楚npm究竟裝了什么,編譯了什么,同時(shí)每個(gè)項(xiàng)目都會(huì)安裝到一個(gè)node_module中,也增加了開發(fā)的成本。Flutter的package插件生態(tài)還處于完善發(fā)展匯總階段,默認(rèn)統(tǒng)一管理在pub上安裝,而flutterpackagesget安裝的插件文件一般保存在計(jì)算機(jī)的統(tǒng)一位置,多個(gè)項(xiàng)目都引用同一份插件和同一個(gè)SDK。Windows平臺中一般是在C:\Users\xxxxx\AppData\Roaming\Pub\Cache路徑下,macOS平臺上則是默認(rèn)在~/.pub-cache目錄下。1.5.6編譯和產(chǎn)物ReactNative編譯后的文件主要是JSbundle文本文件,F(xiàn)lutter編譯后的產(chǎn)物是可執(zhí)行的二進(jìn)制文件。從安全性的角度考慮,F(xiàn)lutter編譯后的產(chǎn)物是更加安全和高效的。如表1-1所示,可以看到ReactNative的空項(xiàng)目打包大小,Android是大于iOS的;Flutter的空項(xiàng)目打包大小,iOS是大于Android的。這是因?yàn)镽eactNative運(yùn)行所需的JSCore在iOS中是內(nèi)置的,而Android需要打包到項(xiàng)目里。這個(gè)現(xiàn)象在Flutter中卻是相反的情況,F(xiàn)lutter繪制所需要的Skia在Android平臺中是內(nèi)置的,而iOS平臺中需要打包到項(xiàng)目里。這個(gè)大小規(guī)律在GSY實(shí)際項(xiàng)目上也有著相同的體現(xiàn)。綜上所述,在同等條件下,ReactNative打包的結(jié)果是Android的比iOS的大,而Flutter打包的結(jié)果則正好相反,iOS的比Android的大。1.5.7熱更新和支持平臺在熱更新方面,ReactNative支持code-push等成熟的熱更新方式,而Flutter受制于AppleStore的條款限制不可以使用熱更新。因?yàn)镽eactNative熱更新的只是JavaScript文本,而Flutter需要熱更新的是二進(jìn)制代碼,谷歌作為Flutter開源框架的維護(hù)方,同時(shí)又是GooglePlay的審核者,同樣不會(huì)希望Flutter可以繞過其應(yīng)用商店的限制。當(dāng)然,也有一些第三方的熱更新方案:Flutter在Android上打包成so文件的邏輯,讓其在Android上實(shí)現(xiàn)熱更新成為可能,還有直接使用JavaScript代碼編寫控件模板,之后轉(zhuǎn)化為Flutter控件渲染的方案。相比ReactNative,使用JavaScript代碼更新Flutter應(yīng)用的方法反而顯得本末倒置。在支持平臺上,ReactNative官方僅支持Android和iOS,第三方比如微軟也讓ReactNative支持Windows,還有Taro將ReactNative拓展到小程序等。Flutter官方目前支持Android、iOS和Web,同時(shí)官方早前就宣布支持Fuchsia系統(tǒng),并且未來還會(huì)在正式版上支持各大PC平臺。這其實(shí)得益于FlutterUI的平臺無關(guān)性,如圖1-7所示,Dart語言早先在Web上就有dart2js的支持,所以Flutter在跨平臺上“跨”得足夠徹底。圖1-7Flutter應(yīng)用端實(shí)現(xiàn)與Web端實(shí)現(xiàn)的對比最后總結(jié)一下:ReactNative的優(yōu)勢在于JavaScript和React的豐富生態(tài),并且支持熱更新,劣勢是在界面兼容和性能上不及Flutter;Flutter的優(yōu)勢在于高性能的界面開發(fā)、控件平臺無關(guān)性和多平臺支持,最明顯的劣勢是不支持熱更新,同時(shí)目前生態(tài)不及ReactNative豐富。對于Flutter生態(tài)不豐富的問題,相信很快就會(huì)改善,因?yàn)槎潭虄赡陼r(shí)間內(nèi),就已經(jīng)有近1.8萬個(gè)處于closed狀態(tài)和8000+個(gè)處于open狀態(tài)的issue,這代表了它的熱度,也代表著它需要面對的問題和挑戰(zhàn)。目前國內(nèi)像微信小程序的底層也在灰度測試Flutter引擎,閑魚、騰訊課堂、美團(tuán)等也在積極嘗試跨平臺業(yè)務(wù)。筆者認(rèn)為,跨平臺一般不會(huì)縮減團(tuán)隊(duì)開發(fā)人員,不會(huì)減少工作量,團(tuán)隊(duì)還是要有Android和iOS開發(fā)人員,因?yàn)樵脚_的支持必不可少。Flutter和ReactNative的定位都是跨平臺UI框架,其主要價(jià)值在于讓業(yè)務(wù)邏輯統(tǒng)一,不用同一個(gè)頁面在Android和iOS各實(shí)現(xiàn)一次,從而可以降低團(tuán)隊(duì)內(nèi)部的溝通成本。第2章走進(jìn)Flutter的世界本章開始正式介紹Flutter開發(fā)相關(guān)的基礎(chǔ)知識,主要包含環(huán)境配置、Dart語言、Flutter控件、Flutter常用開發(fā)技巧等,為讀者理解Flutter開發(fā)奠定基礎(chǔ),提供走進(jìn)Flutter世界的直通車。通過對本章的學(xué)習(xí),讀者可以快速地走進(jìn)Flutter的世界,初步掌握Flutter的開發(fā)與應(yīng)用。2.1開發(fā)環(huán)境對于跨平臺開發(fā)者,macOS是最合適的開發(fā)環(huán)境,假如用戶只有Windows或者Linux環(huán)境,雖然也可以用于Flutter開發(fā)學(xué)習(xí),但是涉及iOS平臺的編譯和打包,仍然需要macOS的支持。已具備開發(fā)環(huán)境的讀者可以跳過本節(jié)。2.1.1前置準(zhǔn)備Flutter是運(yùn)行在Android與iOS平臺上的跨平臺框架,所以必不可少地需要Android和iOS的運(yùn)行環(huán)境。對于iOS平臺,需要macOS和Xcode的支持,Xcode一般在macOS的AppStore中就可以直接安裝。對于Android開發(fā)環(huán)境,需要JavaSDK(Java8,比如1.8.0_201)和AndroidSDK的支持。JavaSDK可通過Oracle官網(wǎng)下載安裝,AndroidSDK建議安裝AndroidStudio后,通過AndroidStudio的SDKManager進(jìn)行下載。如圖2-1所示,在Setting(Windows)/Preferences(macOS)下找到AndroidSDK,然后選擇需要的版本進(jìn)行安裝。一般在初始化時(shí),AndroidStudio默認(rèn)會(huì)帶有最新版本的SDK,建議將圖中箭頭所指的APILevel為28及以上的版本都下載安裝。另外,圖2-2中箭頭所指處也需要對應(yīng)更新或者下載。注意,如果AndroidStudio是初次啟動(dòng),那么界面會(huì)如圖2-3所示,需要單擊“Configure”按鈕才能打開Setting(Windows)/Preferences(macOS)的入口。當(dāng)安裝完成之后,還需要將AndroidSDK配置到環(huán)境變量:在macOS下,將其配置到.bash_profile文件內(nèi)(macOSCatalina版本開始終端可能使用的是zsh,配置文件為.zshrc);在Windows下,通過“屬性”→“高級系統(tǒng)設(shè)置”→“環(huán)境變量”命令進(jìn)行配置。最后還需要準(zhǔn)備Git環(huán)境。相信對于開發(fā)者而言,Git工具不會(huì)陌生,Git工具直接從Git官方網(wǎng)站下載安裝即可。圖2-1安裝AndroidSDK圖2-2安裝AndroidSDKTools圖2-3初次啟動(dòng)入口2.1.2安裝Flutter開發(fā)環(huán)境安裝Flutter開發(fā)環(huán)境首先需要使用“gitclonehttps:///flutter/flutter.git”命令將Flutter項(xiàng)目克隆到本地,之后將本地Flutter項(xiàng)目的bin目錄地址配置到環(huán)境變量中。Flutter項(xiàng)目主要有master、stable、beta三個(gè)分支,建議開發(fā)者使用stable分支進(jìn)行開發(fā),只需在本地Flutter目錄下使用git命令“gitcheckoutstable”,即可切換到stable分支。在執(zhí)行初始化Flutter環(huán)境之前,可以通過配置鏡像來加快速度,是否需要使用鏡像,視讀者的網(wǎng)絡(luò)環(huán)境而定。如下面的配置信息所示,其中的FLUTTER_STORAGE_BASE_URL表示的是FlutterSDK的初始化地址,而PUB_HOSTED_URL表示的是pub包下載的地址。當(dāng)不設(shè)置鏡像時(shí),官方默認(rèn)地址配置如下所示:配置代理之后,可以運(yùn)行“flutterdoctor”命令對環(huán)境進(jìn)行初始化。Windows用戶需要注意一點(diǎn),F(xiàn)lutter初始化流程會(huì)涉及壓縮包的下載和解壓,這個(gè)過程可能會(huì)被360安全衛(wèi)士等殺毒軟件打斷而導(dǎo)致失敗。最終運(yùn)行成功后可以看到以下輸出。這里還需要注意,F(xiàn)lutter會(huì)通過pub下載插件包,而插件包可以通過本地、pub倉庫、遠(yuǎn)程git連接等方式進(jìn)行下載,下載后的內(nèi)容通常會(huì)保存在pub-cache目錄,如:打開pub-cache目錄,如果是通過默認(rèn)pub下載的,會(huì)保存在hosted//目錄下;如果設(shè)定過代理,比如,會(huì)保存在hosted//目錄下。與hosted目錄同級的還有g(shù)it目錄,通過遠(yuǎn)程git連接進(jìn)行下載的內(nèi)容會(huì)存放在這個(gè)目錄下。在默認(rèn)情況下,F(xiàn)lutter可以使用pub上的第三方插件。但是有時(shí)候考慮安全或更新速度等因素,會(huì)選擇使用git引用遠(yuǎn)程項(xiàng)目而不使用官方pub上的包。如果在拉包的過程中出現(xiàn)異常,下次再同步時(shí),在pub-cache路徑內(nèi)的git目錄下會(huì)檢測到已經(jīng)存在目錄。并且可能是因?yàn)榭漳夸浀葐栴},會(huì)導(dǎo)致執(zhí)行“flutterpackagesget”命令下載依賴的時(shí)候無法正常執(zhí)行。此時(shí)需要先清除pub-cache內(nèi)git的異常目錄,之后重新執(zhí)行“flutterpackagesget”命令再同步下載。如果需要更新FlutterSDK,只需執(zhí)行“flutterupgrade”命令等待片刻即可。2.1.3配置編輯器對于編輯器的選擇,筆者比較推薦AndroidStudio,當(dāng)然也可以選擇VisualStudioCode、IntelliJIDEA等工具。但前面既然安裝AndroidSDK時(shí)已經(jīng)下載了AndroidStudio,則直接用AndroidStudio會(huì)更方便一些。事實(shí)上,AndroidStudio就是在IntelliJIDEA的基礎(chǔ)上進(jìn)行的定制化編輯器。如圖2-4和圖2-5所示,首先需要在AndroidStudio中安裝Dart和Flutter插件;同時(shí)需要在AndroidStudio下激活對Dart語言的支持。如圖2-6所示,并且將Dart語言的本地路徑補(bǔ)全,Dart語言的本地路徑一般在Flutter項(xiàng)目的cache目錄下。圖2-4安裝Flutter插件圖2-5安裝Dart插件圖2-6配置DartSDK路徑最后就是創(chuàng)建Flutter項(xiàng)目的流程,如圖2-7所示,通過在AndroidStudio中單擊“File”→“New”→“NewFlutterProject”命令,就可以創(chuàng)建Flutter項(xiàng)目,整個(gè)創(chuàng)建過程所需時(shí)間視網(wǎng)絡(luò)情況而定,因?yàn)檫@個(gè)過程可能需要從網(wǎng)絡(luò)端同步信息,所以第一次可能會(huì)有點(diǎn)慢。當(dāng)然,也可以從命令終端使用“fluttercreateproject”命令創(chuàng)建項(xiàng)目。圖2-7新建Flutter項(xiàng)目開始創(chuàng)建項(xiàng)目的流程如圖2-8~圖2-10所示,其中重點(diǎn)注意創(chuàng)建時(shí)圖2-9中的Androidx、Kotlin、Swift等可選配置項(xiàng)。在一般情況下,建議選擇Androidx和Kotlin選項(xiàng),但是Swift選項(xiàng)可以不選,因?yàn)槭褂胦bject-c可以讓項(xiàng)目具備更好的兼容性。圖2-8選擇Flutter應(yīng)用模式圖2-9填寫包名并選擇配置信息圖2-10首次打開AndroidStudio時(shí)的選擇創(chuàng)建后的項(xiàng)目工程結(jié)構(gòu)如圖2-11所示,通過單擊頂部左側(cè)的向下三角按鈕,可以選擇啟動(dòng)Android/iOS模擬器,或者連接真實(shí)Android設(shè)備(需要手機(jī)開啟USB調(diào)試)和iOS設(shè)備(需要iOS開發(fā)者賬號),然后單擊頂部右側(cè)三角形圖標(biāo)就可以運(yùn)行項(xiàng)目。在運(yùn)行前,需要在pubspec.yaml文件單擊Packagesget下載項(xiàng)目所需的第三方依賴庫,或者在終端執(zhí)行“flutterpackagesget”命令下載第三方依賴。圖2-11項(xiàng)目工程結(jié)構(gòu)Flutter中主要依靠pubspec.yaml文件添加第三方依賴。如下面的配置信息所示,若是從pub官方平臺添加的依賴,只需要添加項(xiàng)目名稱和版本號,同時(shí)版本號前面的“^”符號表示的意思是匹配最近的一個(gè)大版本,比如^0.1.2將會(huì)匹配所有0.x.x的最新庫,但不包括1.x.x。如果不需要自動(dòng)匹配,可以將“^”去掉。最后需要額外注意的是,一般macOS都會(huì)有CocoaPods,但是如果運(yùn)行iOS時(shí)出現(xiàn)如下錯(cuò)誤,是因?yàn)閙acOS上沒有找到CocoaPods工具,可以通過執(zhí)行“sudogeminstallcocoapods”命令安裝CocoaPods,最后再重新運(yùn)行項(xiàng)目。2.2Dart語言Dart語言是Flutter的特色之一,學(xué)習(xí)過JavaScript、Java或者Kotlin的開發(fā)者,在學(xué)習(xí)Dart上幾乎沒什么難度。Dart語言本身的上手難度也不高,它綜合了動(dòng)態(tài)語言和靜態(tài)語言的一些特性,屬于偽動(dòng)態(tài)語言。Dart雖然是一種面向?qū)ο蟮恼Z言,但是也支持函數(shù)式編程。2.2.1基礎(chǔ)語法Dart語言以main方法為入口開始運(yùn)行,如下面代碼所示,在程序運(yùn)行后將在控制臺輸出“helloworld”。Dart不像Java等語言,它沒有public、private等關(guān)鍵詞用于表示作用域聲明,而是以“_”符號開頭的方法或者成員變量表示私有,通過@protected注解表示調(diào)用保護(hù)的作用。如下面代碼所示,“_privateMember”表示私有內(nèi)部變量;“handleRefresh”方法帶有“@protected”注解,如果在其他路徑下被外部調(diào)用,會(huì)有“Themember'handleRefresh'canonlybeusedwithininstancemembersofsubclassesof'package:xxxxx/****/xxxx/.dart'.”的警告。通過前面的代碼還可以看到,在Dart中通過“//”“///”“/****/”表示代碼注釋,每個(gè)Dart語句必須以分號“;”結(jié)尾,并且Dart作為面向?qū)ο蟮恼Z言,所有的成員都是對象,包括數(shù)字、接口、函數(shù)等都繼承自O(shè)bject對象,默認(rèn)值為null。2.2.2setter/getterDart不需要給變量設(shè)置setter/getter方法,因?yàn)镈art中所有的基礎(chǔ)類型、類等都繼承自O(shè)bject,而Object自帶getter/setter方法。當(dāng)然,如果聲明里帶有final或者const時(shí),那么它只有一個(gè)getter方法。Dart在需要的時(shí)候還可以重載getter/setter。下面的代碼定義了一個(gè)私有的_ratio成員變量,然后對外暴露公開的ratio變量。通過重寫公開的ratio變量的get/set方法,就可以自定義私有_ratio變量的對外行為,比如在之后的“changeValue”方法中,對公開的ratio變量進(jìn)行讀寫時(shí)就會(huì)對應(yīng)調(diào)用到自定義的get/set方法。類似的實(shí)現(xiàn)既可以保護(hù)內(nèi)部私有_ratio變量不暴露,又可以在公開的ratio變量被調(diào)用時(shí)通過get/set方法做自定義判斷處理。2.2.3final/constDart中用final和const做常量聲明,被final和const聲明的變量值無法被修改,const用于表示編譯時(shí)常量,比如“finalname='GSY';constvalue=1000000;”。同時(shí),staticconst組合代表了靜態(tài)常量,其中const的值在編譯期確定,final的值要到運(yùn)行時(shí)才能確定。2.2.4importimport關(guān)鍵字用于實(shí)現(xiàn)類導(dǎo)入,比如將material相關(guān)的控件導(dǎo)入當(dāng)前dart文件中,同時(shí)還可以用“as”增加導(dǎo)入的自定義別名:除了Flutter中常用的package:flutter/material.dart和package:flutter/cupertino.dart包,常用的Dart內(nèi)置包還有dart:io、dart:math等,如表2-1所示。表2-1Dart內(nèi)置包2.2.5基礎(chǔ)數(shù)據(jù)類型Dart中主要支持?jǐn)?shù)字、字符串、布爾、數(shù)組(列表)、集合映射等數(shù)據(jù)類型,并且每種數(shù)據(jù)類型都包含內(nèi)置的封裝方法。數(shù)字類型在Dart中包括整型和浮點(diǎn)型,比如“inti=0;doubley=20.1;”。其中int類型的取值范圍為-253~253,double類型為64位雙精度浮點(diǎn)型,同時(shí)int和double類型其實(shí)都是num類型的子類,所以也可以寫成“numi=0;”。常用num類型內(nèi)置方法如表2-2所示。表2-2常用num類型內(nèi)置方法續(xù)表字符串是一系列的字符文本,如下面代碼所示,可以用""表示,也可以用''表示,同時(shí)字符串支持${expression}表達(dá)式,在表達(dá)式中如果添加的是標(biāo)識符,還可以忽略{}符號,并且字符串支持用+號或者'''換行拼接。需要注意的是,Dart下的數(shù)值在作為字符串使用時(shí),是需要顯式指定toString()的。比如:“inti=0;print("aaaa"+i);”這樣的使用是不支持的,需要使用“print("aaaa"+i.toString())”。當(dāng)然,如果是${expression}表達(dá)式,默認(rèn)會(huì)對參數(shù)調(diào)用toString()方法。布爾類型在Dart中表示為bool,常用于if判斷、assert斷言等地方,并且if語句只支持bool類型,不支持直接使用空數(shù)據(jù)判斷,例如下面的代碼。列表類型在Dart中也是數(shù)組,用List或者[]表示,列表允許存在重復(fù)的元素,允許任意數(shù)量的空值,每個(gè)元素順序插入,同時(shí)在Dart2.3中引入了擴(kuò)展運(yùn)算符“...”和空值判斷擴(kuò)展運(yùn)算符“...?”:如表2-3所示,List支持豐富的內(nèi)置方法。表2-3List內(nèi)置方法集合類型在Dart中用Set或者{}表示,和列表不同的是,Set不允許存在重復(fù)的元素,Set最多允許一個(gè)空值,且Set中的元素都是無序的:如表2-4所示,Set支持豐富的內(nèi)置方法。表2-4Set內(nèi)置方法映射類型在Dart中用Map或者{}表示,Map是以鍵值對形式存儲元素的,對Map而言,key是唯一的,而value可以重復(fù),比如:如表2-5所示,Map支持豐富的內(nèi)置方法。表2-5Map內(nèi)置方法Map、Set、List還有很多內(nèi)置方法,這里就不完全展開講解了。另外補(bǔ)充一點(diǎn),Dart還支持枚舉類型,可以通過enum聲明枚舉:2.2.6邏輯語句與操作符Dart支持“ifelseswitchwhile”等常規(guī)邏輯語句,下面代碼中的“getUserInfo”方法在調(diào)用時(shí)傳入了userName、password文本和UserType枚舉,然后通過if語句進(jìn)行判空,通過switch語句進(jìn)行結(jié)果賦值,最后利用while語句進(jìn)行循環(huán)處理再返回結(jié)果。這里需要注意的是,Dart中if語句只支持bool類型,switch語句支持String、enum、num等類型。同時(shí),if語句也可以通過條件運(yùn)算符表示,如下面代碼所示,當(dāng)key小于0時(shí),返回name,當(dāng)key大于0時(shí),返回value:Dart還支持其他豐富的操作符,比如級聯(lián)操作符。通過級聯(lián)操作符可以對類的內(nèi)部成員進(jìn)行鏈?zhǔn)秸{(diào)用,例如如下代碼:賦值操作符可以讓開發(fā)者減少一些邏輯判斷代碼。賦值操作符“??”、“??=”和“~/”及判空操作符“?”的應(yīng)用如下面代碼所示。Dart還可以對某些操作符進(jìn)行重載,如下面代碼所示,Vector通過對操作符進(jìn)行自定義,實(shí)現(xiàn)了Vector類的加減功能。支持重載的操作符如表2-6所示。表2-6支持重載的操作符2.2.7var與dynamicDart屬于強(qiáng)類型語言,但可以用var聲明變量,Dart對于var聲明會(huì)自推導(dǎo)出數(shù)據(jù)類型。實(shí)際上var是編譯期的語法糖,而dynamic聲明才表示動(dòng)態(tài)類型,dynamic被編譯后是一個(gè)object類型,在編譯期間不對任何的類型進(jìn)行檢查,而是在運(yùn)行時(shí)對類型進(jìn)行檢查。如下面代碼所示,當(dāng)var聲明初始化時(shí),因?yàn)闆]有指定類型和賦值,會(huì)被指定為dynamic,然后賦值的時(shí)候初始化才自推導(dǎo)出i為String類型,這時(shí)候再對i進(jìn)行++操作就會(huì)有“'int'isnotasubtypeoftype'String'”的報(bào)錯(cuò),因?yàn)镾tring類型不能用于計(jì)算。所以,可以看出dynamic類型在運(yùn)行時(shí)才檢測類型,這是開發(fā)時(shí)需要注意的。下面的代碼在編譯期就會(huì)報(bào)錯(cuò),因?yàn)関ar在聲明時(shí),Dart會(huì)通過自推導(dǎo)在編譯期就得知i為String類型,因此不能用于++運(yùn)算。所以,在Dart中要十分注意它屬于強(qiáng)類型語言,并且var并不是真正的動(dòng)態(tài)聲明,dynamic才是。2.2.8函數(shù)方法Dart的方法可以設(shè)置參數(shù)默認(rèn)值和指定名稱,如下面代碼所示的“getRepositoryDetailDao”方法里,branch參數(shù)通過“{}”符號包裹起來,在調(diào)用時(shí)如果不設(shè)置branch的話,則branch默認(rèn)會(huì)是“master”;而方法的參數(shù)類型在聲明時(shí)可以指定或者不指定,比如userName指定了類型,而reposName沒有指定。getRepositoryDetailDao調(diào)用時(shí)對于branch可選參數(shù),需要通過“branch:"dev"”傳遞調(diào)用。在Dart中,方法還可以被作為參數(shù)傳遞,如下面代碼所示,doWhat和doNext方法被作為doSomeThing的參數(shù)進(jìn)行傳遞,這樣的實(shí)現(xiàn)形式可更靈活地組織閉包代碼。2.2.9類、接口和繼承Dart中沒有接口關(guān)鍵字,類都可以作為接口,把某個(gè)類當(dāng)作接口實(shí)現(xiàn)時(shí),只需要使用implements繼承實(shí)現(xiàn),然后復(fù)寫父類方法,implements可以實(shí)現(xiàn)多個(gè)接口。在下面代碼中,Name類實(shí)現(xiàn)了Interface和InterfaceClass的兩個(gè)接口,其中因?yàn)镮nterface是abstract,所以在Interface內(nèi)doA和doB方法可以只聲明不實(shí)現(xiàn)。Dart為了讓類可以像函數(shù)一樣被調(diào)用,默認(rèn)都可以實(shí)現(xiàn)call()方法;同樣地,typedef聲明定義的方法,也是具備call()條件的。在下面代碼中,定義了一個(gè)CallObject,并實(shí)現(xiàn)了CallObject的一個(gè)call方法,這樣在init方法內(nèi)就可以直接執(zhí)行callObject(11,11.0),作為方法使用,或者通過?.call進(jìn)行判空執(zhí)行;typedef聲明的ValueFunction方法,當(dāng)在init2方法內(nèi)執(zhí)行時(shí),同樣可以通過?.call進(jìn)行判空執(zhí)行。2.2.10mixinsDart支持混入的模式,混入時(shí)的基礎(chǔ)順序是從右到左依次執(zhí)行,而且和super方法是否執(zhí)行有關(guān)。當(dāng)多種關(guān)鍵字同時(shí)實(shí)現(xiàn)時(shí),按照出現(xiàn)順序應(yīng)該為extends(繼承)、mixins、implements,事實(shí)上Flutter的啟動(dòng)類就是使用mixins方式實(shí)現(xiàn)的。下面舉個(gè)例子幫助理解,代碼如下:以上代碼實(shí)現(xiàn)的功能如下:●G繼承了B,然后混入了A和A2;●B繼承了Base方法,并重載了a、b、c三個(gè)方法;●A2同樣繼承了Base,但是只重載了a方法;●A同樣繼承了Base,并且只重載了a、b兩個(gè)方法,但是在a方法中沒有super。從最終輸出結(jié)果可以看到:●在執(zhí)行g(shù).a()方法時(shí),輸出是從最右邊的A2.a()開始的,之后才是A.a()、B.a()、basea();●在執(zhí)行g(shù).b()方法時(shí),因?yàn)锳2沒有b()方法存在,所以按照順序只輸出了A.b()、B.b()、baseb();●在執(zhí)行g(shù).c()方法時(shí),因?yàn)锳2和A都沒有實(shí)現(xiàn)c方法,所以只輸出了B.c()和basec()。2.2.11構(gòu)造方法Dart支持多個(gè)構(gòu)造方法,在下面的代碼中,ModelA默認(rèn)構(gòu)造方法只能有一個(gè),可以通過“,this.tag”實(shí)現(xiàn)成員變量的初始化;而通過Model.empty()方法,可以自定義一個(gè)空參數(shù)的構(gòu)造方法;這里方法名稱可以按照需求自定義,比如ModelA.forName就是在新的構(gòu)造方法中,通過實(shí)現(xiàn)成員變量自定義的。除了默認(rèn)構(gòu)造方法,其他構(gòu)造方法都是通過“類型.xxxx()”的模式實(shí)現(xiàn)的。另外,在構(gòu)造方法中,也支持“{}”可選配置,比如ModelA.forOther方法。2.2.12異常處理Dart在遇到錯(cuò)誤時(shí)會(huì)拋出異常,每個(gè)異常都是類Exception的子類,開發(fā)者可以通過throw主動(dòng)拋出異常,并且通過try/catch實(shí)現(xiàn)異常捕獲,也可以繼承Exception實(shí)現(xiàn)自定義的異常拋出。如下面代碼所示,在代碼on的時(shí)候,可以指定需要catch的異常類型,若直接使用Exception表示catch全部,最后可以在finally里做異常處理的退出處理。如表2-7所示為常見的異常類型。表2-7常見的異常類型2.2.13IsolateDart在單線程模式中增加了Isolate提供跨線程的真異步操作。因?yàn)樵贒art中線程不會(huì)共享內(nèi)存,所以也不存在死鎖,從而也導(dǎo)致了Isolate之間的數(shù)據(jù)只能通過port的端口方式發(fā)送接口,所以Isolate也稱為隔離的執(zhí)行。同時(shí),Dart還提供了compute的封裝接口,方便調(diào)用Isolate的快速使用。Isolate主要在import'dart:isolate'包中,其中:●Isolate用于Dart執(zhí)行上下文隔離;●ReceivePort與SendPort一起,是唯一的通信方式;●SendPort將消息發(fā)送到其他ReceivePort。在下面的代碼中,spawn幫助簡化了Isolate創(chuàng)建過程,通過ReceivePort創(chuàng)建后可以用于listen監(jiān)聽數(shù)據(jù),而Isolate.spawn通過echoResult的異步執(zhí)行邏輯,可以在echoResult中通過sendPort將數(shù)據(jù)發(fā)送回來。當(dāng)然,Dart還提供了簡化版的處理方法pute方法通過外部傳入方法和數(shù)據(jù),然后執(zhí)行得到返回結(jié)果。需要注意的是,compute中運(yùn)行的方法必須是頂級方法或者static方法,比如decodeListResult必須是全局方法或者static方法:2.2.14ZoneDart可通過Zone表示指定的代碼執(zhí)行環(huán)境,類似一個(gè)代碼運(yùn)行沙盒的概念。在Flutter中,C++運(yùn)行Dart也是通過在_runMainZoned方法內(nèi)執(zhí)行runZoned方法來啟動(dòng)Dart程序的。同樣在開發(fā)過程中,也可以通過Zone指定環(huán)境運(yùn)行,下面的代碼就是利用runZoned在運(yùn)行環(huán)境內(nèi)捕獲全局異常等信息。另外,也可以給runZoned注冊方法,在需要時(shí)執(zhí)行回調(diào)。如下面代碼所示,在一個(gè)Zone內(nèi)任何地方,只要能獲取onData這個(gè)ZoneUnaryCallback,就都可以調(diào)用到handleData。異步邏輯同樣可以通過Zone的scheduleMicrotask方法插入異步執(zhí)行,例如下面的代碼。關(guān)于Zone相關(guān)的概念在后續(xù)章節(jié)中還會(huì)詳細(xì)介紹。2.2.15異步執(zhí)行在Flutter中支持async/await表示異步執(zhí)行,如下面代碼所示,request方法和doSomeThing方法都是異步執(zhí)行,這里的異步有別于Isolate執(zhí)行,屬于“假異步”的實(shí)現(xiàn)邏輯。在Dart中,通過單線程的任務(wù)調(diào)度實(shí)現(xiàn)而不是并發(fā)實(shí)現(xiàn),具體實(shí)現(xiàn)后續(xù)章節(jié)會(huì)詳細(xì)介紹。這里需要知道的是async/await是語法糖,最終還是通過編譯器轉(zhuǎn)為Future對象執(zhí)行,而Future對象除了await等待異步結(jié)果,還可以通過then鏈?zhǔn)交卣{(diào)執(zhí)行下一步。簡單地說,F(xiàn)uture就是對Zone的封裝使用,如下面代碼所示,F(xiàn)uture.microtask中主要是執(zhí)行了Zone的scheduleMicrotask方法,而result._complete最后調(diào)用的是_zone.runUnary等方法(關(guān)于runUnary相關(guān)的內(nèi)容,后續(xù)講解Stream的章節(jié)會(huì)詳細(xì)介紹)。所以,Dart可以通過async/await實(shí)現(xiàn)異步執(zhí)行邏輯,也可以通過Future實(shí)現(xiàn)一樣的操作,并且await關(guān)鍵字只能在async中配對使用,它們的具體實(shí)現(xiàn)邏輯后續(xù)章節(jié)會(huì)詳細(xì)分析,這里暫時(shí)知道它們的使用方法即可。Dart還有另外一種異步操作:async*/yield或者Stream。async*/yield也只是語法糖,最終通過編譯器轉(zhuǎn)為Stream實(shí)現(xiàn)處理。但是Stream除了異步執(zhí)行,還支持同步操作,事實(shí)上Stream不是單純用于異步邏輯,而是事件流處理模型。簡單來說,Stream中主要有StreamController、StreamSink、Stream和StreamSubscription四個(gè)關(guān)鍵對象,大致總結(jié)如下?!馭treamController:如類名描述,用于整個(gè)Stream過程的控制,提供各類接口用于創(chuàng)建各種事件流?!馭treamSink:一般作為事件的入口,提供如add和addStream等操作。●Stream:事件源本身,一般可用于監(jiān)聽事件或者對事件進(jìn)行轉(zhuǎn)換,如listen和where等操作?!馭treamSubscription:事件訂閱后的對象,表面上用于管理訂閱等各類操作,如cancel和pause等,同時(shí)在內(nèi)部也是事件中轉(zhuǎn)的關(guān)鍵。一般開發(fā)中會(huì)通過StreamController創(chuàng)建Stream,通過StreamSink添加事件,通過Stream監(jiān)聽事件,通過StreamSubscription管理訂閱。另外,Stream中支持各種變化,比如map、expand、where和take等操作,還支持轉(zhuǎn)換為Future執(zhí)行返回結(jié)果。下面的代碼展示了如何使用Stream封裝出簡單的EventBus邏輯。EventBus主要利用了StreamController創(chuàng)建出Stream對象,之后通過Stream流對HttpErrorEvent事件進(jìn)行監(jiān)聽,然后利用fire方法將事件發(fā)送到對應(yīng)的監(jiān)聽上進(jìn)行處理。另外,對于事件還可以做where、take等二次變換,這里的具體使用和操作邏輯,在后續(xù)篇章中會(huì)詳細(xì)介紹,可以暫時(shí)先了解Stream的基礎(chǔ)概念。2.2.16拓展方法伴隨著Flutter1.12而來的Dart2.7還支持拓展方法,它使用戶可以向任何類型(甚至是無法控制的類型)添加新功能,并具有常規(guī)方法調(diào)用的簡潔性和自動(dòng)完成性,例如下面的代碼。2.3Flutter控件介紹Flutter作為跨平臺UI框架,它主要解決的是界面的跨平臺。在Flutter中,一切的顯示都是Widget,Widget是控件的基礎(chǔ)。關(guān)于Widget真實(shí)的內(nèi)部渲染邏輯在下一章會(huì)詳細(xì)介紹,本小節(jié)主要介紹Widget的相關(guān)使用,讓讀者能掌握Flutter控件的基礎(chǔ)開發(fā)技巧。Widget在開發(fā)過程中可以分為有狀態(tài)和無狀態(tài)兩種,在Flutter中每個(gè)頁面都是一幀。無狀態(tài)就是保持在那一幀,而對于有狀態(tài)的Widget,當(dāng)數(shù)據(jù)更新時(shí),其實(shí)是構(gòu)建了新的Widget,因?yàn)閃idget是不可變的,只是State實(shí)現(xiàn)了數(shù)據(jù)的跨幀保存與恢復(fù)。2.3.1無狀態(tài)控件(StatelessWidget)下面的代碼是無狀態(tài)控件的簡單實(shí)現(xiàn),通過繼承StatelessWidget,然后在build方法內(nèi)返回一個(gè)嵌套好的布局進(jìn)行渲染。這里讀者可能還對Flutter的內(nèi)置控件不大熟悉,不過沒關(guān)系,后面馬上就會(huì)詳細(xì)介紹,這里只需了解一個(gè)無狀態(tài)控件的實(shí)現(xiàn)就是這么簡單。在Flutter中,Widget和Widget之間默認(rèn)是通過child進(jìn)行嵌套的,其中有的Widget只能有一個(gè)child,比如上面代碼中的Container;有的Widget可以有多個(gè)child,比如Column布局支持多個(gè)子控件,所以它的參數(shù)就不是child而是children。2.3.2有狀態(tài)控件(StatefulWidget)前面說過,對于有狀態(tài)的Widget,當(dāng)數(shù)據(jù)更新時(shí),其實(shí)還構(gòu)建了新的Widget,只是State實(shí)現(xiàn)了數(shù)據(jù)的跨幀保存與恢復(fù),所以StatefulWidget的關(guān)鍵就在于State。在下面的代碼中,DemoStateWidget繼承了StatefulWidget,然后構(gòu)建了DemoStateWidgetState對象,可以看到之前的build方法被_DemoStateWidgetState代理了,業(yè)務(wù)邏輯幾乎是通過State實(shí)現(xiàn)的。在State中,開發(fā)者們可以動(dòng)態(tài)地改變數(shù)據(jù),通過執(zhí)行“setState({})”觸發(fā)頁面更新,在“setState({})”的“{}”內(nèi)可以編寫需要改變的數(shù)據(jù),最終這個(gè)操作會(huì)觸發(fā)Widget重新構(gòu)建去更新界面。比如下面代碼運(yùn)行后的結(jié)果是:在頁面打開兩秒之后,文本顯示從“這就是有狀態(tài)DMEO”變?yōu)椤斑@就變了數(shù)值”。而通過上面的代碼,還可以看出State中具備聲明周期,比如以下三項(xiàng)?!駃nitState:初始化,理論上只初始化一次?!馾idChangeDependencies:在initState之后調(diào)用,此時(shí)可以獲取其他State。●dispose:銷毀,只會(huì)調(diào)用一次。State的生命周期整體流程如圖2-12所示,可以看出,didChangeDependencies方法可能存在被多次調(diào)用的情況,因?yàn)镮nheritedWidget也會(huì)觸發(fā)這個(gè)回調(diào)。InheritedWidget在后續(xù)章節(jié)會(huì)有詳細(xì)講解,這里先了解didChangeDependencies可能存在被多次調(diào)用的情況,同時(shí)setState觸發(fā)的更新還會(huì)執(zhí)行didUpdateWidget回調(diào)。到這里可以看到,F(xiàn)lutter其實(shí)就是這么簡單,因?yàn)镕lutter是響應(yīng)式的開發(fā)設(shè)計(jì),開發(fā)者的關(guān)注點(diǎn)在于創(chuàng)建StatelessWidget或者StatefulWidget用于承載布局,然后在build方法中堆積布局,之后可以把需要顯示的數(shù)據(jù)綁定到Widget中,最后通過setState改變數(shù)據(jù)從而實(shí)現(xiàn)界面的變化。圖2-12State生命周期2.3.3Flutter常用控件Flutter擁有30多種內(nèi)置的控件與布局,其中常用的有Container、Padding、Center、Stack、Column、Row、Expanded和ListView等,如表2-8所示。下面簡單講解它們的特性和使用方法。表2-8常用控件與布局Container是最常用的默認(rèn)布局,它其實(shí)是由好幾個(gè)默認(rèn)的Widget組成的模板。Container只能包含一個(gè)child,支持配置padding、margin、color、width、height和decoration(一般配置邊框和陰影)等屬性。在Flutter中,不是所有的控件都有width、height、padding、margin和color等屬性,因?yàn)镕lutter中默認(rèn)的Widget顆粒度都很細(xì),一般都是Padding、Center和Row等Widget,它們負(fù)責(zé)的功能都比較單一。開發(fā)時(shí)經(jīng)常需要將Widget搭配成通用的模板,比如這里提到的Container。Column和Row也是必備布局,橫豎布局屬于日常開發(fā)中最常見的場景。如下面代碼所示,除了使用children做嵌套,它們常用的可配置屬性還有:主軸方向是start或者center等;副軸方向是start或者center;mainAxisSize表示控件主軸是充滿最大尺寸或者只根據(jù)child的大小調(diào)整自身大小等。Expanded控件用于在Column和Row中Flex充滿,當(dāng)有兩個(gè)Expanded控件在Column或Row控件中時(shí)默認(rèn)均分充滿,可以通過設(shè)置flex屬性決定填充比例,flex值默認(rèn)是1,例如下面的代碼。Center和Align用于做對齊顯示,Center其實(shí)就是Align的默認(rèn)狀態(tài)。在Align中,主要是通過Alignment實(shí)現(xiàn)位置調(diào)整的,而Alignment默認(rèn)具備如下面代碼所示的類型封裝,Center恰好就是center=Alignment(0.0,0.0)。接下來,結(jié)合上述介紹的Widget實(shí)現(xiàn)一個(gè)簡單的控件。如下面代碼所示,首先創(chuàng)建一個(gè)私有方法_getBottomItem用于返回一個(gè)Expanded控件,該方法用于后續(xù)在Row下實(shí)現(xiàn)平均充滿。在return的布局內(nèi),主要是實(shí)現(xiàn)了一個(gè)居中的Icon圖標(biāo)和文本,中間間隔5.0的padding,得到如圖2-13所示的界面效果。圖2-13Row控件中的Child接著把上述的_getBottomItem方法放到新的布局里。如下面代碼所示,首先是Container包含了Card用于快速地實(shí)現(xiàn)圓角和陰影效果;其次使用FlatButton(按鍵)控件實(shí)現(xiàn)了點(diǎn)擊;接著通過Padding實(shí)現(xiàn)了邊距;然后又通過Column垂直布局包含了兩個(gè)子Widget,一個(gè)是Container,一個(gè)是Row。Column中的Container用于包裹Text控件調(diào)整文本;Column中的Row控件使用_getBottomItem方法返回前面配置好的布局。最終得到效果如圖2-14所示的界面。圖2-14ListView中的CardChild如圖2-15所示為分類總結(jié)的常用控件,可以看到,在Flutter中的布局就是這樣一層一層嵌套出來的。因?yàn)樵谀J(rèn)情況下,各個(gè)Widget的“職能”都很細(xì),所以開發(fā)者需要根據(jù)自己的業(yè)務(wù)場景,最終配置出各類通用的控件模板,來減少代碼的嵌套。當(dāng)然,還有其他更高級的自定義布局方式,這里暫時(shí)先不展開。圖2-15分類總結(jié)的常用控件2.3.4Flutter頁面Flutter除了布局的Widget,還有用于交互顯示的Widget和用于完整頁面信息的Widget,常見的有MaterialApp、Scaffold、Appbar、Text、RichText、TextField、Image和FlatButton等,如表2-9所示。本小節(jié)開始簡單介紹這些Widget并完成一個(gè)獨(dú)立的頁面展示。表2-9常用開發(fā)Widget如下面代碼所示,通過在build方法內(nèi)返回一個(gè)Scaffold,就可以快速地實(shí)現(xiàn)一個(gè)簡單完整的頁面需求。在上面的代碼中,首先創(chuàng)建了一個(gè)StatefulWidget的DemoPage用于承載布局;然后在_DemoPageState中通過build創(chuàng)建了一個(gè)Scaffold腳手架。Flutter一般用Scaffold作為一個(gè)頁面的起始腳手架,然后在Scaffold配置一個(gè)AppBar標(biāo)題欄,配置一個(gè)ListView用于顯示內(nèi)容。AppBar在Flutter中用于實(shí)現(xiàn)標(biāo)題區(qū)域,默認(rèn)會(huì)將title設(shè)置為Text,用于展示標(biāo)題文本。并且AppBar內(nèi)部會(huì)判斷堆棧信息,決定是否顯示返回按鍵。而在ListView中,返回了20個(gè)之前創(chuàng)建過的DemoItem控件,并且會(huì)從AppBar下方開始展示。如下面代碼所示,要運(yùn)行Flutter程序還需要一個(gè)Application,所以這里創(chuàng)建了一個(gè)StatelessWidget作為容器,使用了MaterialApp作為應(yīng)用入口,將上方的DemoPage設(shè)置為MaterialApp的home頁面,通過main入口執(zhí)行加載DemoApp,最終可以得到如圖2-16所示的Demo頁面效果。圖2-16Demo頁面效果2.3.5路由跳轉(zhuǎn)Flutter中的頁面跳轉(zhuǎn)是通過Navigator實(shí)現(xiàn)的,而路由跳轉(zhuǎn)又可以分為:命名路由跳轉(zhuǎn)和直接使用Route跳轉(zhuǎn)。命名路由跳轉(zhuǎn)默認(rèn)可以通過MaterialApp的routes參數(shù)配置路由表;而直接使用Route的跳轉(zhuǎn),需要構(gòu)建Route參數(shù)信息,然后通過Navigator實(shí)現(xiàn)調(diào)整。如下面代碼所示,在MaterialApp內(nèi)可以通過routes參數(shù)指定路由名稱和頁面的映射,上一小節(jié)中MaterialApp的home參數(shù),指的就是routes中key值為“/”的映射。當(dāng)命名路由跳轉(zhuǎn)時(shí),通過Navigator.pushNamed傳入key就可以跳轉(zhuǎn)到key對應(yīng)的頁面,并且可以在pushNamed方法中通過arguments參數(shù)傳遞參數(shù)。arguments是一個(gè)Object對象,傳遞的內(nèi)容在新的頁面內(nèi)可以通過“ModalRoute.of(context).settings.arguments;”的方法獲取。前面介紹的命令路由,內(nèi)部實(shí)現(xiàn)其實(shí)就是對直接使用Route路由跳轉(zhuǎn)的封裝。直接使用Route跳轉(zhuǎn)可以通過Navigator.push方法傳入BuildContext和MaterialPageRoute來實(shí)現(xiàn)頁面跳轉(zhuǎn)。另外可以看到,Navigator的push方法返回的是一個(gè)Future,這個(gè)Future的作用是在頁面返回時(shí)被調(diào)用,也就是可以通過Navigator的pop方法在返回時(shí)攜帶參數(shù),之后在Future的then回調(diào)中就可以監(jiān)聽頁面的返回結(jié)果:對于路由跳轉(zhuǎn),更高級的用法還有在命名路由中通過指定onGenerateRoute方法實(shí)現(xiàn)自定義處理。如下面代碼所示,就是利用onGenerateRoute方法在MaterialApp中增加路由頁面的特殊定制處理。另外,直接使用Route跳轉(zhuǎn)和命名式路由跳轉(zhuǎn)可以混合使用,因?yàn)樗鼈冏罱K都是通過Route構(gòu)建新的頁面,至于為什么Route在Flutter中可以作為一個(gè)新頁面的配置,這在后續(xù)章節(jié)會(huì)詳細(xì)解釋。2.4Flutter常見開發(fā)技巧本節(jié)將介紹一些Flutter常見的開發(fā)技巧。2.4.1常見的問題處理(1)當(dāng)項(xiàng)目在執(zhí)行flutterpackagesget等操作時(shí),可能會(huì)遇到“Waitingforanotherfluttercommandtoreleasethestartuplock”的提示,是因?yàn)镕lutter命令需要等上一個(gè)任務(wù)執(zhí)行完成。如果出現(xiàn)長時(shí)間卡住的情況,可以通過打開Flutter本地目錄的/bin/cache/,然后找到lockfile文件,將其刪除后并重新運(yùn)行就不會(huì)被提示打斷。(2)當(dāng)控件出現(xiàn)如圖2-17所示的狀態(tài)時(shí),是因?yàn)闆]有使用Material相關(guān)的默認(rèn)設(shè)置,常見于沒有使用MaterialApp或者Scaffold做嵌套處理。比如在使用showDialog等方法打開彈出時(shí),需要用Scaffold或者M(jìn)aterial做嵌套顯示。圖2-17沒有Material效果的情況下(3)當(dāng)編寫Flutter代碼時(shí),在編輯框里輸入stl可以自動(dòng)彈出創(chuàng)建無狀態(tài)控件的模板選項(xiàng);而當(dāng)輸入stf時(shí),會(huì)彈出創(chuàng)建有狀態(tài)控件的模板選項(xiàng)。另外,在對代碼格式化時(shí),括號內(nèi)外的逗號都會(huì)影響格式化的換行位置,如圖2-18所示。如果覺得默認(rèn)換行的線太短,可以在-Editor-CodeStyle-Dart-WrappingandBraces-Hardwrapat中設(shè)置自定義的數(shù)值。圖2-18默認(rèn)格式化換行(4)Flutter在Debug模式下為JIT運(yùn)行,所以并不能用于性能對比;需要在Release模式下測試性能,可以給應(yīng)用配置profile啟動(dòng)模式,具體實(shí)現(xiàn)可以參考第9章的性能調(diào)測內(nèi)容。另外需要注意的是,不要用模擬器測試性能,因?yàn)槿鏸OS模式器是不支持GPU加速的。(5)如果需要配置Flutter的啟動(dòng)圖,iOS的啟動(dòng)頁可以在“ios/Runner/Assets.xcassets/LaunchImage.imageset/”下添加配置,該目錄下有Contents.json文件和啟動(dòng)圖片,將需要修改的啟動(dòng)頁放

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論