一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

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

 doink 2022-03-23

今天我們聊聊跨平臺解決方案,通過此文,我們可以了解到

  • 跨平臺技術(shù)的主流解決方案,對比
  • flutter的原理、優(yōu)勢
  • Dart語法
  • 用flutter,搭建一個基礎app,涵蓋各種常見操作

跨平臺技術(shù)簡介

“一次編寫,多處運行”。

對于跨平臺開發(fā),業(yè)界一直都在努力尋找好的解決方案。時至今日,已經(jīng)有很多成熟的解決方案,根據(jù)其原理,我們主要分為三類:

  • H5+原生(Cordova、Ionic)
  • JavaScript開發(fā)+原生渲染 (React Native、Weex)
  • 自繪UI+原生(Flutter)

接下來我們逐個來看看這三種類型的原理及優(yōu)缺點。

H5+原生

將APP的某些頁面,通過WebView (Android)或WKWebView(IOS)加載網(wǎng)頁地址實現(xiàn)功能。

因為WebView實質(zhì)上就是一個瀏覽器內(nèi)核,其JavaScript依然運行在一個權(quán)限受限的沙箱中,所以對于大多數(shù)系統(tǒng)能力都沒有訪問權(quán)限,如無法訪問文件系統(tǒng)、不能使用藍牙等。對于H5不能實現(xiàn)的功能,都需要原生去做,因此我們會實現(xiàn)一些訪問原生能力的API, 通過JavaScript 暴露給網(wǎng)頁調(diào)用。


項目中一些原生跳轉(zhuǎn)、訪問原生UI信息,暴露給js
  • 優(yōu)點
    接入簡單
  • 缺點
    性能表現(xiàn)一般,在復雜的UI樣式下,不流暢。如果沒做離線緩存,耗費流量,頁面載入速度比原生慢。

JavaScript開發(fā)+原生渲染

此類框架將UI節(jié)點映射成原生控件,并通過JS控制,其最終產(chǎn)品是一個“原生”的移動應用。
比如 React Native,他的控件ScrollView、Image,映射到IOS是 UIScrollView、UIImageView;安卓則是 ScrollView、ImageView。
因此,從使用感受上和用Objective-C或Java編寫的原生應用相近。

React Native
FaceBook的 React Native

React Native (簡稱RN)是Facebook于2015年4月開源的跨平臺移動應用開發(fā)框架,是Facebook早先開源的JS框架 React 在原生移動應用平臺的衍生產(chǎn)物,目前支持iOS和Android兩個平臺。RN使用Javascript語言,類似于HTML的JSX,以及CSS來開發(fā)移動應用,因此熟悉Web前端開發(fā)的技術(shù)人員只需很少的學習就可以進入移動應用開發(fā)領域。

由于RN現(xiàn)在比較火熱,并且Flutter也是受React啟發(fā),很多思想也都是相通的,我們有必要深入了解一下React原理。React是一個響應式的Web框架,我們先了解一下兩個重要的概念:Dom樹響應式編程。

DOM樹

Html的DOM樹

文檔對象模型(Document Object Model,簡稱DOM),是W3C組織推薦的處理可擴展標志語言的標準編程接口,一種獨立于平臺和語言的方式訪問和修改一個文檔的內(nèi)容和結(jié)構(gòu)。
簡單來說,DOM就是文檔樹,與用戶界面控件樹對應,在前端開發(fā)中通常指HTML對應的渲染樹,但廣義的DOM也可以指Android中的XML布局文件對應的控件樹,而術(shù)語DOM操作就是指直接來操作渲染樹(或控件樹), 因此,可以看到其實DOM樹和控件樹是等價的概念,只不過前者用于Web開發(fā)中,而后者常用于原生開發(fā)中。


安卓控件樹

響應式編程
響應式編程是與異步數(shù)據(jù)流交互的一種編程范式。
React中提出一個重要思想:狀態(tài)改變則UI隨之自動改變,而React框架本身就是響應用戶狀態(tài)改變的事件而執(zhí)行重新構(gòu)建用戶界面的工作,這就是典型的響應式編程范式,下面我們總結(jié)一下React中響應式原理:

開發(fā)者只需關(guān)注狀態(tài)轉(zhuǎn)移(數(shù)據(jù)),當狀態(tài)發(fā)生變化,React框架會自動根據(jù)新的狀態(tài)重新構(gòu)建UI。
React框架在接收到用戶狀態(tài)改變通知后,會根據(jù)當前渲染樹,結(jié)合最新的狀態(tài)改變,通過Diff算法,計算出樹中變化的部分,然后只更新變化的部分(DOM操作),從而避免整棵樹重構(gòu),提高性能。
值得注意的是,在第二步中,狀態(tài)變化后React框架并不會立即去計算并渲染DOM樹的變化部分,相反,React會在DOM的基礎上建立一個抽象層,即虛擬DOM樹,對數(shù)據(jù)和狀態(tài)所做的任何改動,都會被自動且高效的同步到虛擬DOM,最后再批量同步到真實DOM中,而不是每次改變都去操作一下DOM。為什么不能每次改變都直接去操作DOM樹?這是因為在瀏覽器中每一次DOM操作都有可能引起瀏覽器的重繪或回流

