本文面向 Flutter 初學(xué)者,旨在用易懂的方式帶大家入門。除了 Flutter 代碼,還會介紹到語法、原理、特性等基礎(chǔ)知識。相信本文能幫助你學(xué)習(xí)和理解 Flutter。
創(chuàng)新互聯(lián)服務(wù)項目包括郊區(qū)網(wǎng)站建設(shè)、郊區(qū)網(wǎng)站制作、郊區(qū)網(wǎng)頁制作以及郊區(qū)網(wǎng)絡(luò)營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢、行業(yè)經(jīng)驗、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,郊區(qū)網(wǎng)站推廣取得了明顯的社會效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到郊區(qū)省份的部分城市,未來相信會繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!
我們先看一下目前的一些跨平臺方案,從前端渲染的角度來分類的話,大致可以分為以下幾種方案。
WebView 渲染
這種方案就很好理解,現(xiàn)在很多項目都會嵌入 H5 的頁面。就是用 JavaScript 等前端技術(shù)進(jìn)行開發(fā),在客戶端上用 WebView 來進(jìn)行渲染。微信小程序目前使用的就是這種方案。
它的優(yōu)點很明顯,使用成熟的前端技術(shù)進(jìn)行開發(fā),學(xué)習(xí)成本低,開發(fā)效率高,并且支持動態(tài)發(fā)布代碼。
但缺點也很明顯,在性能體驗上,和原生還是存在較大差距的。
原生控件渲染
既然 WebView 的性能不夠好,于是就有了使用原生控件進(jìn)行渲染的方案。這種方案,同樣也是使用 JavaScript 開發(fā),區(qū)別是它最終是調(diào)用原生控件進(jìn)行渲染的。這種方案的代表是 Facebook 的 React Native。
由于使用原生控件進(jìn)行渲染,性能體驗也會更接近原生。但也只是更接近,和原生還是有差距的,因為它需要頻繁的進(jìn)行 JavaScript 和原生之間的通信,這個通信效率是比較低的。
另外,由于需要適配各個平臺的控件,那就有可能出現(xiàn),系統(tǒng)控件更新了,而框架本身還沒有更新,由此產(chǎn)生了一些問題。換句話說,這種方案是受到原生控件限制的。
繪圖引擎渲染
接下來就是主角了。
在前端,如果完全不使用原生控件,我們可以通過系統(tǒng)的繪圖 API 繪制出一個用戶界面。從這個角度出發(fā),可以在各個平臺使用一個統(tǒng)一接口的繪圖引擎來進(jìn)行界面繪制,這個引擎最終調(diào)用的是系統(tǒng)的 API 繪制的。這樣的話,它的性能可以做到接近原生,并且又不受原生控件的限制,在不同平臺上能夠做到 UI 統(tǒng)一。
Flutter 就是這樣的一個開發(fā)框架。
一個跨平臺 UI 解決方案
Flutter 是由 Google 開發(fā)的,一個跨平臺 UI 解決方案。換句話說,它原則上只管 UI 的問題,如果涉及到平臺本身的一些功能,比如調(diào)用藍(lán)牙、攝像頭,一般還是需要原生代碼去操作。但現(xiàn)在也會有一些第三方庫幫我們解決這些問題。
繪圖引擎 Skia
Flutter 使用 Skia 作為它的繪圖引擎。Skia 已經(jīng)被 Google 收購,目前很多 Google 旗下的產(chǎn)品都是用 Skia 繪制的,包括 Android。
Android 內(nèi)置了 Skia,但 iOS 沒有,所以在打 iOS 安裝包的時候,會把 Skia 一起打進(jìn)去。這就導(dǎo)致了,用同一份 Flutter 代碼打包之后,iOS 的包要比 Android 的包大一些。
開發(fā)語言 Dart
Flutter 使用的開發(fā)語言,叫 Dart。Dart 也是 Google 自家的,它是一門面向?qū)ο蟮恼Z言,從它身上會看到一些其他開發(fā)語言的影子。學(xué)習(xí)起來難度不大的。
前面講跨平臺方案的時候,可以發(fā)現(xiàn)別的方案基本都是用 JavaScript 作為開發(fā)語言的,但為什么 Flutter 不用?就因為 Dart 是谷歌自家的嗎?這個問題先留著,我們后面會提到。
這里部分就簡單點帶過了,具體的搭建流程可以在官網(wǎng)查看:
主要的搭建步驟如下:
下載 Flutter SDK
官網(wǎng)下載地址:
由于在國內(nèi)訪問可能受限,官方為中國開發(fā)者搭建了鏡像:
更新環(huán)境變量
解壓后,將 flutter\bin 的全路徑添加到環(huán)境變量 PATH 中。
安裝開發(fā)工具
理論上,任何文本編輯器都可以用來開發(fā) Flutter 應(yīng)用,但推薦的開發(fā)工具是 Android Studio、IntelliJ 以及 VS Code。因為在這些開發(fā)工具上,可以安裝官方的 Flutter 和 Dart 插件,得到更好的開發(fā)體驗。文章里使用 Android Studio 來演示。
如果你打算開發(fā) iOS 應(yīng)用,則還需要安裝 Xcode。
安裝插件
在開發(fā)工具的插件設(shè)置中,安裝上面說到的 Flutter 和 Dart 插件。Flutter 插件用于支持 Flutter 的運行、調(diào)試、熱重載等功能,而 Dart 插件則提供了代碼的輸入校驗、代碼補(bǔ)全等功能。
萬物始于 Hello World,我們先來創(chuàng)建一個顯示 Hello World 的 Flutter 項目。
在 Android Studio 的歡迎頁面選擇 Start a new Flutter project ,或者通過菜單欄的 File New New Flutter Project ,創(chuàng)建一個新的 Flutter 項目。
創(chuàng)建好的項目里面包含了 android 和 ios 兩個文件夾,它們是標(biāo)準(zhǔn)的 Android 和 iOS 項目。我們的 Flutter 代碼,存放在 lib 文件夾里。項目創(chuàng)建好后,會默認(rèn)帶一個計數(shù)器的示例,我們不管它,把 main.dart 的代碼改成 Hello World:
啟動一個模擬器,或者連上真機(jī),點擊 Run 運行一下,就能看這樣一個界面了:
具體代碼先混個眼熟就好,具體的后面會再講到。
在寫 Flutter 之前,還要先跟大家簡單介紹一下 Dart 的語法。如果你有 Java 或 JavaScript 的開發(fā)經(jīng)驗,以及面向?qū)ο蟮木幊趟枷?,學(xué)起來是很快的。
我們可以在 test 文件夾下新建一個 dart 文件,用來寫測試代碼。
指定類型
var
但和 JavaScript 不同的是,以下代碼在 JavaScript 是不會報錯的,但在 Dart 里會報錯:
Object
如果非要上面這樣寫,那也可以。把 var 換成 Object 就不報錯了:
和 Java 類似,Object 是所有對象的根基類。但是這樣的話,如果想打印一下 num 的字符串長度,是會報錯的:
因為 length 是屬于 String 的,但系統(tǒng)只知道 num 是一個對象,并不知道它是一個 String。
dynamic
如果還是非要這樣寫,那也可以。Dart 有一個特有的關(guān)鍵字 dynamic,把 Object 改成 dynamic 就不報錯了:
我們運行一下這個文件,可以在控制臺看到正確打印出了字符串長度。
函數(shù)
dynamic
在 Dart 里,函數(shù)也是可以不寫返回類型的,不寫的話會被當(dāng)做 dynamic 來處理。這樣的話,函數(shù)的類型就是 return 的類型,如果沒有 return 則是 void 類型。比如可以這樣:
運行之后是能正確打印出字符串長度的。
用于傳參
Dart 里的函數(shù)也是一個對象,所以可以把函數(shù)作為參數(shù)來傳遞,比如:
可選參數(shù)
在 Dart 的函數(shù)傳參里,有一個叫可選參數(shù)的概念,我們以文字控件 Text 為例,在源碼里可以看到 Text 的構(gòu)造函數(shù)是這樣的:
首先,在參數(shù)里有一個 data,它是要顯示的文字內(nèi)容,是一個必填項。而 data 后面的一堆參數(shù),是用一個大括號括起來的,這些參數(shù)就叫做可選參數(shù),意思是這些參數(shù)可傳可不傳。
假如我們要顯示一個比較長的文字,又想限制它最多顯示兩行,就可以這樣來創(chuàng)建一個 Text:
可選參數(shù),在 Flutter 里面用的非常多。
異步
Future
在 Dart 里使用 Future 來處理異步任務(wù),比如我們現(xiàn)在延時一秒打印 666,代碼如下:
Future 的語法和 Promise 非常像。任務(wù)執(zhí)行成功會調(diào)用 then,執(zhí)行失敗會調(diào)用 catchError,而無論成功還是失敗,都會調(diào)用 whenComplete。
async/await
如果你不喜歡上面那種寫法,或者是想把異步轉(zhuǎn)成同步,就可以用 async 和 await 這兩個關(guān)鍵字來轉(zhuǎn)換。
我們把上面的代碼轉(zhuǎn)換一下,寫一個 getString 方法,返回的類型是 Future,它會延時返回一個字符串。在 main 函數(shù)后面加上 async 關(guān)鍵字,在 getString() 前面加上 await,代碼如下:
運行之后可以看到,能正常延時一秒后,把字符串打印出來。這里 getString() 返回的類型是 Future,而 await getString() 則是返回了延時之后返回的字符串。await 要在 async 的函數(shù)里面才能使用。
async 和 await 其實是一個語法糖,它最終也是轉(zhuǎn)換成 Future 調(diào)用鏈的形式執(zhí)行的。
接下來回到 Flutter,F(xiàn)lutter 里最重要的一個概念是 Widget(下面翻譯作控件)。
在原生開發(fā)里面,我們可能會在界面上區(qū)分,這是一個 View,這是一個 Layout,這是一個 View Controller。但在 Flutter 里面,它們?nèi)紝儆谝粋€統(tǒng)一的模型 Widget??梢哉f,在 Flutter 界面里,所有東西都是 Widget。
以前學(xué)面向?qū)ο蟮臅r候,我們都聽過一句話,叫萬物皆對象。我這里套用一下,在 Flutter 里, 萬物皆控件 。
具體有哪些控件,我做了一下簡單的分類。
根控件
所有的控件都屬于 StatefulWidget 或 StatelessWidget 。它們的區(qū)別是,StatefulWidget 擁有狀態(tài) State ,而 StatelessWidget 沒有。
StatefulWidget
當(dāng)一個控件是可變的時候,就要使用 StatefulWidget 來構(gòu)建。StatefulWidget 本身不可變,但它持有的狀態(tài) State 是可變的。
StatelessWidget
當(dāng)一個控件狀態(tài)是固定不可變的時候,就可以使用 StatelessWidget。前面我們寫的 Hello World 就是使用 StatelessWidget。
容器控件
容器類控件一般是將某些屬性或配置,作用在它的子控件上,比如控件所在的寬高、背景、位置等。
常用的容器控件有 Container、Center、Padding 等。
布局控件
布局控件可以類比作原生開發(fā)中的 Layout,通常它會擁有一個 children 的屬性,用于接收一個控件數(shù)組,對這些控件進(jìn)行特定的排版。
常用的布局控件有 Row、Column、Stack、Flex 等。
基礎(chǔ)控件
基礎(chǔ)控件就是常用的文字、按鈕、圖片等控件。
常用的基礎(chǔ)控件有 Text、TextField、Button、Image 等。
功能控件
在 Flutter 里還有一類控件,它們不影響 UI 布局,但帶有一些特定的功能,比如頁面跳轉(zhuǎn)、事件監(jiān)聽、定義主題等。我們把這一類控件稱作功能控件。
常用的功能控件有 Navigator、NotificationListener、Theme 等。
開始寫 Flutter 代碼了。還記不記得,在 Flutter 項目創(chuàng)建之后,是自帶一個計數(shù)器 demo 的,現(xiàn)在我們用自己的代碼實現(xiàn)一遍。代碼修改成如下:
運行之后,就可以看到這樣的界面了:
按鈕每點擊一次,數(shù)字就會加一。下面我們來分析一下這段代碼,看下里面用到的一些 Widget。
StatefulWidget
由于頁面中的數(shù)字是跟隨狀態(tài)變化的,所以該頁面改用 StatefulWidget。StatefulWidget 并不會直接返回一個 Widget,而是返回狀態(tài) State,在 State 里再返回 Widget。
Scaffold
Scaffold 是一個標(biāo)準(zhǔn)的 Material Design 頁面,它包含了標(biāo)題欄、浮動按鈕、側(cè)滑菜單、底部導(dǎo)航欄等配置。我們這里用到了標(biāo)題欄 appBar、頁面內(nèi)容 body、浮動按鈕 floatingActionButton。
AppBar
AppBar 就是標(biāo)題欄,通過查看控件的構(gòu)造方法,我們可以知道它可配置的屬性。
AppBar 的可選參數(shù)除了標(biāo)題 title,還可以配置標(biāo)題前的內(nèi)容 leading,右側(cè)的操作按鈕 anctions,控件垂直高度 elevation 等。我們只傳了 title,其他屬性都用默認(rèn)值。
Center
Center 是一個容器類控件,它的作用就是讓它的子控件居中顯示。
FloatingActionButton
熟悉安卓開發(fā)的應(yīng)該對這個控件比較熟悉,它就是頁面右下角一個特定樣式的 Button,參數(shù)里面的 onPressed 是一個必填項,要傳一個點擊之后的回調(diào)函數(shù)。
根據(jù)這個例子,下面給大家介紹一下 Flutter 兩個比較重要的特性。
點擊 Button 之后,我們把 num 變量加一,并使用 setState 通知狀態(tài)發(fā)生了改變,F(xiàn)lutter 會根據(jù)新的狀態(tài)更新 UI。如果有接觸過小程序開發(fā),setState 就和小程序的 setData 類似。
在 Flutter 里面我們不需要用 set 方法來更新 UI,可變控件是和狀態(tài)綁定的,這就是 Flutter 的響應(yīng)式 UI 編程。
在 Android Q 和 iOS 13 里都加入了暗黑模式,我們也換一個暗黑主題來玩一下。MaterialApp 里有一個 theme 的屬性,我們把它配置一下:
這次改完之后不點 Run 了,我們點一下閃電圖標(biāo) Flutter Hot Reload ,就能看到界面發(fā)生了變化:
這就是 Flutter 的 熱重載 ,在修改完代碼之后,通過熱重載就能馬上在設(shè)備上看到修改結(jié)果,可以很大程度上增加開發(fā)效率。
下面再給大家介紹幾個 Flutter 里的常見操作。
在 Flutter 里,使用 Navigator 來管理頁面跳轉(zhuǎn),比如要跳轉(zhuǎn)到一個 NewPage 可以這樣寫:
進(jìn)棧使用 push,出棧則是 pop。
使用 MaterialPageRoute 會模擬出 Android 上頁面跳轉(zhuǎn)的過場效果。
我們來看看怎么顯示一張本地圖片。
先在根目錄新建一個存放圖片的文件夾,比如叫 images,把圖片 picture.png 放進(jìn)去。
找到根目錄下的 pubspec.yaml 文件,這個便是 Flutter 依賴配置文件,我們需要在這里配置一下剛才的圖片:
這樣,我們就能使用 Image 控件把這張圖片顯示出來了:
和 node 的 npm 以及 Android 的 jcenter 類似,F(xiàn)lutter 也擁有一個公共倉庫 pub.dev。pub.dev 是 Google 官方的 Dart 倉庫,在上面可以找到我們需要的包和插件。
Flutter 本身沒有 Toast,我們來接入一個。在 pub.dev 上搜索后,我決定使用 fluttertoast:
按照說明,在 pubspec.yaml 文件里的 dependencies 下配置:
點一下 Android Studio 右上角的 Packages get 同步之后就可以使用了:
我們上面使用的都是 Material Design 的控件,它們都是在 flutter/material.dart 包里面的。如果要使用 iOS 風(fēng)格的控件,則要用到 flutter/cupertino.dart 包:
iOS 風(fēng)格的控件,基本都以 Cupertino 開頭。我們把計時器頁面里的控件替換一下:
效果如下:
代碼的部分就到這里了,接下來跟大家聊一下編譯方式,編程語言的編譯方式有兩種。
關(guān)于它們孰優(yōu)孰劣,就要看從哪個角度去對比了。JIT 的話,它的一大特點就是支持動態(tài)發(fā)布代碼,也就是支持熱更新。但要是從性能的角度考慮,AOT 會更好,因為在運行的時候不用再進(jìn)行編譯的操作的,運行的效率會更高一些。
回到我們一開始的時候留下的問題,為什么別的跨平臺方案都是用 JavaScript,而 Flutter 要用 Dart 來開發(fā)。JavaScript 的編譯方式是 JIT 的,它不支持 AOT。而 Dart 同時支持 JIT 和 AOT。
Flutter 在開發(fā)階段使用 JIT,讓我們用上了熱重載,增加了開發(fā)效率。在打包時改用 AOT,保證了正式版應(yīng)用的性能。
最后講一下大家比較關(guān)心的一個東西,F(xiàn)lutter 是否支持熱更新?前面說到 Dart 支持 JIT,所以從技術(shù)層面它是支持的。但是目前是不支持的,在官方的計劃文檔中,可以看到:
至于原因,官方在這里進(jìn)行了說明。總的來說,是由于政策的限制,以及出于對性能和安全性的考慮,暫時不支持了。
到這就結(jié)束啦。由于想把 Flutter 基礎(chǔ)在一篇內(nèi)講完,沒有涉及太多細(xì)節(jié),如果要寫 Flutter 代碼還需要深入學(xué)習(xí)。但相信理解之后再學(xué),會輕松很多。
Flutter Dio源碼分析(一)--Dio介紹
Flutter Dio源碼分析(二)--HttpClient、Http、Dio對比
Flutter Dio源碼分析(三)--深度剖析
Flutter Dio源碼分析(四)--封裝
Flutter Dio源碼分析(一)--Dio介紹視頻教程
Flutter Dio源碼分析(二)--HttpClient、Http、Dio對比視頻教程
Flutter Dio源碼分析(三)--深度剖析視頻教程
Flutter Dio源碼分析(四)--封裝視頻教程
github倉庫地址
本文會手把手教你該怎么去封裝一個類庫,平時在我們的工作中都是拿著別人的造好的輪子在使用,這篇文章將帶你怎么去自己造輪子,以后再碰到別的類庫需要對其進(jìn)行封裝的時候提供一個的思路和方法。
在前面的文章中,我們對 Dio 的基本使用、請求庫對比、源碼分析,我們知道 Dio 的使用非常的簡單,那為什么還需要進(jìn)行封裝呢?有兩點如下:
當(dāng)組件庫方法發(fā)生重要改變需要遷移的時候如果有多處地方用到,那么需要對使用到的每個文件都進(jìn)行修改,非常的繁瑣而且很容易出問題。
當(dāng)不需要 Dio 庫的時候,我們可以隨時方便切換到別的網(wǎng)絡(luò)請求庫,當(dāng)然 Dio 目前內(nèi)置支持使用第三方庫的適配器。
因為一個應(yīng)用程序基本都是統(tǒng)一的配置方式,所以我們可以針對 攔截器 、 轉(zhuǎn)換器 、 緩存 、 統(tǒng)一處理錯誤 、 代理配置 、 證書校驗 等多個配置進(jìn)行統(tǒng)一管理。
因為我們的應(yīng)用程序在每個頁面中都會用到網(wǎng)絡(luò)請求,那么如果我們每次請求的時候都去實例化一個 Dio ,無非是增加了系統(tǒng)不必要的開銷,而使用單例模式對象一旦創(chuàng)建每次訪問都是同一個對象,不需要再次實例化該類的對象。
這是通過靜態(tài)變量的私有構(gòu)造器來創(chuàng)建的單例模式
我們對 超時時間 、 響應(yīng)時間 、 BaseUrl 進(jìn)行統(tǒng)一設(shè)置
因為不管是 get() 還是 post() 請求, Dio 內(nèi)部最終都會調(diào)用 request 方法,只是傳入的 method 不一樣,所以我們這里定義一個枚舉類型在一個方法中進(jìn)行處理
我們已經(jīng)把 Restful API 風(fēng)格簡化成了一個方法,通過 DioMethod 來標(biāo)明不同的請求方式。在我們平時開發(fā)的過程中,需要在請求前、響應(yīng)前、錯誤時對某一些接口做特殊的處理,那我們就需要用到攔截器。 Dio 為我們提供了自定義攔截器功能,很容易輕松的實現(xiàn)對請求、響應(yīng)、錯誤時進(jìn)行攔截
我們發(fā)現(xiàn)雖然 Dio 框架已經(jīng)封裝了一個 DioError 類庫,但如果需要對返回的錯誤進(jìn)行統(tǒng)一彈窗處理或者路由跳轉(zhuǎn)等就只能自定義了
在我們發(fā)送請求的時候會碰到幾種情況,比如需要對非open開頭的接口自動加上一些特定的參數(shù),獲取需要在請求頭增加統(tǒng)一的 token
在我們請求接口前可以對響應(yīng)數(shù)據(jù)進(jìn)行一些基礎(chǔ)的處理,比如對響應(yīng)的結(jié)果進(jìn)行自定義封裝,還可以針對單獨的 url 做特殊處理等。
我們看了轉(zhuǎn)換器的介紹,發(fā)現(xiàn)和攔截器的功能差不多,那為什么還要存在轉(zhuǎn)換器,有兩點:
執(zhí)行流程: 請求攔截器 請求轉(zhuǎn)換器 發(fā)起請求 響應(yīng)轉(zhuǎn)換器 響應(yīng)攔截器 最終結(jié)果 。
只會被用于 'PUT'、 'POST'、 'PATCH'方法,因為只有這些方法才可以攜帶請求體(request body)
會被用于所有請求方法的返回數(shù)據(jù)。
在開發(fā)過程中,客戶端和服務(wù)器打交道的時候,往往會用一個 token 來做校驗,因為每個公司處理刷新token的邏輯都不一樣,我這里舉一個簡單的例子
為什么我們需要有取消請求的功能,如果當(dāng)我們的頁面在發(fā)送請求時,用戶主動退出當(dāng)前界面或者app應(yīng)用程序退出的時候數(shù)據(jù)還沒有響應(yīng),那我們就需要取消該網(wǎng)絡(luò)請求,防止不必要的錯誤。
由 服務(wù)器生成 的 一小段文本信息 ,發(fā)送給瀏覽器,瀏覽器把 cookie 以kv形式保存到本地 某個目錄下的文本文件內(nèi),下一次請求同一網(wǎng)站時會把該 cookie 發(fā)送給服務(wù)器。
cookie 的使用需要用到兩個第三方組件 dio_cookie_manager 和 cookie_jar
因為在我們平時的開發(fā)過程中,會碰到一種情況,在進(jìn)行網(wǎng)絡(luò)請求時,我們希望能正常訪問到上次的數(shù)據(jù),對于用戶的體驗比較好,而不是展示一個空白的頁面,該緩存主要是 《Flutter實戰(zhàn)》網(wǎng)絡(luò)接口緩存 提供參考。
我們在程序退出后內(nèi)存緩存將會消失,所以我們用 shared_preferences 進(jìn)行磁盤緩存數(shù)據(jù)。
在我們用flutter進(jìn)行抓包的時候需要配置 Dio 代理。由 DefaultHttpClientAdapter 提供了一個 onHttpClientCreate 回調(diào)來設(shè)置底層 HttpClient 的代理。
用于驗證正在訪問的網(wǎng)站是否真實。提供安全性,因為證書和域名綁定,并且由根證書機(jī)構(gòu)簽名確認(rèn)。
日志打印主要是幫助我們開發(fā)時進(jìn)行輔助排錯
APP 啟動頁在國內(nèi)是最常見也是必備的場景,其中啟動頁在 iOS 上算是強(qiáng)制性的要求,其實配置啟動頁挺簡單,因為在 Flutter 里現(xiàn)在只需要:
一般只要配置無誤并且圖片尺寸匹配,基本上就不會有什么問題, 那既然這樣,還有什么需要適配的呢?
事實上大部分時候 iOS 是不會有什么問題, 因為 LaunchScreen.storyboard 的流程本就是 iOS 官方用來做應(yīng)用啟動的過渡;而對于 Andorid 而言,直到 12 之前 windowBackground 這種其實只能算“民間”野路子 ,所以對于 Andorid 來說,這其中就涉及到一個點:
所以下面主要介紹 Flutter 在 Android 上為了這個啟動圖做了哪些騷操作~
在已經(jīng)忘記版本的“遠(yuǎn)古時期” , FlutterActivity 還在 io.flutter.app.FlutterActivity 路徑下的時候,那時啟動頁的邏輯相對簡單,主要是通過 App 的 AndroidManifest 文件里是否配置了 SplashScreenUntilFirstFrame 來進(jìn)行判斷。
在 FlutterActivity 內(nèi)部 FlutterView 被創(chuàng)建的時候,會通過讀取 meta-data 來判斷是否需要使用 createLaunchView 邏輯 :
是不是很簡單,那就會有人疑問為什么要這樣做?我直接配置 Activity 的 android:windowBackground 不就完成了嗎?
這就是上面提到的時間差問題, 因為啟動頁到 Flutter 渲染完第一幀畫面中間,會出現(xiàn)概率出現(xiàn)黑屏的情況,所以才需要這個行為來實現(xiàn)過渡 。
經(jīng)歷了“遠(yuǎn)古時代”之后, FlutterActivity 來到了 io.flutter.embedding.android.FlutterActivity , 在到 2.5 版本發(fā)布之前,F(xiàn)lutter 又針對這個啟動過程做了不少調(diào)整和優(yōu)化,其中主要就是 SplashScreen 。
自從開始進(jìn)入 embedding 階段后, FlutterActivity 主要用于實現(xiàn)了一個叫 Host 的 interface ,其中和我們有關(guān)系的就是 provideSplashScreen 。
默認(rèn)情況下它會從 AndroidManifest 文件里是否配置了 SplashScreenDrawable 來進(jìn)行判斷 。
默認(rèn)情況下當(dāng) AndroidManifest 文件里配置了 SplashScreenDrawable ,那么這個 Drawable 就會在 FlutterActivity 創(chuàng)建 FlutterView 時被構(gòu)建成 DrawableSplashScreen 。
DrawableSplashScreen 其實就是一個實現(xiàn)了 io.flutter.embedding.android.SplashScreen 接口的類,它的作用就是:
之后 FlutterActivity 內(nèi)會創(chuàng)建出 FlutterSplashView ,它是個 FrameLayout。
FlutterSplashView 將 FlutterView 和 ImageView 添加到一起, 然后通過 transitionToFlutter 的方法來執(zhí)行動畫,最后動畫結(jié)束時通過 onTransitionComplete 移除 splashScreenView 。
所以整體邏輯就是:
當(dāng)然這里也是分狀態(tài):
當(dāng)然這個階段的 FlutterActivity 也可以通過 override provideSplashScreen 方法來自定義 SplashScreen 。
看到?jīng)]有,做了這么多其實也就是為了彌補(bǔ)啟動頁和 Flutter 渲染之間, 另外還有一個優(yōu)化,叫 NormalTheme 。
通過該配置 NormalTheme ,在 Activity 啟動時,就會首先執(zhí)行 switchLaunchThemeForNormalTheme(); 方法將主題從 LaunchTheme 切換到 NormalTheme 。
大概配置完就是如下樣子, 前面分析那么多其實就是為了告訴你,如果出現(xiàn)問題了,你可以從哪個地方去找到對應(yīng)的點 。
講了那么多, Flutter 2.5 之后 provideSplashScreen 和 io.flutter.embedding.android.SplashScreenDrawable 就被棄用了,驚不喜驚喜,意不意外,開不開心 ?
通過源碼你會發(fā)現(xiàn),當(dāng)你設(shè)置了 splashScreen 的時候,會看到一個 log 警告:
為什么會棄用?
其實這個提議是在 這個 issue 上,然后通過 這個 pr 完成調(diào)整。
大概意思就是: 原本的設(shè)計搞復(fù)雜了,用 OnPreDrawListener 更精準(zhǔn),而且不需要為了后面 Andorid12 的啟動支持做其他兼容,只需要給 FlutterActivity 等類增加接口開關(guān)即可 。
也就是2.5之后 Flutter 使用 ViewTreeObserver.OnPreDrawListener 來實現(xiàn)延遲直到加載出 Flutter 的第一幀。
為什么說默認(rèn)情況? 因為這個行為在 FlutterActivity 里,是在 getRenderMode() == RenderMode.surface 才會被調(diào)用,而 RenderMode 又和 BackgroundMode 有關(guān)心 。
所以在 2.5 版本后, FlutterActivity 內(nèi)部創(chuàng)建完 FlutterView 后就會執(zhí)行一個 delayFirstAndroidViewDraw 的操作。
這里主要注意一個參數(shù): isFlutterUiDisplayed 。
當(dāng) Flutter 被完成展示的時候, isFlutterUiDisplayed 就會被設(shè)置為 true。
所以當(dāng) Flutter 沒有執(zhí)行完成之前, FlutterView 的 onPreDraw 就會一直返回 false ,這也是 Flutter 2.5 開始之后適配啟動頁的新調(diào)整。
看了這么多,大概可以看到其實開源項目的推進(jìn)并不是一帆風(fēng)順的,沒有什么是一開始就是最優(yōu)解,而是經(jīng)過多方嘗試和交流,才有了現(xiàn)在的版本,事實上開源項目里,類似這樣的經(jīng)歷數(shù)不勝數(shù):
最近在研究flutter如何繞過原生端直接調(diào)用c++的函數(shù)庫,在flutter2版本官方提供了比較穩(wěn)定的FFI(外部功能接口),下面是最基本的用法:
1.創(chuàng)建一個用于flutter調(diào)用的c++文件,文件名為cout.c++,文件里面有一個count方法
2.將寫好的c++文件放在IOS/Classes文件夾下(至于為什么要放在這里,網(wǎng)上很多博客都是固定放在這個位置,至于能不能放在其他位置,以后再研究)。
3.在android目錄下創(chuàng)建CMakeLists.txt文件,把所有需要調(diào)用的c++文件通過add_library導(dǎo)入,這里導(dǎo)入剛才寫好的cout.c++。
4.在android/app目錄下的build.gradle文件里配置CMakelists。
5.經(jīng)過上面4步所需要調(diào)用的c++庫已經(jīng)導(dǎo)入進(jìn)我們的flutter項目了,接下來就是在flutter端調(diào)用c++庫里面的函數(shù)。
輸出結(jié)果:
花費大概一個多月的時間,這個完全由Flutter編寫的Todo-List項目總算初步完成了!現(xiàn)在,它終于要被開源出來了。
在開始介紹之前,先來簡單的看一下真機(jī)運行效果吧
下面,針對使用者和開發(fā)者,我將來分別介紹一次
app中,可以在主題切換界面選擇各種主題顏色進(jìn)行切換,app自帶六個默認(rèn)主題,這些都是我經(jīng)過多次嘗試所挑選出來的顏色搭配。同時你也可以選擇自定義主題顏色
在app中,每項任務(wù)都會帶有一個圖標(biāo),而app提供了所有 Flutter 自帶的 Material design 風(fēng)格的圖標(biāo)。這些圖標(biāo),你同樣可以進(jìn)行任意顏色的自定義
在app中,有多項其他的操作是你可以進(jìn)行自定義的
比如說主頁測滑欄的頭部展示內(nèi)容。當(dāng)然,還有一些其他的操作,就由你去自行體驗了
當(dāng)你完成了一項任務(wù)后,這個任務(wù)就會從主頁轉(zhuǎn)移到完成列表頁面,在這里你可以看到任務(wù)的一些額外信息
那么,對于使用者的介紹就到這里結(jié)束
下面就是為廣大開發(fā)者們介紹的時間了!
各位開發(fā)者們請扶好你們的秀發(fā),下面就我來帶領(lǐng)各位參觀參觀這個項目的內(nèi)部構(gòu)造
項目中使用了一些非常優(yōu)秀的第三方庫,也特別感謝這些開發(fā)者們,讓我的發(fā)量保持健康
下面就是這些控件的信息
項目使用的狀態(tài)管理框架是 codeProvider/code ,而整個項目的架構(gòu)如下
看起來是不是和 Android 中的 MVP 模式很像呢?其實都差不多的,只是名字略有不同罷了,你也可以就把上面的模式當(dāng)作是 MVP 模式。
Flutter 可以說是特別適合這種架構(gòu)模式的,因為視圖跟隨數(shù)據(jù)而變化,你基本上不用去關(guān)心View,只要去對數(shù)據(jù)進(jìn)行操作就好了。
項目目錄結(jié)構(gòu)如下:
先說明一下除了 lib 外的其他目錄:
然后是 lib 目錄
項目創(chuàng)建于6月21日,到如今發(fā)布1.0.0版本花了三十多天的時間,雖然我做過很多測試,解決了很多bug,但是時間確實不充裕。紕漏也會在所難免
所以如果使用過程中遇到什么問題,或者對于項目有什么好的建議,歡迎在app中的反饋界面提出來,也可以在下面留下評論,又或者在github上提issue。
項目的UI設(shè)計與后臺接口均由我獨自一人完成,所以這也是為什么時間不夠的原因。不過目前項目中只是包含了兩個自己寫的接口,后續(xù)計劃中應(yīng)該會加入登錄功能,到時候會有很多接口操作。
若有興趣,請持續(xù)關(guān)注!
如果你覺得這個app不錯,或者這個項目有幫助到你,不妨給這個項目一個Star吧。項目后面也會持續(xù)保持更新和維護(hù)!
ToDo-List
UI項目地址
密碼:Aczh
首先查看入口函數(shù):
類MyApp:
MyHomePage:
state:
build:
此demo頁面涉及到兩個組件:圖片和icon。在這里做一個簡單的介紹,更詳細(xì)的學(xué)習(xí)請參考flutter官網(wǎng)和相關(guān)書籍
在flutter中,我們可以通過Image組件來加載并顯示圖片,Image的數(shù)據(jù)源可以是asset、文件、內(nèi)存以及網(wǎng)絡(luò)。
ImageProvider 是一個抽象類,主要定義了圖片數(shù)據(jù)獲取的接口 load() ,從不同的數(shù)據(jù)源獲取圖片需要實現(xiàn)不同的 ImageProvider ,如 AssetImage 是實現(xiàn)了從Asset中加載圖片的ImageProvider,而 NetworkImage 實現(xiàn)了從網(wǎng)絡(luò)加載圖片的ImageProvider。
Image也提供了一個快捷的構(gòu)造函數(shù) Image.asset 用于從asset中加載、顯示圖片:
Image也提供了一個快捷的構(gòu)造函數(shù) Image.network 用于從網(wǎng)絡(luò)加載、顯示圖片:
Flutter中,可以像web開發(fā)一樣使用iconfont,iconfont也即"字體圖標(biāo)",它是將圖標(biāo)做成字體文件,然后通過指定不同的字符而顯示不同的圖片。
加號為圖片組件,減一為icon組件。點擊加號,數(shù)字加1;點擊-1,數(shù)字減少1。