Google跨平臺(tái)技術(shù)Flutter介紹

今天我們聊聊跨平臺(tái)解決方案秀存,通過(guò)此文,我們可以了解到

  • 跨平臺(tái)技術(shù)的主流解決方案羽氮,對(duì)比
  • flutter的原理或链、優(yōu)勢(shì)
  • Dart語(yǔ)法
  • 用flutter,搭建一個(gè)基礎(chǔ)app档押,涵蓋各種常見(jiàn)操作

跨平臺(tái)技術(shù)簡(jiǎn)介

“一次編寫(xiě)株扛,多處運(yù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)用。


項(xiàng)目中一些原生跳轉(zhuǎn)查排、訪問(wèn)原生UI信息凳枝,暴露給js
  • 優(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
FaceBook的 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ù)

Html的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ā)中咪惠。


安卓控件樹(shù)

響應(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

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.jsRax 這兩個(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)。

flutter正逐步被眾多廠家采用

這種平臺(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ā)
Dart語(yǔ)言

這是一個(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)成。


framework.png
  • 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)型

Flutter的widget

階段一:基礎(chǔ)功能實(shí)現(xiàn)

UI
flutter計(jì)數(shù)器.png
代碼

為了閱讀方便碉怔,直接把解釋寫(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è)本地的圖片


新頁(yè)面.png

準(zhǔn)備工作

1. 添加第三方庫(kù)倔丈,顯示單詞

隨機(jī)英語(yǔ)單詞憨闰,我們?cè)诘谌絺}(cāng)庫(kù)Dart Packages里,找到一個(gè)庫(kù)
^3.1.5 表示:大于等于3.1.5最新的版本

image.png

把庫(kù)需五、版本添加到根目錄的pubspec.yaml文件鹉动,同時(shí)點(diǎn)擊右上角的Packages get,這會(huì)將依賴(lài)包安裝到我們的項(xiàng)目
image.png

完成后宏邮,我們使用import 'package:english_words/english_words.dart';引入此包
在根目錄可以看到包的版本泽示、源碼

image.png

2. 顯示本地圖片

在根目錄新建imgs的文件夾缸血,插入一張圖片

項(xiàng)目結(jié)構(gòu),lib庫(kù)就是我們的flutter開(kāi)發(fā)庫(kù)

在根目錄pubspec.yaml里械筛,聲明該圖片

image.png

代碼

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)求分為五步:

  1. 創(chuàng)建一個(gè)HttpClient
    HttpClient httpClient = new HttpClient();
  2. 打開(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); //可以直接添加輸入流
  1. 等待連接服務(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)容。

  2. 讀取響應(yīng)內(nèi)容
    String responseBody = await response.transform(utf8.decoder).join();
    我們通過(guò)讀取響應(yīng)流來(lái)獲取服務(wù)器返回的數(shù)據(jù)疼燥,在讀取時(shí)我們可以設(shè)置編碼格式沧卢,這里是utf8。

  3. 請(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上。


訪問(wèn)網(wǎng)頁(yè)搞莺,獲取接口返回值

代碼

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è)
單詞收藏結(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ù)博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末崔挖,一起剝皮案震驚了整個(gè)濱河市贸街,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌狸相,老刑警劉巖薛匪,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異脓鹃,居然都是意外死亡逸尖,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)娇跟,“玉大人岩齿,你說(shuō)我怎么就攤上這事“” “怎么了盹沈?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)吃谣。 經(jīng)常有香客問(wèn)我乞封,道長(zhǎng),這世上最難降的妖魔是什么基协? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任歌亲,我火速辦了婚禮,結(jié)果婚禮上澜驮,老公的妹妹穿的比我還像新娘。我一直安慰自己惋鸥,他們只是感情好杂穷,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著卦绣,像睡著了一般耐量。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上滤港,一...
    開(kāi)封第一講書(shū)人閱讀 52,158評(píng)論 1 308
  • 那天廊蜒,我揣著相機(jī)與錄音,去河邊找鬼溅漾。 笑死山叮,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的添履。 我是一名探鬼主播屁倔,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼暮胧!你這毒婦竟也來(lái)了锐借?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤往衷,失蹤者是張志新(化名)和其女友劉穎钞翔,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體席舍,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡布轿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片驮捍。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡疟呐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出东且,到底是詐尸還是另有隱情启具,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布珊泳,位于F島的核電站鲁冯,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏色查。R本人自食惡果不足惜薯演,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望秧了。 院中可真熱鬧跨扮,春花似錦、人聲如沸验毡。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)晶通。三九已至璃氢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間狮辽,已是汗流浹背一也。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留喉脖,地道東北人椰苟。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像动看,于是被迫代替她去往敵國(guó)和親尊剔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359