如果DOM只是外觀風格發(fā)生變化,如顏色變化,會導致瀏覽器重繪界面。如果DOM樹的結(jié)構(gòu)發(fā)生變化,如尺寸、布局、節(jié)點隱藏等導致,瀏覽器就需要回流(及重新排版布局)。而瀏覽器的重繪和回流都是比較昂貴的操作,如果每一次改變都直接對DOM進行操作,這會帶來性能問題,而批量操作只會觸發(fā)一次DOM更新。

上文已經(jīng)提到React Native 是React 在原生移動應用平臺的衍生產(chǎn)物,那兩者主要的區(qū)別是什么呢?其實,主要的區(qū)別在于虛擬DOM映射的對象是什么?React中虛擬DOM最終會映射為瀏覽器DOM樹,而RN中虛擬DOM會通過 JavaScriptCore 映射為原生控件樹。

JavaScriptCore 是一個JavaScript解釋器,它在React Native中主要有兩個作用:

  • 為JavaScript提供運行環(huán)境。
  • 是JavaScript與原生應用之間通信的橋梁,作用和JsBridge一樣,事實上,在iOS中,很多JsBridge的實現(xiàn)都是基于 JavaScriptCore 。

而RN中將虛擬DOM映射為原生控件的過程中分兩步:

  • 布局消息傳遞; 將虛擬DOM布局信息傳遞給原生;
  • 原生根據(jù)布局信息通過對應的原生控件渲染控件樹;

至此,React Native 便實現(xiàn)了跨平臺。 相對于混合應用,由于React Native是原生控件渲染,所以性能會比混合應用中H5好很多,同時React Native是Web開發(fā)技術(shù)棧,也只需維護一份代碼,同樣是跨平臺框架。

Weex
阿里巴巴的WEEX

Weex 致力于使開發(fā)者能基于通用跨平臺的 Web 開發(fā)語言和開發(fā)經(jīng)驗,來構(gòu)建 Android、iOS 和 Web 應用。簡單來說,在集成了 WeexSDK 之后,你可以使用 JavaScript 語言和前端開發(fā)經(jīng)驗來開發(fā)移動應用。

Weex 渲染引擎與 DSL 語法層是分開的,Weex 并不強依賴任何特定的前端框架。目前 Vue.jsRax 這兩個前端框架被廣泛應用于 Weex 頁面開發(fā),同時 Weex 也對這兩個前端框架提供了最完善的支持。Weex 的另一個主要目標是跟進流行的 Web 開發(fā)技術(shù)并將其和原生開發(fā)的技術(shù)結(jié)合,實現(xiàn)開發(fā)效率和運行性能的高度統(tǒng)一。在開發(fā)階段,一個 Weex 頁面就像開發(fā)普通網(wǎng)頁一樣;在運行時,Weex 頁面又充分利用了各種操作系統(tǒng)的原生組件和能力。

JavaScript開發(fā)+原生渲染的方式優(yōu)缺點如下:
優(yōu)點:

  • 采用Web開發(fā)技術(shù)棧,社區(qū)龐大、上手快、開發(fā)成本相對較低。
  • 原生渲染,性能相比H5提高很多。

不足:

  • 渲染時需要JavaScript和原生之間通信,在有些場景如拖動可能會因為通信頻繁導致卡頓。
  • 由于渲染依賴原生控件,不同平臺的控件需要單獨維護,并且當系統(tǒng)更新時,社區(qū)控件可能會滯后;除此之外,其控件系統(tǒng)也會受到原生UI系統(tǒng)限制。
  • JavaScript為腳本語言,執(zhí)行時需要JIT,執(zhí)行效率和AOT代碼有差距。

自繪UI+原生

我們看看最后一種跨平臺技術(shù):自繪UI+原生。這種技術(shù)的思路是,通過在不同平臺實現(xiàn)一個統(tǒng)一接口的渲染引擎來繪制UI,而不依賴系統(tǒng)原生控件,所以可以做到不同平臺UI的一致性。因為技術(shù)難度比較大,目前成熟且流行的代表作就是Google出品的Flutter,也是我們今天所要介紹的。

Flutter是什么

Flutter 是 Google推出并開源的移動應用開發(fā)框架,主打跨平臺、高保真、高性能。
開發(fā)者通過 Dart語言開發(fā) App,一套代碼可以同時運行在 iOS 和 Android平臺。 Flutter提供了豐富的組件、接口,開發(fā)者可以很快地為 Flutter添加 native擴展。同時 Flutter還使用 Native引擎渲染視圖,這無疑能為用戶提供良好的體驗。

