今天我們聊聊跨平臺(tái)解決方案秀存,通過(guò)此文,我們可以了解到
- 跨平臺(tái)技術(shù)的主流解決方案羽氮,對(duì)比
- flutter的原理或链、優(yōu)勢(shì)
- Dart語(yǔ)法
- 用flutter,搭建一個(gè)基礎(chǔ)app档押,涵蓋各種常見(jiàn)操作
跨平臺(tái)技術(shù)簡(jiǎn)介
對(duì)于跨平臺(tái)開(kāi)發(fā)汇荐,業(yè)界一直都在努力尋找好的解決方案洞就。時(shí)至今日,已經(jīng)有很多成熟的解決方案掀淘,根據(jù)其原理旬蟋,我們主要分為三類(lèi):
- H5+原生(Cordova、Ionic)
- JavaScript開(kāi)發(fā)+原生渲染 (React Native革娄、Weex)
- 自繪UI+原生(Flutter)
接下來(lái)我們逐個(gè)來(lái)看看這三種類(lèi)型的原理及優(yōu)缺點(diǎn)倾贰。
H5+原生
將APP的某些頁(yè)面,通過(guò)WebView (Android)或WKWebView(IOS)加載網(wǎng)頁(yè)地址實(shí)現(xiàn)功能拦惋。
因?yàn)閃ebView實(shí)質(zhì)上就是一個(gè)瀏覽器內(nèi)核匆浙,其JavaScript依然運(yùn)行在一個(gè)權(quán)限受限的沙箱中,所以對(duì)于大多數(shù)系統(tǒng)能力都沒(méi)有訪問(wèn)權(quán)限厕妖,如無(wú)法訪問(wèn)文件系統(tǒng)首尼、不能使用藍(lán)牙等。對(duì)于H5不能實(shí)現(xiàn)的功能,都需要原生去做软能,因此我們會(huì)實(shí)現(xiàn)一些訪問(wèn)原生能力的API迎捺, 通過(guò)JavaScript 暴露給網(wǎng)頁(yè)調(diào)用。
- 優(yōu)點(diǎn)
接入簡(jiǎn)單 - 缺點(diǎn)
性能表現(xiàn)一般,在復(fù)雜的UI樣式下跋核,不流暢岖瑰。如果沒(méi)做離線緩存,耗費(fèi)流量砂代,頁(yè)面載入速度比原生慢锭环。
JavaScript開(kāi)發(fā)+原生渲染
此類(lèi)框架將UI節(jié)點(diǎn)映射成原生控件,并通過(guò)JS控制泊藕,其最終產(chǎn)品是一個(gè)“原生”的移動(dòng)應(yīng)用辅辩。
比如 React Native,他的控件ScrollView娃圆、Image玫锋,映射到IOS是 UIScrollView、UIImageView讼呢;安卓則是 ScrollView撩鹿、ImageView。
因此悦屏,從使用感受上和用Objective-C或Java編寫(xiě)的原生應(yīng)用相近节沦。
React Native
React Native (簡(jiǎn)稱(chēng)RN)是Facebook于2015年4月開(kāi)源的跨平臺(tái)移動(dòng)應(yīng)用開(kāi)發(fā)框架,是Facebook早先開(kāi)源的JS框架 React 在原生移動(dòng)應(yīng)用平臺(tái)的衍生產(chǎn)物础爬,目前支持iOS和Android兩個(gè)平臺(tái)甫贯。RN使用Javascript語(yǔ)言,類(lèi)似于HTML的JSX看蚜,以及CSS來(lái)開(kāi)發(fā)移動(dòng)應(yīng)用叫搁,因此熟悉Web前端開(kāi)發(fā)的技術(shù)人員只需很少的學(xué)習(xí)就可以進(jìn)入移動(dòng)應(yīng)用開(kāi)發(fā)領(lǐng)域。
由于RN現(xiàn)在比較火熱供炎,并且Flutter也是受React啟發(fā)渴逻,很多思想也都是相通的,我們有必要深入了解一下React原理音诫。React是一個(gè)響應(yīng)式的Web框架惨奕,我們先了解一下兩個(gè)重要的概念:Dom樹(shù)
與響應(yīng)式編程
。
DOM樹(shù)
文檔對(duì)象模型(Document Object Model竭钝,簡(jiǎn)稱(chēng)DOM)梨撞,是W3C組織推薦的處理可擴(kuò)展標(biāo)志語(yǔ)言的標(biāo)準(zhǔn)編程接口雹洗,一種獨(dú)立于平臺(tái)和語(yǔ)言的方式訪問(wèn)和修改一個(gè)文檔的內(nèi)容和結(jié)構(gòu)。
簡(jiǎn)單來(lái)說(shuō)聋袋,DOM就是文檔樹(shù),與用戶(hù)界面控件樹(shù)對(duì)應(yīng)穴吹,在前端開(kāi)發(fā)中通常指HTML對(duì)應(yīng)的渲染樹(shù)幽勒,但廣義的DOM也可以指Android中的XML布局文件對(duì)應(yīng)的控件樹(shù),而術(shù)語(yǔ)DOM操作就是指直接來(lái)操作渲染樹(shù)(或控件樹(shù))港令, 因此啥容,可以看到其實(shí)DOM樹(shù)和控件樹(shù)是等價(jià)的概念,只不過(guò)前者用于Web開(kāi)發(fā)中顷霹,而后者常用于原生開(kāi)發(fā)中咪惠。
響應(yīng)式編程
響應(yīng)式編程是與異步數(shù)據(jù)流交互的一種編程范式。
React中提出一個(gè)重要思想:狀態(tài)改變則UI隨之自動(dòng)改變淋淀,而React框架本身就是響應(yīng)用戶(hù)狀態(tài)改變的事件而執(zhí)行重新構(gòu)建用戶(hù)界面的工作遥昧,這就是典型的響應(yīng)式編程范式,下面我們總結(jié)一下React中響應(yīng)式原理:
開(kāi)發(fā)者只需關(guān)注狀態(tài)轉(zhuǎn)移(數(shù)據(jù))朵纷,當(dāng)狀態(tài)發(fā)生變化炭臭,React框架會(huì)自動(dòng)根據(jù)新的狀態(tài)重新構(gòu)建UI。
React框架在接收到用戶(hù)狀態(tài)改變通知后袍辞,會(huì)根據(jù)當(dāng)前渲染樹(shù)鞋仍,結(jié)合最新的狀態(tài)改變,通過(guò)Diff算法搅吁,計(jì)算出樹(shù)中變化的部分威创,然后只更新變化的部分(DOM操作),從而避免整棵樹(shù)重構(gòu)谎懦,提高性能肚豺。
值得注意的是,在第二步中界拦,狀態(tài)變化后React框架并不會(huì)立即去計(jì)算并渲染DOM樹(shù)的變化部分详炬,相反,React會(huì)在DOM的基礎(chǔ)上建立一個(gè)抽象層寞奸,即虛擬DOM樹(shù)呛谜,對(duì)數(shù)據(jù)和狀態(tài)所做的任何改動(dòng),都會(huì)被自動(dòng)且高效的同步到虛擬DOM枪萄,最后再批量同步到真實(shí)DOM中隐岛,而不是每次改變都去操作一下DOM。為什么不能每次改變都直接去操作DOM樹(shù)瓷翻?這是因?yàn)樵跒g覽器中每一次DOM操作都有可能引起瀏覽器的重繪或回流:
如果DOM只是外觀風(fēng)格發(fā)生變化聚凹,如顏色變化割坠,會(huì)導(dǎo)致瀏覽器重繪界面。如果DOM樹(shù)的結(jié)構(gòu)發(fā)生變化妒牙,如尺寸彼哼、布局、節(jié)點(diǎn)隱藏等導(dǎo)致湘今,瀏覽器就需要回流(及重新排版布局)敢朱。而瀏覽器的重繪和回流都是比較昂貴的操作,如果每一次改變都直接對(duì)DOM進(jìn)行操作摩瞎,這會(huì)帶來(lái)性能問(wèn)題拴签,而批量操作只會(huì)觸發(fā)一次DOM更新。
上文已經(jīng)提到React Native 是React 在原生移動(dòng)應(yīng)用平臺(tái)的衍生產(chǎn)物旗们,那兩者主要的區(qū)別是什么呢蚓哩?其實(shí),主要的區(qū)別在于虛擬DOM映射的對(duì)象是什么上渴?React中虛擬DOM最終會(huì)映射為瀏覽器DOM樹(shù)岸梨,而RN中虛擬DOM會(huì)通過(guò) JavaScriptCore 映射為原生控件樹(shù)。
JavaScriptCore 是一個(gè)JavaScript解釋器稠氮,它在React Native中主要有兩個(gè)作用:
- 為JavaScript提供運(yùn)行環(huán)境盛嘿。
- 是JavaScript與原生應(yīng)用之間通信的橋梁,作用和JsBridge一樣括袒,事實(shí)上次兆,在iOS中,很多JsBridge的實(shí)現(xiàn)都是基于 JavaScriptCore 锹锰。
而RN中將虛擬DOM映射為原生控件的過(guò)程中分兩步:
- 布局消息傳遞芥炭; 將虛擬DOM布局信息傳遞給原生;
- 原生根據(jù)布局信息通過(guò)對(duì)應(yīng)的原生控件渲染控件樹(shù)恃慧;
至此园蝠,React Native 便實(shí)現(xiàn)了跨平臺(tái)。 相對(duì)于混合應(yīng)用痢士,由于React Native是原生控件渲染彪薛,所以性能會(huì)比混合應(yīng)用中H5好很多,同時(shí)React Native是Web開(kāi)發(fā)技術(shù)棧怠蹂,也只需維護(hù)一份代碼善延,同樣是跨平臺(tái)框架。
Weex
Weex 致力于使開(kāi)發(fā)者能基于通用跨平臺(tái)的 Web 開(kāi)發(fā)語(yǔ)言和開(kāi)發(fā)經(jīng)驗(yàn)城侧,來(lái)構(gòu)建 Android易遣、iOS 和 Web 應(yīng)用。簡(jiǎn)單來(lái)說(shuō)嫌佑,在集成了 WeexSDK 之后豆茫,你可以使用 JavaScript 語(yǔ)言和前端開(kāi)發(fā)經(jīng)驗(yàn)來(lái)開(kāi)發(fā)移動(dòng)應(yīng)用侨歉。
Weex 渲染引擎與 DSL 語(yǔ)法層是分開(kāi)的,Weex 并不強(qiáng)依賴(lài)任何特定的前端框架揩魂。目前 Vue.js 和 Rax 這兩個(gè)前端框架被廣泛應(yīng)用于 Weex 頁(yè)面開(kāi)發(fā)幽邓,同時(shí) Weex 也對(duì)這兩個(gè)前端框架提供了最完善的支持。Weex 的另一個(gè)主要目標(biāo)是跟進(jìn)流行的 Web 開(kāi)發(fā)技術(shù)并將其和原生開(kāi)發(fā)的技術(shù)結(jié)合火脉,實(shí)現(xiàn)開(kāi)發(fā)效率和運(yùn)行性能的高度統(tǒng)一牵舵。在開(kāi)發(fā)階段,一個(gè) Weex 頁(yè)面就像開(kāi)發(fā)普通網(wǎng)頁(yè)一樣忘分;在運(yùn)行時(shí)棋枕,Weex 頁(yè)面又充分利用了各種操作系統(tǒng)的原生組件和能力白修。
JavaScript開(kāi)發(fā)+原生渲染的方式優(yōu)缺點(diǎn)如下:
優(yōu)點(diǎn):
- 采用Web開(kāi)發(fā)技術(shù)棧妒峦,社區(qū)龐大、上手快兵睛、開(kāi)發(fā)成本相對(duì)較低肯骇。
- 原生渲染,性能相比H5提高很多祖很。
不足:
- 渲染時(shí)需要JavaScript和原生之間通信笛丙,在有些場(chǎng)景如拖動(dòng)可能會(huì)因?yàn)橥ㄐ蓬l繁導(dǎo)致卡頓。
- 由于渲染依賴(lài)原生控件假颇,不同平臺(tái)的控件需要單獨(dú)維護(hù)胚鸯,并且當(dāng)系統(tǒng)更新時(shí),社區(qū)控件可能會(huì)滯后笨鸡;除此之外姜钳,其控件系統(tǒng)也會(huì)受到原生UI系統(tǒng)限制。
- JavaScript為腳本語(yǔ)言形耗,執(zhí)行時(shí)需要JIT哥桥,執(zhí)行效率和AOT代碼有差距。
自繪UI+原生
我們看看最后一種跨平臺(tái)技術(shù):自繪UI+原生激涤。這種技術(shù)的思路是拟糕,通過(guò)在不同平臺(tái)實(shí)現(xiàn)一個(gè)統(tǒng)一接口的渲染引擎來(lái)繪制UI,而不依賴(lài)系統(tǒng)原生控件倦踢,所以可以做到不同平臺(tái)UI的一致性送滞。因?yàn)榧夹g(shù)難度比較大,目前成熟且流行的代表作就是Google出品的Flutter辱挥,也是我們今天所要介紹的累澡。
Flutter是什么
Flutter 是 Google推出并開(kāi)源的移動(dòng)應(yīng)用開(kāi)發(fā)框架,主打跨平臺(tái)般贼、高保真愧哟、高性能奥吩。
開(kāi)發(fā)者通過(guò) Dart語(yǔ)言開(kāi)發(fā) App,一套代碼可以同時(shí)運(yùn)行在 iOS 和 Android平臺(tái)蕊梧。 Flutter提供了豐富的組件霞赫、接口,開(kāi)發(fā)者可以很快地為 Flutter添加 native擴(kuò)展肥矢。同時(shí) Flutter還使用 Native引擎渲染視圖端衰,這無(wú)疑能為用戶(hù)提供良好的體驗(yàn)。
這種平臺(tái)技術(shù)的優(yōu)點(diǎn)如下:
- 性能高甘改;由于自繪引擎是直接調(diào)用系統(tǒng)API來(lái)繪制UI旅东,所以性能和原生控件不相上下。
- 靈活十艾、組件庫(kù)易維護(hù)抵代、UI外觀保真度和一致性高;由于UI渲染不依賴(lài)原生控件忘嫉,也就不需要根據(jù)不同平臺(tái)的控件單獨(dú)維護(hù)一套組件庫(kù)荤牍,所以代碼容易維護(hù)。由于組件庫(kù)是同一套代碼庆冕、同一個(gè)渲染引擎康吵,所以在不同平臺(tái),組件顯示外觀可以做到高保真和高一致性访递;另外晦嵌,由于不依賴(lài)原生控件,也就不會(huì)受原生布局系統(tǒng)的限制拷姿,這樣布局系統(tǒng)會(huì)非常靈活惭载。
不足:
- 涉及到硬件層功能(定位、緩存文件位置等)的訪問(wèn)跌前,仍然需要用原生代碼實(shí)現(xiàn)接入棕兼,期待第三方庫(kù)發(fā)展成熟。
- 熱更新能力抵乓,相比 H5伴挚、React有所欠缺 ,《閑魚(yú)動(dòng)態(tài)下發(fā)解決方案》
為什么選flutter
不僅僅是跨平臺(tái)的UI
除了一套豐富的跨平臺(tái)UI解決方案灾炭,Dart給我們提供了網(wǎng)絡(luò)訪問(wèn)茎芋、文件讀寫(xiě)功能。
同時(shí)Flutter支持第三方包的引用蜈出,并提供了一個(gè)官方平臺(tái)供社區(qū)使用田弥。可以想象铡原,等待Flutter流行后偷厦,會(huì)有更多優(yōu)秀的庫(kù)出現(xiàn)在此平臺(tái)上供開(kāi)發(fā)者使用商叹。
跨平臺(tái)自繪引擎
Flutter與用于構(gòu)建移動(dòng)應(yīng)用程序的其它大多數(shù)框架不同,因?yàn)镕lutter既不使用WebView只泼,也不使用操作系統(tǒng)的原生控件剖笙。 相反,F(xiàn)lutter使用自己的高性能渲染引擎來(lái)繪制widget请唱。這樣不僅可以保證在Android和iOS上UI的一致性弥咪,而且也可以避免對(duì)原生控件依賴(lài)而帶來(lái)的限制及高昂的維護(hù)成本。
Flutter使用Skia作為其2D渲染引擎十绑,Skia是Google的一個(gè)2D圖形處理函數(shù)庫(kù)聚至,包含字型、坐標(biāo)轉(zhuǎn)換本橙,以及點(diǎn)陣圖都有高效能且簡(jiǎn)潔的表現(xiàn)扳躬,Skia是跨平臺(tái)的,并提供了非常友好的API勋功,目前Google Chrome瀏覽器和Android均采用Skia作為其繪圖引擎坦报,值得一提的是库说,由于Android系統(tǒng)已經(jīng)內(nèi)置了Skia狂鞋,所以Flutter在打包APK(Android應(yīng)用安裝包)時(shí),不需要再將Skia打入APK中潜的,但iOS系統(tǒng)并未內(nèi)置Skia骚揍,所以構(gòu)建iPA時(shí),也必須將Skia一起打包啰挪,這也是為什么Flutter APP的Android安裝包比iOS安裝包小的主要原因信不。
高性能
Flutter高性能主要靠?jī)牲c(diǎn)來(lái)保證:
首先,F(xiàn)lutter APP采用Dart語(yǔ)言開(kāi)發(fā)亡呵。Dart在 JIT(即時(shí)編譯)模式下抽活,速度與 JavaScript基本持平。但是 Dart支持 AOT,當(dāng)以 AOT模式運(yùn)行時(shí),JavaScript便追不上了潮剪。速度的提升對(duì)高幀率下的視圖數(shù)據(jù)計(jì)算很有幫助棍丐。
其次,F(xiàn)lutter使用自己的渲染引擎來(lái)繪制UI褥民,布局?jǐn)?shù)據(jù)等由Dart語(yǔ)言直接控制,所以在布局過(guò)程中不需要像RN那樣要在JavaScript和Native之間通信,這在一些滑動(dòng)和拖動(dòng)的場(chǎng)景下具有明顯優(yōu)勢(shì)誉尖,因?yàn)樵诨瑒?dòng)和拖動(dòng)過(guò)程往往都會(huì)引起布局發(fā)生變化,所以JavaScript需要和Native之間不停的同步布局信息铸题,這和在瀏覽器中要JavaScript頻繁操作DOM所帶來(lái)的問(wèn)題是相同的铡恕,都會(huì)帶來(lái)比較可觀的性能開(kāi)銷(xiāo)琢感。
采用Dart語(yǔ)言開(kāi)發(fā)
這是一個(gè)很有意思,但也很有爭(zhēng)議的問(wèn)題探熔,在了解Flutter為什么選擇了 Dart而不是 JavaScript之前我們先來(lái)介紹兩個(gè)概念:JIT和AOT猩谊。
目前,程序主要有兩種運(yùn)行方式:靜態(tài)編譯與動(dòng)態(tài)解釋祭刚。
靜態(tài)編譯的程序在執(zhí)行前全部被翻譯為機(jī)器碼牌捷,通常將這種類(lèi)型稱(chēng)為AOT (Ahead of time)即 “提前編譯”;而解釋執(zhí)行的則是一句一句邊翻譯邊運(yùn)行涡驮,通常將這種類(lèi)型稱(chēng)為JIT(Just-in-time)即“即時(shí)編譯”暗甥。
AOT程序的典型代表是用C/C++開(kāi)發(fā)的應(yīng)用,它們必須在執(zhí)行前編譯成機(jī)器碼捉捅,而JIT的代表則非常多撤防,如JavaScript、python等棒口,事實(shí)上寄月,所有腳本語(yǔ)言都支持JIT模式。但需要注意的是JIT和AOT指的是程序運(yùn)行方式无牵,和編程語(yǔ)言并非強(qiáng)關(guān)聯(lián)的漾肮,有些語(yǔ)言既可以以JIT方式運(yùn)行也可以以AOT方式運(yùn)行,如Java茎毁、Python克懊,它們可以在第一次執(zhí)行時(shí)編譯成中間字節(jié)碼、然后在之后執(zhí)行時(shí)可以直接執(zhí)行字節(jié)碼七蜘。
現(xiàn)在我們看看Flutter為什么選擇Dart語(yǔ)言谭溉?筆者根據(jù)官方解釋以及自己對(duì)Flutter的理解總結(jié)了以下幾條(由于其它跨平臺(tái)框架都將JavaScript作為其開(kāi)發(fā)語(yǔ)言,所以主要將Dart和JavaScript做一個(gè)對(duì)比):
- 開(kāi)發(fā)效率高
Dart運(yùn)行時(shí)和編譯器支持Flutter的兩個(gè)關(guān)鍵特性的組合:
基于JIT的快速開(kāi)發(fā)周期:Flutter在開(kāi)發(fā)階段采用橡卤,采用JIT模式扮念,這樣就避免了每次改動(dòng)都要進(jìn)行編譯,極大的節(jié)省了開(kāi)發(fā)時(shí)間碧库;
基于AOT的發(fā)布包: Flutter在發(fā)布時(shí)可以通過(guò)AOT生成高效的ARM代碼以保證應(yīng)用性能柜与。而JavaScript則不具有這個(gè)能力。
- 高性能
Flutter旨在提供流暢谈为、高保真的的UI體驗(yàn)旅挤。為了實(shí)現(xiàn)這一點(diǎn),F(xiàn)lutter中需要能夠在每個(gè)動(dòng)畫(huà)幀中運(yùn)行大量的代碼伞鲫。這意味著需要一種既能提供高性能的語(yǔ)言粘茄,而不會(huì)出現(xiàn)會(huì)丟幀的周期性暫停,而Dart支持AOT,在這一點(diǎn)上可以做的比JavaScript更好柒瓣。
- 快速內(nèi)存分配
Flutter框架使用函數(shù)式流儒搭,這使得它在很大程度上依賴(lài)于底層的內(nèi)存分配器。因此芙贫,擁有一個(gè)能夠有效地處理瑣碎任務(wù)的內(nèi)存分配器將顯得十分重要搂鲫,在缺乏此功能的語(yǔ)言中,F(xiàn)lutter將無(wú)法有效地工作磺平。當(dāng)然Chrome V8的JavaScript引擎在內(nèi)存分配上也已經(jīng)做的很好魂仍,事實(shí)上Dart開(kāi)發(fā)團(tuán)隊(duì)的很多成員都是來(lái)自Chrome團(tuán)隊(duì)的,所以在內(nèi)存分配上Dart并不能作為超越JavaScript的優(yōu)勢(shì)拣挪,而對(duì)于Flutter來(lái)說(shuō)擦酌,它需要這樣的特性,而Dart也正好滿足而已菠劝。
- 類(lèi)型安全
由于Dart是類(lèi)型安全的語(yǔ)言赊舶,支持靜態(tài)類(lèi)型檢測(cè),所以可以在編譯前發(fā)現(xiàn)一些類(lèi)型的錯(cuò)誤赶诊,并排除潛在問(wèn)題笼平,這一點(diǎn)對(duì)于前端開(kāi)發(fā)者來(lái)說(shuō)可能會(huì)更具有吸引力。與之不同的舔痪,JavaScript是一個(gè)弱類(lèi)型語(yǔ)言寓调,也因此前端社區(qū)出現(xiàn)了很多給JavaScript代碼添加靜態(tài)類(lèi)型檢測(cè)的擴(kuò)展語(yǔ)言和工具,如:微軟的TypeScript以及Facebook的Flow辙喂。相比之下捶牢,Dart本身就支持靜態(tài)類(lèi)型鸠珠,這是它的一個(gè)重要優(yōu)勢(shì)巍耗。
- Dart團(tuán)隊(duì)就在你身邊
Dart、Flutter都隸屬于Google渐排,因此Dart團(tuán)隊(duì)的積極投入炬太,F(xiàn)lutter團(tuán)隊(duì)可以獲得更多、更方便的支持驯耻,正如Flutter官網(wǎng)所述“我們正與Dart社區(qū)進(jìn)行密切合作亲族,以改進(jìn)Dart在Flutter中的使用。例如可缚,當(dāng)我們最初采用Dart時(shí)霎迫,該語(yǔ)言并沒(méi)有提供生成原生二進(jìn)制文件的工具鏈(這對(duì)于實(shí)現(xiàn)可預(yù)測(cè)的高性能具有很大的幫助),但是現(xiàn)在它實(shí)現(xiàn)了帘靡,因?yàn)镈art團(tuán)隊(duì)專(zhuān)門(mén)為Flutter構(gòu)建了它知给。同樣,Dart VM之前已經(jīng)針對(duì)吞吐量進(jìn)行了優(yōu)化,但團(tuán)隊(duì)現(xiàn)在正在優(yōu)化VM的延遲時(shí)間涩赢,這對(duì)于Flutter的工作負(fù)載更為重要戈次。”
在Dart2.0版本有個(gè)特性:可選的 new 和 const 筒扒,即在聲明對(duì)象時(shí)怯邪,關(guān)鍵字 new 和 const 不再是必須的。此特性將極大方便嵌套新建對(duì)象代碼的書(shū)寫(xiě)花墩,如: Flutter 中的層層嵌套的組件
Flutter框架結(jié)構(gòu)
我們先了解下Flutter的結(jié)構(gòu)悬秉,由引擎層(c++)和框架層(Dart)構(gòu)成。
- Flutter Framework
這是一個(gè)純 Dart實(shí)現(xiàn)的 SDK冰蘑,它實(shí)現(xiàn)了一套基礎(chǔ)庫(kù)搂捧,自底向上,我們來(lái)簡(jiǎn)單介紹一下:
底下兩層(Foundation和Animation懂缕、Painting允跑、Gestures)在Google的一些視頻中被合并為一個(gè)dart UI層,對(duì)應(yīng)的是Flutter中的dart:ui包搪柑,它是Flutter引擎暴露的底層UI庫(kù)聋丝,提供動(dòng)畫(huà)、手勢(shì)及繪制能力工碾。
Rendering層弱睦,這一層是一個(gè)抽象的布局層,它依賴(lài)于dart UI層渊额,Rendering層會(huì)構(gòu)建一個(gè)UI樹(shù)况木,當(dāng)UI樹(shù)有變化時(shí),會(huì)計(jì)算出有變化的部分旬迹,然后更新UI樹(shù)火惊,最終將UI樹(shù)繪制到屏幕上,這個(gè)過(guò)程類(lèi)似于React中的虛擬DOM奔垦。Rendering層可以說(shuō)是Flutter UI框架最核心的部分屹耐,它除了確定每個(gè)UI元素的位置、大小之外還要進(jìn)行坐標(biāo)變換椿猎、繪制(調(diào)用底層dart:ui)惶岭。
Widgets層是Flutter提供的的一套基礎(chǔ)組件庫(kù),在基礎(chǔ)組件庫(kù)之上犯眠,F(xiàn)lutter還提供了 Material 和Cupertino兩種視覺(jué)風(fēng)格的組件庫(kù)按灶。而我們Flutter開(kāi)發(fā)的大多數(shù)場(chǎng)景,只是和這兩層打交道筐咧。
- Flutter Engine
這是一個(gè)純 C++實(shí)現(xiàn)的 SDK鸯旁,其中包括了 Skia引擎、Dart運(yùn)行時(shí)、文字排版引擎等羡亩。在代碼調(diào)用 dart:ui庫(kù)時(shí)摩疑,調(diào)用最終會(huì)走到Engine層,然后實(shí)現(xiàn)真正的繪制邏輯畏铆。
Flutter實(shí)戰(zhàn)
下面雷袋,我們來(lái)實(shí)現(xiàn)一個(gè)計(jì)數(shù)器項(xiàng)目,通過(guò)這個(gè)項(xiàng)目辞居,我們可以
- 了解Flutter項(xiàng)目構(gòu)成
- 了解Dart基本語(yǔ)法
前言
Flutter 的核心設(shè)計(jì)思想便是一切即Widget楷怒。在flutter的世界里,包括view瓦灶、view controllers鸠删、layouts等在內(nèi)的概念都建立在Widget之上。widget是Flutter功能的抽象描述贼陶,不同的widget組合成了完整的頁(yè)面刃泡。
所以掌握Flutter的基礎(chǔ)就是學(xué)會(huì)使用widget開(kāi)始。目前Flutter的widget涵蓋了下述類(lèi)型
階段一:基礎(chǔ)功能實(shí)現(xiàn)
UI
代碼
為了閱讀方便碉怔,直接把解釋寫(xiě)在代碼里
import 'package:flutter/material.dart';
程序入口
void main() => runApp(MyApp());
MyApp類(lèi)代表Flutter應(yīng)用烘贴,它繼承了 StatelessWidget類(lèi),這也就意味著應(yīng)用本身也是一個(gè)widget
在Flutter中撮胧,大多數(shù)東西都是widget桨踪,包括對(duì)齊(alignment)、填充(padding)和布局(layout)芹啥。
Flutter在構(gòu)建頁(yè)面時(shí)锻离,會(huì)調(diào)用組件的build方法。
widget的主要工作是提供一個(gè)build()方法來(lái)描述如何構(gòu)建UI界面(通常是通過(guò)組合墓怀、拼裝其它基礎(chǔ)widget)汽纠。
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
MaterialApp 是Material庫(kù)中提供的Flutter APP框架,通過(guò)它可以設(shè)置應(yīng)用的名稱(chēng)捺疼、
主題疏虫、語(yǔ)言、首頁(yè)及路由列表等啤呼。MaterialApp也是一個(gè)widget。
return MaterialApp(
//應(yīng)用首頁(yè)路由
home: MyHomePage(title: "Flutter計(jì)數(shù)器"),
);
}
}
MyHomePage 是應(yīng)用的首頁(yè)呢袱,它繼承自StatefulWidget類(lèi)官扣,表示它是一個(gè)有狀態(tài)的widget(Stateful widget)
class MyHomePage extends StatefulWidget {
final String title;
MyHomePage({Key key, this.title}) : super(key: key);
@override
State<StatefulWidget> createState() => _HomePageState();
}
class _HomePageState extends State<MyHomePage> {
int _counter = 0;
void increaseCount() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
Scaffold 是Material庫(kù)中提供的頁(yè)面腳手架,它包含導(dǎo)航欄和Body以及FloatingActionButton(如果需要的話)羞福。
本書(shū)后面示例中惕蹄,路由默認(rèn)都是通過(guò)Scaffold創(chuàng)建。
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
//水平對(duì)齊
body: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[Text('你點(diǎn)擊了按鈕:$_counter次')],
),
),
floatingActionButton: FloatingActionButton(
onPressed: increaseCount, child: Icon(Icons.add)),
);
}
}
StatefulWidget、StatelessWidget介紹
StatefulWidget和StatelessWidget這兩大類(lèi)卖陵,是我們Flutter開(kāi)發(fā)中經(jīng)常用到的遭顶。用我直觀的感受概括來(lái)說(shuō),StatefulWidget創(chuàng)建的Widget是界面可變的Widget泪蔫,而StatelessWidget創(chuàng)建的Widget則為界面不可變的Widget棒旗。
(Widget可以理解為Flutter提供給我們選擇使用的組件,使用Flutter開(kāi)發(fā)的APP就是用一個(gè)接著一個(gè)的Widget嵌套撩荣、組裝而成铣揉,有點(diǎn)類(lèi)似與HTML語(yǔ)法。)
StatefulWidget在整個(gè)生命周期可以改變很多次餐曹,在StatefulWidget的Widget可以在運(yùn)行的過(guò)程中變換多次進(jìn)行邏輯交互逛拱,以傳達(dá)作者想要展示的信息。例如改變文字台猴、改變顏色朽合、改變大小、改變形狀饱狂、改變圖片等等我們經(jīng)常能夠見(jiàn)到的變化旁舰。StatelessWidget在初始化之后就無(wú)法再改變。想要使用StatelessWidget進(jìn)行邏輯交互嗡官,通過(guò)改變某些變量以改變Widget的樣式是不可行的箭窜,使用前應(yīng)當(dāng)注意。
Dart的字符串衍腥,用單引號(hào)磺樱、雙引號(hào)包裹都可以,$符號(hào)是特殊的符號(hào)后面可直接跟變量名婆咸。如果想要顯示該符號(hào)竹捉,記得轉(zhuǎn)義\$
階段二:路由、本地圖片資源尚骄、第三方包
這一步我們加入了一個(gè)新的頁(yè)面块差,里面顯示一個(gè)隨機(jī)的英語(yǔ)單詞、一個(gè)本地的圖片
準(zhǔn)備工作
1. 添加第三方庫(kù)倔丈,顯示單詞
隨機(jī)英語(yǔ)單詞憨闰,我們?cè)诘谌絺}(cāng)庫(kù)Dart Packages里,找到一個(gè)庫(kù)
^3.1.5 表示:大于等于3.1.5最新的版本
把庫(kù)需五、版本添加到根目錄的pubspec.yaml文件鹉动,同時(shí)點(diǎn)擊右上角的Packages get,這會(huì)將依賴(lài)包安裝到我們的項(xiàng)目
完成后宏邮,我們使用import 'package:english_words/english_words.dart';
引入此包
在根目錄可以看到包的版本泽示、源碼
2. 顯示本地圖片
在根目錄新建imgs的文件夾缸血,插入一張圖片
在根目錄pubspec.yaml里械筛,聲明該圖片
代碼
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
//新頁(yè)面
class NewRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('路由頁(yè)面')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RandomWordsWidget(),
Image(
image: AssetImage('imgs/icon_no_one.png'),
),
Image(
image: NetworkImage(
"https://avatars2.githubusercontent.com/u/20411648?s=460&v=4"),
width: 100,
height: 100,
)
],
),
),
);
}
}
//隨機(jī)文字
class RandomWordsWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final wordPair = WordPair.random();
return Padding(
padding: EdgeInsets.all(8.0),
child: Text(
wordPair.toString(),
style: TextStyle(fontSize: 30, color: Colors.green),
),
);
}
}
接入新頁(yè)面
改造后的主頁(yè)如下
import 'package:flutter/material.dart';
import 'package:flutter_app/NewRoute.dart'; //**1**
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
//應(yīng)用首頁(yè)路由
home: MyHomePage(title: "Flutter計(jì)數(shù)器"),
routes: {"DemoNewPage": (context) => NewRoute()}, //**2**
);
}
}
//主頁(yè)入口
class MyHomePage extends StatefulWidget {
final String title;
MyHomePage({Key key, this.title}) : super(key: key);
@override
State<StatefulWidget> createState() => _HomePageState();
}
//主頁(yè)狀態(tài)渲染
class _HomePageState extends State<MyHomePage> {
int _counter = 0;
void increaseCount() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
//水平對(duì)齊
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('你點(diǎn)擊了按鈕:$_counter次'),
FlatButton(
child: Text('點(diǎn)擊前往新頁(yè)面'),
textColor: Colors.blue,
onPressed: () {
Navigator.pushNamed(context, "DemoNewPage");//**3**
},
)
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: increaseCount, child: Icon(Icons.add)),
);
}
}
打開(kāi)新的頁(yè)面有兩種方式
- 自定義打開(kāi)捎泻,優(yōu)勢(shì)可以傳遞參數(shù)
Navigator.push( context,
new MaterialPageRoute(builder: (context) {
return new NewRoute();
}));
- 路由方式,優(yōu)勢(shì)簡(jiǎn)單易維護(hù)埋哟,參考項(xiàng)目中的寫(xiě)法笆豁,代碼中
1處 引入 路由頁(yè)面
2處 聲明路由
3處 執(zhí)行路由跳轉(zhuǎn)
階段三:文件訪問(wèn)&異步操作、網(wǎng)絡(luò)訪問(wèn)
文件訪問(wèn)&異步操作
Dart的IO庫(kù)包含了文件讀寫(xiě)的相關(guān)類(lèi)定欧,它屬于Dart語(yǔ)法標(biāo)準(zhǔn)的一部分渔呵,所以通過(guò)Dart IO庫(kù),無(wú)論是Dart VM下的腳本還是Flutter砍鸠,都是通過(guò)Dart IO庫(kù)來(lái)操作文件的扩氢,不過(guò)和Dart VM相比,F(xiàn)lutter有一個(gè)重要差異是文件系統(tǒng)路徑不同爷辱,這是因?yàn)镈art VM是運(yùn)行在PC或服務(wù)器操作系統(tǒng)下录豺,而Flutter是運(yùn)行在移動(dòng)操作系統(tǒng)中,他們的文件系統(tǒng)會(huì)有一些差異饭弓。
Android和iOS的應(yīng)用存儲(chǔ)目錄不同双饥,第三方庫(kù)PathProvider 提供了一種平臺(tái)透明的方式來(lái)訪問(wèn)設(shè)備文件系統(tǒng)上的常用位置。該類(lèi)當(dāng)前支持訪問(wèn)兩個(gè)文件系統(tǒng)位置:
-
臨時(shí)目錄: 可以使用
getTemporaryDirectory()
來(lái)獲取臨時(shí)目錄弟断; 系統(tǒng)可隨時(shí)清除的臨時(shí)目錄(緩存)咏花。在iOS上,這對(duì)應(yīng)于NSTemporaryDirectory()
返回的值阀趴。在Android上昏翰,這是getCacheDir()
返回的值。 -
文檔目錄: 可以使用
getApplicationDocumentsDirectory()
來(lái)獲取應(yīng)用程序的文檔目錄刘急,該目錄用于存儲(chǔ)只有自己可以訪問(wèn)的文件棚菊。只有當(dāng)應(yīng)用程序被卸載時(shí),系統(tǒng)才會(huì)清除該目錄叔汁。在iOS上统求,這對(duì)應(yīng)于NSDocumentDirectory
。在Android上据块,這是AppData
目錄码邻。
代碼
我們?cè)谥黜?yè)里的_HomePageState
組件里添加下述的代碼,使其能記住上次按鈕點(diǎn)擊數(shù)量瑰钮。
Future<File> _getLocalFile() async {
String dir = (await getApplicationDocumentsDirectory()).path;
return File('$dir/count.txt');
}
Future<int> _readCount() async {
File file = await _getLocalFile();
String count = await file.readAsString();
if (count.isEmpty) return 0;
return int.parse(count);
}
Future<Null> _saveCount(int count) async {
File file = await _getLocalFile();
file.writeAsString(count.toString());
}
int _counter = 0;
void increaseCount() {
setState(() {
_counter++;
});
_saveCount(_counter);
}
@override
void initState() {
super.initState();
refreshCount();
}
void refreshCount() async {
int count = await _readCount();
setState(() => _counter = count);
}
我們注意到冒滩,文件讀寫(xiě)是耗時(shí)的異步操作,但是我們代碼寫(xiě)的和同步操作一樣浪谴,避免了很多回調(diào)嵌套开睡。
打個(gè)比方,讀取文件數(shù)據(jù)并顯示在UI上苟耻,我們異步的寫(xiě)法是
_readCount().then((int count) {
setState(() {
_counter = count;
});
});
在Dart里篇恒,我們可以這樣寫(xiě)
void refreshCount() async {
int count = await _readCount();
setState(() => _counter = count);
}
Dart類(lèi)庫(kù)有非常多的返回Future或者Stream對(duì)象的函數(shù)。 這些函數(shù)被稱(chēng)為異步函數(shù):它們只會(huì)在設(shè)置好一些耗時(shí)操作之后返回凶杖,比如像 IO操作胁艰。而不是等到這個(gè)操作完成。
async和await關(guān)鍵詞支持了異步編程智蝠,你可以用同步風(fēng)格的寫(xiě)法腾么,創(chuàng)作異步代碼。
我們介紹下Dart異步編程中常用的關(guān)鍵字
Future
Future與JavaScript中的Promise非常相似杈湾,表示一個(gè)異步操作的最終完成(或失斀馐)及其結(jié)果值的表示。簡(jiǎn)單來(lái)說(shuō)漆撞,它就是用于處理異步操作的殴泰,異步處理成功了就執(zhí)行成功的操作,異步處理失敗了就捕獲錯(cuò)誤或者停止后續(xù)操作浮驳。一個(gè)Future只會(huì)對(duì)應(yīng)一個(gè)結(jié)果悍汛,要么成功,要么失敗至会。
由于本身功能較多离咐,這里我們只介紹其常用的API及特性。還有奉件,請(qǐng)記住宵蛀,F(xiàn)uture 的所有API的返回值仍然是一個(gè)Future對(duì)象,所以可以很方便的進(jìn)行鏈?zhǔn)秸{(diào)用瓶蚂。
Future.then
為了方便示例糖埋,在本例中我們使用Future.delayed 創(chuàng)建了一個(gè)延時(shí)任務(wù)(實(shí)際場(chǎng)景會(huì)是一個(gè)真正的耗時(shí)任務(wù),比如一次網(wǎng)絡(luò)請(qǐng)求)窃这,即2秒后返回結(jié)果字符串"hi world!"瞳别,然后我們?cè)趖hen中接收異步結(jié)果并打印結(jié)果,代碼如下:
Future.delayed(new Duration(seconds: 2),(){
return "hi world!";
}).then((data){
print(data);
});
Async/await
回調(diào)地獄(Callback hell)
如果代碼中有大量異步邏輯杭攻,并且出現(xiàn)大量異步任務(wù)依賴(lài)其它異步任務(wù)的結(jié)果時(shí)祟敛,必然會(huì)出現(xiàn)Future.then回調(diào)中套回調(diào)情況。舉個(gè)例子兆解,比如現(xiàn)在有個(gè)需求場(chǎng)景是用戶(hù)先登錄馆铁,登錄成功后會(huì)獲得用戶(hù)Id,然后通過(guò)用戶(hù)Id锅睛,再去請(qǐng)求用戶(hù)個(gè)人信息埠巨,獲取到用戶(hù)個(gè)人信息后历谍,為了使用方便,我們需要將其緩存在本地文件系統(tǒng)辣垒,代碼如下:
//先分別定義各個(gè)異步任務(wù)
Future<String> login(String userName, String pwd){
...
//用戶(hù)登錄
};
Future<String> getUserInfo(String id){
...
//獲取用戶(hù)信息
};
Future saveUserInfo(String userInfo){
...
// 保存用戶(hù)信息
};
基于有三個(gè)彼此有關(guān)聯(lián)的異步任務(wù)望侈,因此,回調(diào)會(huì)嵌套三層
login("1000110002","xl123456").then((id){
//登錄成功后通過(guò)勋桶,id獲取用戶(hù)信息
getUserInfo(id).then((userInfo){
//獲取用戶(hù)信息后保存
saveUserInfo(userInfo).then((){
//保存用戶(hù)信息脱衙,接下來(lái)執(zhí)行其它操作
...
});
});
})
可以感受一下,如果業(yè)務(wù)邏輯中有大量異步依賴(lài)的情況例驹,將會(huì)出現(xiàn)上面這種在回調(diào)里面套回調(diào)的情況捐韩,過(guò)多的嵌套會(huì)導(dǎo)致的代碼可讀性下降以及出錯(cuò)率提高,并且非常難維護(hù)鹃锈,這個(gè)問(wèn)題被形象的稱(chēng)為回調(diào)地獄(Callback hell)荤胁。
回調(diào)地獄問(wèn)題在之前JavaScript中非常突出,也是JavaScript被吐槽最多的點(diǎn)仪召,但隨著ECMAScript6和ECMAScript7標(biāo)準(zhǔn)發(fā)布后寨蹋,這個(gè)問(wèn)題得到了非常好的解決,而解決回調(diào)地獄的兩大神器正是ECMAScript6引入了Promise扔茅,以及ECMAScript7中引入的async/await已旧。
而在Dart中幾乎是完全平移了JavaScript中的這兩者:Future相當(dāng)于Promise,而async/await連名字都沒(méi)改召娜。接下來(lái)我們看看通過(guò)Future和async/await如何消除上面示例中的嵌套問(wèn)題运褪。
使用Future消除callback hell
login("1000110002","xl123456").then((id){
return getUserInfo(id);
}).then((userInfo){
return saveUserInfo(userInfo);
}).then((e){
//執(zhí)行接下來(lái)的操作
}).catchError((e){
//錯(cuò)誤處理
print(e);
});
正如上文所述, “Future 的所有API的返回值仍然是一個(gè)Future對(duì)象玖瘸,所以可以很方便的進(jìn)行鏈?zhǔn)秸{(diào)用” 秸讹,如果在then中返回的是一個(gè)Future的話,該future會(huì)執(zhí)行雅倒,執(zhí)行結(jié)束后會(huì)觸發(fā)后面的then回調(diào)璃诀,這樣依次向下,就避免了層層嵌套蔑匣。
使用async/await消除callback hell
通過(guò)Future回調(diào)中再返回Future的方式雖然能避免層層嵌套劣欢,但是還是有一層回調(diào),有沒(méi)有一種方式能夠讓我們可以像寫(xiě)同步代碼那樣來(lái)執(zhí)行異步任務(wù)而不使用回調(diào)的方式裁良?答案是肯定的凿将,這就要使用async/await了,下面我們先直接看代碼价脾,然后再解釋?zhuān)a如下:
task() async {
try{
String id = await login("alice","******");
String userInfo = await getUserInfo(id);
await saveUserInfo(userInfo);
//執(zhí)行接下來(lái)的操作
} catch(e){
//錯(cuò)誤處理
print(e);
}
}
async用來(lái)表示函數(shù)是異步的牧抵,定義的函數(shù)會(huì)返回一個(gè)Future對(duì)象,可以使用then方法添加回調(diào)函數(shù)侨把。
await 后面是一個(gè)Future犀变,表示等待該異步任務(wù)完成妹孙,異步完成后才會(huì)往下走;await必須出現(xiàn)在 async 函數(shù)內(nèi)部弛作。
可以看到涕蜂,我們通過(guò)async/await將一個(gè)異步流用同步的代碼表示出來(lái)了华匾。
其實(shí)映琳,無(wú)論是在JavaScript還是Dart中,async/await都只是一個(gè)語(yǔ)法糖蜘拉,編譯器或解釋器最終都會(huì)將其轉(zhuǎn)化為一個(gè)Promise(Future)的調(diào)用鏈萨西。
網(wǎng)絡(luò)訪問(wèn)
Dart的IO庫(kù)中提供了Http請(qǐng)求的一些類(lèi),我們可以直接使用HttpClient來(lái)發(fā)起請(qǐng)求旭旭。使用HttpClient發(fā)起請(qǐng)求分為五步:
- 創(chuàng)建一個(gè)HttpClient
HttpClient httpClient = new HttpClient();
- 打開(kāi)Http連接谎脯,設(shè)置請(qǐng)求頭
HttpClientRequest request = await httpClient.getUrl(uri);
這一步可以使用任意Http method,如httpClient.post(...)持寄、httpClient.delete(...)等源梭。如果包含Query參數(shù),可以在構(gòu)建uri時(shí)添加稍味,如:
Uri uri=Uri(scheme: "https", host: "flutterchina.club", queryParameters: {
"xx":"xx",
"yy":"dd"
});
通過(guò)HttpClientRequest可以設(shè)置請(qǐng)求header废麻,如:
request.headers.add("user-agent", "test");
如果是post或put等可以攜帶請(qǐng)求體方法,可以通過(guò)HttpClientRequest對(duì)象發(fā)送request body模庐,如:
String payload="...";
request.add(utf8.encode(payload));
//request.addStream(_inputStream); //可以直接添加輸入流
等待連接服務(wù)器
HttpClientResponse response = await request.close();
這一步完成后,請(qǐng)求信息就已經(jīng)發(fā)送給服務(wù)器了,返回一個(gè)HttpClientResponse對(duì)象际跪,它包含響應(yīng)頭(header)和響應(yīng)流(響應(yīng)體的Stream)春感,接下來(lái)就可以通過(guò)讀取響應(yīng)流來(lái)獲取響應(yīng)內(nèi)容。讀取響應(yīng)內(nèi)容
String responseBody = await response.transform(utf8.decoder).join();
我們通過(guò)讀取響應(yīng)流來(lái)獲取服務(wù)器返回的數(shù)據(jù)疼燥,在讀取時(shí)我們可以設(shè)置編碼格式沧卢,這里是utf8。請(qǐng)求結(jié)束醉者,關(guān)閉HttpClient
httpClient.close();
關(guān)閉client后但狭,通過(guò)該client發(fā)起的所有請(qǐng)求都會(huì)中止。
網(wǎng)絡(luò)請(qǐng)求例子
我們演示一個(gè)網(wǎng)絡(luò)請(qǐng)求例子湃交,訪問(wèn)云教學(xué)登錄頁(yè)熟空,把http返回值呈現(xiàn)在UI上。
代碼
import 'dart:_http';
import 'dart:convert';
import 'package:flutter/material.dart';
//網(wǎng)絡(luò)請(qǐng)求例子
class WebViewShow extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("網(wǎng)絡(luò)訪問(wèn)展示"),
),
body: WebViewQueryWidget(),
);
}
}
class WebViewQueryWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() => _WebViewShowState();
}
class _WebViewShowState extends State<WebViewQueryWidget> {
String _text = '載入中...';
static const String DEFAULT_URL = "https://cas.xueleyun.com/cas/login";
@override
void initState() {
super.initState();
queryText();
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Text(_text),
physics:BouncingScrollPhysics(),
scrollDirection: Axis.vertical,
);
}
void queryText() async {
HttpClient httpClient = new HttpClient();
HttpClientRequest request = await httpClient.getUrl(Uri.parse(DEFAULT_URL));
request.headers.add("user-agent", "flutter test");
request.headers.add("userId", "1030610065");
//等待連接服務(wù)器(會(huì)將請(qǐng)求信息發(fā)送給服務(wù)器)
HttpClientResponse response = await request.close();
_text = await response.transform(utf8.decoder).join();
httpClient.close();
setState(() {});
}
}
階段四:列表渲染息罗、操作
收藏頁(yè)
我們實(shí)現(xiàn)一個(gè)列表,可以點(diǎn)擊收藏單詞才沧。從右上角按鈕跳去結(jié)果頁(yè)查看
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app/CollectResultShow.dart';
//收藏列表
class ListViewShow extends StatefulWidget {
@override
State<StatefulWidget> createState() => _ListViewShowState();
}
class _ListViewShowState extends State<ListViewShow> {
static const int MAX_COUNT = 100;
final TextStyle _biggerFont = TextStyle(fontSize: 18.0);
final Divider _divider = Divider();
List<WordPair> _wordList = List<WordPair>();
Set<WordPair> _likeWordList = Set<WordPair>();
@override
void initState() {
_wordList = generateWordPairs().take(MAX_COUNT).toList();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('單詞記憶表'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.list),
onPressed: goDetail,
)
],
),
body: _buildList(),
);
}
void goDetail() {
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => new CollectResultShow(word: _likeWordList)));
}
//創(chuàng)建單詞列表
Widget _buildList() {
//創(chuàng)建一個(gè)帶分隔符的列表控件
return ListView.separated(
itemCount: MAX_COUNT,
padding: EdgeInsets.all(8),
itemBuilder: (context, i) {
return _buildItem(_wordList[i], i);
},
separatorBuilder: (context, i) => _divider,
);
}
//創(chuàng)建列表元素
Widget _buildItem(WordPair word, int pos) {
final userLike = _likeWordList.contains(word);
int showPos = pos + 1;
return ListTile(
title: Text(
word.asCamelCase,
style: _biggerFont,
),
leading: Text('$showPos:'),
trailing: Icon(
userLike ? Icons.favorite : Icons.favorite_border,
color: userLike ? Colors.red : null,
),
onTap: () => setState(() {
//更新收藏?cái)?shù)據(jù)迈喉,通知列表更新
if (userLike) {
_likeWordList.remove(word);
} else {
_likeWordList.add(word);
}
}),
);
}
}
收藏結(jié)果頁(yè)
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
class CollectResultShow extends StatelessWidget {
final Set<WordPair> word;
CollectResultShow({Key key, @required this.word}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('我收藏的單詞'),
),
body: Padding(
padding: EdgeInsets.all(10),
child: Text(
translateTxt(),
style: TextStyle(color: Colors.black87,fontSize: 18, height: 1.5),
),
));
}
String translateTxt() {
if (word == null || word.length == 0) {
return '暫無(wú)收藏';
}
StringBuffer stringBuffer = StringBuffer();
天然支持lambada表達(dá)式
word.forEach((word) {
stringBuffer.writeln(word);
});
return stringBuffer.toString();
}
}
總結(jié)
通過(guò)上述學(xué)習(xí)绍刮,我們已經(jīng)掌握了Flutter開(kāi)發(fā)一些基本操作。Flutter目前沒(méi)有布局文件挨摸,UI代碼都是手寫(xiě)在dart文件中孩革,因此結(jié)構(gòu)比較復(fù)雜的布局嵌套會(huì)比較深,初學(xué)者需要適應(yīng)下得运。未來(lái)期待會(huì)有UI設(shè)計(jì)器來(lái)提升開(kāi)發(fā)效率膝蜈,并且分離UI和后臺(tái)代碼,易維護(hù)熔掺。
dart語(yǔ)法用熟悉了饱搏,其實(shí)還是挺順手的。
外環(huán)境:業(yè)界阿里巴巴置逻、美團(tuán)推沸、馬蜂窩等產(chǎn)品都已陸續(xù)引入Flutter改造非核心項(xiàng)目。說(shuō)明業(yè)界對(duì)于Flutter的跨平臺(tái)思路比較認(rèn)可券坞,愿意嘗試落地鬓催;另一方面來(lái)說(shuō)Flutter從發(fā)布到現(xiàn)在1.2.1版本,還處在發(fā)展階段恨锚,對(duì)于Flutter逐步接入大型項(xiàng)目的解決方案宇驾,支持的不是很完美,阿里巴巴的解決方案成本較大眠冈,個(gè)人開(kāi)發(fā)者可以保持學(xué)習(xí)觀望飞苇。
本文內(nèi)容基于《Flutter實(shí)戰(zhàn)》,并結(jié)合自己學(xué)習(xí)的感悟蜗顽。感謝這些作者:
《Flutter實(shí)戰(zhàn)》布卡、Flutter社區(qū)、Dart語(yǔ)法了解雇盖、Flutter第三方插件忿等、 RTC Dev Meetup、閑魚(yú)Flutter技術(shù)博客