flutter正逐步被眾多廠家采用

這種平臺技術(shù)的優(yōu)點如下:

  • 性能高;由于自繪引擎是直接調(diào)用系統(tǒng)API來繪制UI,所以性能和原生控件不相上下。
  • 靈活、組件庫易維護、UI外觀保真度和一致性高;由于UI渲染不依賴原生控件,也就不需要根據(jù)不同平臺的控件單獨維護一套組件庫,所以代碼容易維護。由于組件庫是同一套代碼、同一個渲染引擎,所以在不同平臺,組件顯示外觀可以做到高保真和高一致性;另外,由于不依賴原生控件,也就不會受原生布局系統(tǒng)的限制,這樣布局系統(tǒng)會非常靈活。

不足:

  • 涉及到硬件層功能(定位、緩存文件位置等)的訪問,仍然需要用原生代碼實現(xiàn)接入,期待第三方庫發(fā)展成熟。
  • 熱更新能力,相比 H5、React有所欠缺 ,《閑魚動態(tài)下發(fā)解決方案》

為什么選flutter

不僅僅是跨平臺的UI

除了一套豐富的跨平臺UI解決方案,Dart給我們提供了網(wǎng)絡訪問、文件讀寫功能。
同時Flutter支持第三方包的引用,并提供了一個官方平臺供社區(qū)使用??梢韵胂?,等待Flutter流行后,會有更多優(yōu)秀的庫出現(xiàn)在此平臺上供開發(fā)者使用。

跨平臺自繪引擎

Flutter與用于構(gòu)建移動應用程序的其它大多數(shù)框架不同,因為Flutter既不使用WebView,也不使用操作系統(tǒng)的原生控件。 相反,F(xiàn)lutter使用自己的高性能渲染引擎來繪制widget。這樣不僅可以保證在Android和iOS上UI的一致性,而且也可以避免對原生控件依賴而帶來的限制及高昂的維護成本。

Flutter使用Skia作為其2D渲染引擎,Skia是Google的一個2D圖形處理函數(shù)庫,包含字型、坐標轉(zhuǎn)換,以及點陣圖都有高效能且簡潔的表現(xiàn),Skia是跨平臺的,并提供了非常友好的API,目前Google Chrome瀏覽器和Android均采用Skia作為其繪圖引擎,值得一提的是,由于Android系統(tǒng)已經(jīng)內(nèi)置了Skia,所以Flutter在打包APK(Android應用安裝包)時,不需要再將Skia打入APK中,但iOS系統(tǒng)并未內(nèi)置Skia,所以構(gòu)建iPA時,也必須將Skia一起打包,這也是為什么Flutter APP的Android安裝包比iOS安裝包小的主要原因。

高性能

Flutter高性能主要靠兩點來保證:
首先,F(xiàn)lutter APP采用Dart語言開發(fā)。Dart在 JIT(即時編譯)模式下,速度與 JavaScript基本持平。但是 Dart支持 AOT,當以 AOT模式運行時,JavaScript便追不上了。速度的提升對高幀率下的視圖數(shù)據(jù)計算很有幫助。
其次,F(xiàn)lutter使用自己的渲染引擎來繪制UI,布局數(shù)據(jù)等由Dart語言直接控制,所以在布局過程中不需要像RN那樣要在JavaScript和Native之間通信,這在一些滑動和拖動的場景下具有明顯優(yōu)勢,因為在滑動和拖動過程往往都會引起布局發(fā)生變化,所以JavaScript需要和Native之間不停的同步布局信息,這和在瀏覽器中要JavaScript頻繁操作DOM所帶來的問題是相同的,都會帶來比較可觀的性能開銷。

采用Dart語言開發(fā)
Dart語言

這是一個很有意思,但也很有爭議的問題,在了解Flutter為什么選擇了 Dart而不是 JavaScript之前我們先來介紹兩個概念:JIT和AOT。

目前,程序主要有兩種運行方式:靜態(tài)編譯與動態(tài)解釋。
靜態(tài)編譯的程序在執(zhí)行前全部被翻譯為機器碼,通常將這種類型稱為AOT (Ahead of time)即 “提前編譯”;而解釋執(zhí)行的則是一句一句邊翻譯邊運行,通常將這種類型稱為JIT(Just-in-time)即“即時編譯”。

AOT程序的典型代表是用C/C++開發(fā)的應用,它們必須在執(zhí)行前編譯成機器碼,而JIT的代表則非常多,如JavaScript、python等,事實上,所有腳本語言都支持JIT模式。但需要注意的是JIT和AOT指的是程序運行方式,和編程語言并非強關(guān)聯(lián)的,有些語言既可以以JIT方式運行也可以以AOT方式運行,如Java、Python,它們可以在第一次執(zhí)行時編譯成中間字節(jié)碼、然后在之后執(zhí)行時可以直接執(zhí)行字節(jié)碼。

現(xiàn)在我們看看Flutter為什么選擇Dart語言?筆者根據(jù)官方解釋以及自己對Flutter的理解總結(jié)了以下幾條(由于其它跨平臺框架都將JavaScript作為其開發(fā)語言,所以主要將Dart和JavaScript做一個對比):

  • 開發(fā)效率高

Dart運行時和編譯器支持Flutter的兩個關(guān)鍵特性的組合:

基于JIT的快速開發(fā)周期:Flutter在開發(fā)階段采用,采用JIT模式,這樣就避免了每次改動都要進行編譯,極大的節(jié)省了開發(fā)時間;

基于AOT的發(fā)布包: Flutter在發(fā)布時可以通過AOT生成高效的ARM代碼以保證應用性能。而JavaScript則不具有這個能力。

  • 高性能

Flutter旨在提供流暢、高保真的的UI體驗。為了實現(xiàn)這一點,F(xiàn)lutter中需要能夠在每個動畫幀中運行大量的代碼。這意味著需要一種既能提供高性能的語言,而不會出現(xiàn)會丟幀的周期性暫停,而Dart支持AOT,在這一點上可以做的比JavaScript更好。

  • 快速內(nèi)存分配

Flutter框架使用函數(shù)式流,這使得它在很大程度上依賴于底層的內(nèi)存分配器。因此,擁有一個能夠有效地處理瑣碎任務的內(nèi)存分配器將顯得十分重要,在缺乏此功能的語言中,F(xiàn)lutter將無法有效地工作。當然Chrome V8的JavaScript引擎在內(nèi)存分配上也已經(jīng)做的很好,事實上Dart開發(fā)團隊的很多成員都是來自Chrome團隊的,所以在內(nèi)存分配上Dart并不能作為超越JavaScript的優(yōu)勢,而對于Flutter來說,它需要這樣的特性,而Dart也正好滿足而已。

  • 類型安全

由于Dart是類型安全的語言,支持靜態(tài)類型檢測,所以可以在編譯前發(fā)現(xiàn)一些類型的錯誤,并排除潛在問題,這一點對于前端開發(fā)者來說可能會更具有吸引力。與之不同的,JavaScript是一個弱類型語言,也因此前端社區(qū)出現(xiàn)了很多給JavaScript代碼添加靜態(tài)類型檢測的擴展語言和工具,如:微軟的TypeScript以及Facebook的Flow。相比之下,Dart本身就支持靜態(tài)類型,這是它的一個重要優(yōu)勢。

  • Dart團隊就在你身邊

Dart、Flutter都隸屬于Google,因此Dart團隊的積極投入,F(xiàn)lutter團隊可以獲得更多、更方便的支持,正如Flutter官網(wǎng)所述“我們正與Dart社區(qū)進行密切合作,以改進Dart在Flutter中的使用。例如,當我們最初采用Dart時,該語言并沒有提供生成原生二進制文件的工具鏈(這對于實現(xiàn)可預測的高性能具有很大的幫助),但是現(xiàn)在它實現(xiàn)了,因為Dart團隊專門為Flutter構(gòu)建了它。同樣,Dart VM之前已經(jīng)針對吞吐量進行了優(yōu)化,但團隊現(xiàn)在正在優(yōu)化VM的延遲時間,這對于Flutter的工作負載更為重要?!?/p>

在Dart2.0版本有個特性:可選的 new 和 const ,即在聲明對象時,關(guān)鍵字 new 和 const 不再是必須的。此特性將極大方便嵌套新建對象代碼的書寫,如: Flutter 中的層層嵌套的組件

Flutter框架結(jié)構(gòu)

我們先了解下Flutter的結(jié)構(gòu),由引擎層(c++)和框架層(Dart)構(gòu)成。


framework.png
  • Flutter Framework

這是一個純 Dart實現(xiàn)的 SDK,它實現(xiàn)了一套基礎庫,自底向上,我們來簡單介紹一下:

底下兩層(Foundation和Animation、Painting、Gestures)在Google的一些視頻中被合并為一個dart UI層,對應的是Flutter中的dart:ui包,它是Flutter引擎暴露的底層UI庫,提供動畫、手勢及繪制能力。

Rendering層,這一層是一個抽象的布局層,它依賴于dart UI層,Rendering層會構(gòu)建一個UI樹,當UI樹有變化時,會計算出有變化的部分,然后更新UI樹,最終將UI樹繪制到屏幕上,這個過程類似于React中的虛擬DOM。Rendering層可以說是Flutter UI框架最核心的部分,它除了確定每個UI元素的位置、大小之外還要進行坐標變換、繪制(調(diào)用底層dart:ui)。

Widgets層是Flutter提供的的一套基礎組件庫,在基礎組件庫之上,F(xiàn)lutter還提供了 Material 和Cupertino兩種視覺風格的組件庫。而我們Flutter開發(fā)的大多數(shù)場景,只是和這兩層打交道。

  • Flutter Engine

這是一個純 C++實現(xiàn)的 SDK,其中包括了 Skia引擎、Dart運行時、文字排版引擎等。在代碼調(diào)用 dart:ui庫時,調(diào)用最終會走到Engine層,然后實現(xiàn)真正的繪制邏輯。

Flutter實戰(zhàn)

下面,我們來實現(xiàn)一個計數(shù)器項目,通過這個項目,我們可以

  • 了解Flutter項目構(gòu)成
  • 了解Dart基本語法
前言

Flutter 的核心設計思想便是一切即Widget。在flutter的世界里,包括view、view controllers、layouts等在內(nèi)的概念都建立在Widget之上。widget是Flutter功能的抽象描述,不同的widget組合成了完整的頁面。
所以掌握Flutter的基礎就是學會使用widget開始。目前Flutter的widget涵蓋了下述類型

Flutter的widget

階段一:基礎功能實現(xiàn)

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

為了閱讀方便,直接把解釋寫在代碼里

import 'package:flutter/material.dart';

程序入口
void main() => runApp(MyApp());


MyApp類代表Flutter應用,它繼承了 StatelessWidget類,這也就意味著應用本身也是一個widget
在Flutter中,大多數(shù)東西都是widget,包括對齊(alignment)、填充(padding)和布局(layout)。
Flutter在構(gòu)建頁面時,會調(diào)用組件的build方法。
widget的主要工作是提供一個build()方法來描述如何構(gòu)建UI界面(通常是通過組合、拼裝其它基礎widget)。
class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {


MaterialApp 是Material庫中提供的Flutter APP框架,通過它可以設置應用的名稱、
主題、語言、首頁及路由列表等。MaterialApp也是一個widget。
    return MaterialApp(
      //應用首頁路由
      home: MyHomePage(title: "Flutter計數(shù)器"),
    );
  }
}

MyHomePage 是應用的首頁,它繼承自StatefulWidget類,表示它是一個有狀態(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庫中提供的頁面腳手架,它包含導航欄和Body以及FloatingActionButton(如果需要的話)。 
本書后面示例中,路由默認都是通過Scaffold創(chuàng)建。
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      //水平對齊
      body: Center(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[Text('你點擊了按鈕:$_counter次')],
        ),
      ),
      floatingActionButton: FloatingActionButton(
          onPressed: increaseCount, child: Icon(Icons.add)),
    );
  }
}
StatefulWidget、StatelessWidget介紹

StatefulWidget和StatelessWidget這兩大類,是我們Flutter開發(fā)中經(jīng)常用到的。用我直觀的感受概括來說,StatefulWidget創(chuàng)建的Widget是界面可變的Widget,而StatelessWidget創(chuàng)建的Widget則為界面不可變的Widget。
(Widget可以理解為Flutter提供給我們選擇使用的組件,使用Flutter開發(fā)的APP就是用一個接著一個的Widget嵌套、組裝而成,有點類似與HTML語法。)
StatefulWidget在整個生命周期可以改變很多次,在StatefulWidget的Widget可以在運行的過程中變換多次進行邏輯交互,以傳達作者想要展示的信息。例如改變文字、改變顏色、改變大小、改變形狀、改變圖片等等我們經(jīng)常能夠見到的變化。StatelessWidget在初始化之后就無法再改變。想要使用StatelessWidget進行邏輯交互,通過改變某些變量以改變Widget的樣式是不可行的,使用前應當注意。

Dart的字符串,用單引號、雙引號包裹都可以,$符號是特殊的符號后面可直接跟變量名。如果想要顯示該符號,記得轉(zhuǎn)義\$

階段二:路由、本地圖片資源、第三方包

這一步我們加入了一個新的頁面,里面顯示一個隨機的英語單詞、一個本地的圖片


新頁面.png

準備工作

1. 添加第三方庫,顯示單詞

隨機英語單詞,我們在第三方倉庫Dart Packages里,找到一個庫
^3.1.5 表示:大于等于3.1.5最新的版本

image.png

把庫、版本添加到根目錄的pubspec.yaml文件,同時點擊右上角的Packages get,這會將依賴包安裝到我們的項目
image.png

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

image.png
2. 顯示本地圖片

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

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

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

image.png

代碼

import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';

//新頁面
class NewRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('路由頁面')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            RandomWordsWidget(),
            Image(
              image: AssetImage('imgs/icon_no_one.png'),
            ),
            Image(
              image: NetworkImage(
                  "https://avatars2./u/20411648?s=460&v=4"),
              width: 100,
              height: 100,
            )
          ],
        ),
      ),
    );
  }
}

//隨機文字
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),
      ),
    );
  }
}

接入新頁面

改造后的主頁如下

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(
      //應用首頁路由
      home: MyHomePage(title: "Flutter計數(shù)器"),
      routes: {"DemoNewPage": (context) => NewRoute()}, //**2**
    );
  }
}

//主頁入口
class MyHomePage extends StatefulWidget {
  final String title;

  MyHomePage({Key key, this.title}) : super(key: key);

  @override
  State<StatefulWidget> createState() => _HomePageState();
}

//主頁狀態(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),
      ),
      //水平對齊
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('你點擊了按鈕:$_counter次'),
            FlatButton( 
              child: Text('點擊前往新頁面'),
              textColor: Colors.blue,
              onPressed: () {
                Navigator.pushNamed(context, "DemoNewPage");//**3**
              },
            )
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
          onPressed: increaseCount, child: Icon(Icons.add)),
    );
  }
}

打開新的頁面有兩種方式

  • 自定義打開,優(yōu)勢可以傳遞參數(shù)
  Navigator.push( context,
           new MaterialPageRoute(builder: (context) {
                  return new NewRoute();
             }));
  • 路由方式,優(yōu)勢簡單易維護,參考項目中的寫法,代碼中
    1處 引入 路由頁面
    2處 聲明路由
    3處 執(zhí)行路由跳轉(zhuǎn)

階段三:文件訪問&異步操作、網(wǎng)絡訪問

文件訪問&異步操作

Dart的IO庫包含了文件讀寫的相關(guān)類,它屬于Dart語法標準的一部分,所以通過Dart IO庫,無論是Dart VM下的腳本還是Flutter,都是通過Dart IO庫來操作文件的,不過和Dart VM相比,F(xiàn)lutter有一個重要差異是文件系統(tǒng)路徑不同,這是因為Dart VM是運行在PC或服務器操作系統(tǒng)下,而Flutter是運行在移動操作系統(tǒng)中,他們的文件系統(tǒng)會有一些差異。
Android和iOS的應用存儲目錄不同,第三方庫PathProvider 提供了一種平臺透明的方式來訪問設備文件系統(tǒng)上的常用位置。該類當前支持訪問兩個文件系統(tǒng)位置:

  • 臨時目錄: 可以使用 getTemporaryDirectory() 來獲取臨時目錄; 系統(tǒng)可隨時清除的臨時目錄(緩存)。在iOS上,這對應于NSTemporaryDirectory() 返回的值。在Android上,這是getCacheDir()返回的值。
  • 文檔目錄: 可以使用getApplicationDocumentsDirectory()來獲取應用程序的文檔目錄,該目錄用于存儲只有自己可以訪問的文件。只有當應用程序被卸載時,系統(tǒng)才會清除該目錄。在iOS上,這對應于NSDocumentDirectory。在Android上,這是AppData目錄。

代碼

我們在主頁里的_HomePageState組件里添加下述的代碼,使其能記住上次按鈕點擊數(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);
  }

我們注意到,文件讀寫是耗時的異步操作,但是我們代碼寫的和同步操作一樣,避免了很多回調(diào)嵌套。

打個比方,讀取文件數(shù)據(jù)并顯示在UI上,我們異步的寫法是

    _readCount().then((int count) {
      setState(() {
        _counter = count;
      });
    });

在Dart里,我們可以這樣寫

  void refreshCount() async {
    int count = await _readCount();
    setState(() => _counter = count);
  }

Dart類庫有非常多的返回Future或者Stream對象的函數(shù)。 這些函數(shù)被稱為異步函數(shù):它們只會在設置好一些耗時操作之后返回,比如像 IO操作。而不是等到這個操作完成。
async和await關(guān)鍵詞支持了異步編程,你可以用同步風格的寫法,創(chuàng)作異步代碼。

我們介紹下Dart異步編程中常用的關(guān)鍵字

Future

Future與JavaScript中的Promise非常相似,表示一個異步操作的最終完成(或失敗)及其結(jié)果值的表示。簡單來說,它就是用于處理異步操作的,異步處理成功了就執(zhí)行成功的操作,異步處理失敗了就捕獲錯誤或者停止后續(xù)操作。一個Future只會對應一個結(jié)果,要么成功,要么失敗。

由于本身功能較多,這里我們只介紹其常用的API及特性。還有,請記住,F(xiàn)uture 的所有API的返回值仍然是一個Future對象,所以可以很方便的進行鏈式調(diào)用。

Future.then

為了方便示例,在本例中我們使用Future.delayed 創(chuàng)建了一個延時任務(實際場景會是一個真正的耗時任務,比如一次網(wǎng)絡請求),即2秒后返回結(jié)果字符串"hi world!",然后我們在then中接收異步結(jié)果并打印結(jié)果,代碼如下:

Future.delayed(new Duration(seconds: 2),(){
   return "hi world!";
}).then((data){
   print(data);
});
Async/await

回調(diào)地獄(Callback hell)

如果代碼中有大量異步邏輯,并且出現(xiàn)大量異步任務依賴其它異步任務的結(jié)果時,必然會出現(xiàn)Future.then回調(diào)中套回調(diào)情況。舉個例子,比如現(xiàn)在有個需求場景是用戶先登錄,登錄成功后會獲得用戶Id,然后通過用戶Id,再去請求用戶個人信息,獲取到用戶個人信息后,為了使用方便,我們需要將其緩存在本地文件系統(tǒng),代碼如下:

//先分別定義各個異步任務
Future<String> login(String userName, String pwd){
    ...
    //用戶登錄
};
Future<String> getUserInfo(String id){
    ...
    //獲取用戶信息 
};
Future saveUserInfo(String userInfo){
    ...
    // 保存用戶信息 
};

基于有三個彼此有關(guān)聯(lián)的異步任務,因此,回調(diào)會嵌套三層

login("1000110002","xl123456").then((id){
 //登錄成功后通過,id獲取用戶信息    
 getUserInfo(id).then((userInfo){
    //獲取用戶信息后保存 
    saveUserInfo(userInfo).then((){
       //保存用戶信息,接下來執(zhí)行其它操作
        ...
      });
   });
})

可以感受一下,如果業(yè)務邏輯中有大量異步依賴的情況,將會出現(xiàn)上面這種在回調(diào)里面套回調(diào)的情況,過多的嵌套會導致的代碼可讀性下降以及出錯率提高,并且非常難維護,這個問題被形象的稱為回調(diào)地獄(Callback hell)。
回調(diào)地獄問題在之前JavaScript中非常突出,也是JavaScript被吐槽最多的點,但隨著ECMAScript6和ECMAScript7標準發(fā)布后,這個問題得到了非常好的解決,而解決回調(diào)地獄的兩大神器正是ECMAScript6引入了Promise,以及ECMAScript7中引入的async/await。
而在Dart中幾乎是完全平移了JavaScript中的這兩者:Future相當于Promise,而async/await連名字都沒改。接下來我們看看通過Future和async/await如何消除上面示例中的嵌套問題。

使用Future消除callback hell

login("1000110002","xl123456").then((id){
      return getUserInfo(id);
}).then((userInfo){
    return saveUserInfo(userInfo);
}).then((e){
   //執(zhí)行接下來的操作 
}).catchError((e){
  //錯誤處理  
  print(e);
});

正如上文所述, “Future 的所有API的返回值仍然是一個Future對象,所以可以很方便的進行鏈式調(diào)用” ,如果在then中返回的是一個Future的話,該future會執(zhí)行,執(zhí)行結(jié)束后會觸發(fā)后面的then回調(diào),這樣依次向下,就避免了層層嵌套。

使用async/await消除callback hell

通過Future回調(diào)中再返回Future的方式雖然能避免層層嵌套,但是還是有一層回調(diào),有沒有一種方式能夠讓我們可以像寫同步代碼那樣來執(zhí)行異步任務而不使用回調(diào)的方式?答案是肯定的,這就要使用async/await了,下面我們先直接看代碼,然后再解釋,代碼如下:

task() async {
   try{
    String id = await login("alice","******");
    String userInfo = await getUserInfo(id);
    await saveUserInfo(userInfo);
    //執(zhí)行接下來的操作   
   } catch(e){
    //錯誤處理   
    print(e);   
   }  
}

async用來表示函數(shù)是異步的,定義的函數(shù)會返回一個Future對象,可以使用then方法添加回調(diào)函數(shù)。
await 后面是一個Future,表示等待該異步任務完成,異步完成后才會往下走;await必須出現(xiàn)在 async 函數(shù)內(nèi)部。
可以看到,我們通過async/await將一個異步流用同步的代碼表示出來了。

其實,無論是在JavaScript還是Dart中,async/await都只是一個語法糖,編譯器或解釋器最終都會將其轉(zhuǎn)化為一個Promise(Future)的調(diào)用鏈。

網(wǎng)絡訪問

Dart的IO庫中提供了Http請求的一些類,我們可以直接使用HttpClient來發(fā)起請求。使用HttpClient發(fā)起請求分為五步:

  1. 創(chuàng)建一個HttpClient
    HttpClient httpClient = new HttpClient();
  2. 打開Http連接,設置請求頭
    HttpClientRequest request = await httpClient.getUrl(uri);

這一步可以使用任意Http method,如httpClient.post(...)、httpClient.delete(...)等。如果包含Query參數(shù),可以在構(gòu)建uri時添加,如:

Uri uri=Uri(scheme: "https", host: "flutterchina.club", queryParameters: {
    "xx":"xx",
    "yy":"dd"
  });

通過HttpClientRequest可以設置請求header,如:
request.headers.add("user-agent", "test");
如果是post或put等可以攜帶請求體方法,可以通過HttpClientRequest對象發(fā)送request body,如:

String payload="...";
request.add(utf8.encode(payload)); 
//request.addStream(_inputStream); //可以直接添加輸入流
  1. 等待連接服務器
    HttpClientResponse response = await request.close();
    這一步完成后,請求信息就已經(jīng)發(fā)送給服務器了,返回一個HttpClientResponse對象,它包含響應頭(header)和響應流(響應體的Stream),接下來就可以通過讀取響應流來獲取響應內(nèi)容。

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

  3. 請求結(jié)束,關(guān)閉HttpClient
    httpClient.close();
    關(guān)閉client后,通過該client發(fā)起的所有請求都會中止。

網(wǎng)絡請求例子

我們演示一個網(wǎng)絡請求例子,訪問云教學登錄頁,把http返回值呈現(xiàn)在UI上。


訪問網(wǎng)頁,獲取接口返回值

代碼

import 'dart:_http';
import 'dart:convert';
import 'package:flutter/material.dart';

//網(wǎng)絡請求例子
class WebViewShow extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("網(wǎng)絡訪問展示"),
      ),
      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./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");

    //等待連接服務器(會將請求信息發(fā)送給服務器)
    HttpClientResponse response = await request.close();
    _text = await response.transform(utf8.decoder).join();
    httpClient.close();

    setState(() {});
  }
}

階段四:列表渲染、操作

收藏頁

我們實現(xiàn)一個列表,可以點擊收藏單詞。從右上角按鈕跳去結(jié)果頁查看


單詞列表
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)建一個帶分隔符的列表控件
    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(() {
        //更新收藏數(shù)據(jù),通知列表更新
            if (userLike) {
              _likeWordList.remove(word);
            } else {
              _likeWordList.add(word);
            }
          }),
    );
  }
}

收藏結(jié)果頁
單詞收藏結(jié)果頁
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 '暫無收藏';
    }

    StringBuffer stringBuffer = StringBuffer();

天然支持lambada表達式
    word.forEach((word) {
      stringBuffer.writeln(word);
    });
    return stringBuffer.toString();
  }
}

總結(jié)

通過上述學習,我們已經(jīng)掌握了Flutter開發(fā)一些基本操作。Flutter目前沒有布局文件,UI代碼都是手寫在dart文件中,因此結(jié)構(gòu)比較復雜的布局嵌套會比較深,初學者需要適應下。未來期待會有UI設計器來提升開發(fā)效率,并且分離UI和后臺代碼,易維護。
dart語法用熟悉了,其實還是挺順手的。

外環(huán)境:業(yè)界阿里巴巴、美團、馬蜂窩等產(chǎn)品都已陸續(xù)引入Flutter改造非核心項目。說明業(yè)界對于Flutter的跨平臺思路比較認可,愿意嘗試落地;另一方面來說Flutter從發(fā)布到現(xiàn)在1.2.1版本,還處在發(fā)展階段,對于Flutter逐步接入大型項目的解決方案,支持的不是很完美,阿里巴巴的解決方案成本較大,個人開發(fā)者可以保持學習觀望。

本文內(nèi)容基于《Flutter實戰(zhàn)》,并結(jié)合自己學習的感悟。感謝這些作者:
《Flutter實戰(zhàn)》、Flutter社區(qū)、Dart語法了解Flutter第三方插件、 RTC Dev Meetup、閑魚Flutter技術(shù)博客

    本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    国产目拍亚洲精品区一区| 亚洲国产欧美久久精品| 熟女中文字幕一区二区三区| 国产不卡免费高清视频| 国产传媒免费观看视频| 亚洲中文字幕有码在线观看| 免费在线观看激情小视频| 黄色国产精品一区二区三区| 伊人天堂午夜精品草草网| 日韩成人动画在线观看 | 欧美亚洲综合另类色妞| 欧美激情一区=区三区| 操白丝女孩在线观看免费高清| 欧美小黄片在线一级观看| 国产免费操美女逼视频| 九九视频通过这里有精品| 国产精品九九九一区二区| 亚洲熟女精品一区二区成人| 日本不卡一区视频欧美| 久久永久免费一区二区| 好吊日在线观看免费视频| 国产精品第一香蕉视频| 中文字幕无线码一区欧美| 日本在线视频播放91| 国产大屁股喷水在线观看视频| 亚洲综合精品天堂夜夜| 国产日产欧美精品视频| 熟女体下毛荫荫黑森林自拍| 欧洲亚洲精品自拍偷拍| 97精品人妻一区二区三区麻豆| 精品人妻一区二区四区| 日韩不卡一区二区视频| 日韩一级毛一欧美一级乱| 午夜精品成年人免费视频| 五月天六月激情联盟网| 超薄肉色丝袜脚一区二区| 欧美不雅视频午夜福利| 国产一区二区精品高清免费| 欧美尤物在线观看西比尔| 伊人国产精选免费观看在线视频| 中国美女草逼一级黄片视频|