APP 啟動(dòng)頁(yè)在國(guó)內(nèi)是最常見也是必備的場(chǎng)景,其中啟動(dòng)頁(yè)在 iOS 上算是強(qiáng)制性的要求,其實(shí)配置啟動(dòng)頁(yè)挺簡(jiǎn)單,因?yàn)樵?Flutter 里現(xiàn)在只需要:
創(chuàng)新互聯(lián)建站主要從事網(wǎng)站設(shè)計(jì)制作、成都網(wǎng)站設(shè)計(jì)、網(wǎng)頁(yè)設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)陸川,十余年網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專業(yè),歡迎來(lái)電咨詢建站服務(wù):13518219792
一般只要配置無(wú)誤并且圖片尺寸匹配,基本上就不會(huì)有什么問(wèn)題, 那既然這樣,還有什么需要適配的呢?
事實(shí)上大部分時(shí)候 iOS 是不會(huì)有什么問(wèn)題, 因?yàn)? LaunchScreen.storyboard 的流程本就是 iOS 官方用來(lái)做應(yīng)用啟動(dòng)的過(guò)渡;而對(duì)于 Andorid 而言,直到 12 之前 windowBackground 這種其實(shí)只能算“民間”野路子 ,所以對(duì)于 Andorid 來(lái)說(shuō),這其中就涉及到一個(gè)點(diǎn):
所以下面主要介紹 Flutter 在 Android 上為了這個(gè)啟動(dòng)圖做了哪些騷操作~
在已經(jīng)忘記版本的“遠(yuǎn)古時(shí)期” , FlutterActivity 還在 io.flutter.app.FlutterActivity 路徑下的時(shí)候,那時(shí)啟動(dòng)頁(yè)的邏輯相對(duì)簡(jiǎn)單,主要是通過(guò) App 的 AndroidManifest 文件里是否配置了 SplashScreenUntilFirstFrame 來(lái)進(jìn)行判斷。
在 FlutterActivity 內(nèi)部 FlutterView 被創(chuàng)建的時(shí)候,會(huì)通過(guò)讀取 meta-data 來(lái)判斷是否需要使用 createLaunchView 邏輯 :
是不是很簡(jiǎn)單,那就會(huì)有人疑問(wèn)為什么要這樣做?我直接配置 Activity 的 android:windowBackground 不就完成了嗎?
這就是上面提到的時(shí)間差問(wèn)題, 因?yàn)閱?dòng)頁(yè)到 Flutter 渲染完第一幀畫面中間,會(huì)出現(xiàn)概率出現(xiàn)黑屏的情況,所以才需要這個(gè)行為來(lái)實(shí)現(xiàn)過(guò)渡 。
經(jīng)歷了“遠(yuǎn)古時(shí)代”之后, FlutterActivity 來(lái)到了 io.flutter.embedding.android.FlutterActivity , 在到 2.5 版本發(fā)布之前,F(xiàn)lutter 又針對(duì)這個(gè)啟動(dòng)過(guò)程做了不少調(diào)整和優(yōu)化,其中主要就是 SplashScreen 。
自從開始進(jìn)入 embedding 階段后, FlutterActivity 主要用于實(shí)現(xiàn)了一個(gè)叫 Host 的 interface ,其中和我們有關(guān)系的就是 provideSplashScreen 。
默認(rèn)情況下它會(huì)從 AndroidManifest 文件里是否配置了 SplashScreenDrawable 來(lái)進(jìn)行判斷 。
默認(rèn)情況下當(dāng) AndroidManifest 文件里配置了 SplashScreenDrawable ,那么這個(gè) Drawable 就會(huì)在 FlutterActivity 創(chuàng)建 FlutterView 時(shí)被構(gòu)建成 DrawableSplashScreen 。
DrawableSplashScreen 其實(shí)就是一個(gè)實(shí)現(xiàn)了 io.flutter.embedding.android.SplashScreen 接口的類,它的作用就是:
之后 FlutterActivity 內(nèi)會(huì)創(chuàng)建出 FlutterSplashView ,它是個(gè) FrameLayout。
FlutterSplashView 將 FlutterView 和 ImageView 添加到一起, 然后通過(guò) transitionToFlutter 的方法來(lái)執(zhí)行動(dòng)畫,最后動(dòng)畫結(jié)束時(shí)通過(guò) onTransitionComplete 移除 splashScreenView 。
所以整體邏輯就是:
當(dāng)然這里也是分狀態(tài):
當(dāng)然這個(gè)階段的 FlutterActivity 也可以通過(guò) override provideSplashScreen 方法來(lái)自定義 SplashScreen 。
看到?jīng)]有,做了這么多其實(shí)也就是為了彌補(bǔ)啟動(dòng)頁(yè)和 Flutter 渲染之間, 另外還有一個(gè)優(yōu)化,叫 NormalTheme 。
通過(guò)該配置 NormalTheme ,在 Activity 啟動(dòng)時(shí),就會(huì)首先執(zhí)行 switchLaunchThemeForNormalTheme(); 方法將主題從 LaunchTheme 切換到 NormalTheme 。
大概配置完就是如下樣子, 前面分析那么多其實(shí)就是為了告訴你,如果出現(xiàn)問(wèn)題了,你可以從哪個(gè)地方去找到對(duì)應(yīng)的點(diǎn) 。
講了那么多, Flutter 2.5 之后 provideSplashScreen 和 io.flutter.embedding.android.SplashScreenDrawable 就被棄用了,驚不喜驚喜,意不意外,開不開心 ?
通過(guò)源碼你會(huì)發(fā)現(xiàn),當(dāng)你設(shè)置了 splashScreen 的時(shí)候,會(huì)看到一個(gè) log 警告:
為什么會(huì)棄用?
其實(shí)這個(gè)提議是在 這個(gè) issue 上,然后通過(guò) 這個(gè) pr 完成調(diào)整。
大概意思就是: 原本的設(shè)計(jì)搞復(fù)雜了,用 OnPreDrawListener 更精準(zhǔn),而且不需要為了后面 Andorid12 的啟動(dòng)支持做其他兼容,只需要給 FlutterActivity 等類增加接口開關(guān)即可 。
也就是2.5之后 Flutter 使用 ViewTreeObserver.OnPreDrawListener 來(lái)實(shí)現(xiàn)延遲直到加載出 Flutter 的第一幀。
為什么說(shuō)默認(rèn)情況? 因?yàn)檫@個(gè)行為在 FlutterActivity 里,是在 getRenderMode() == RenderMode.surface 才會(huì)被調(diào)用,而 RenderMode 又和 BackgroundMode 有關(guān)心 。
所以在 2.5 版本后, FlutterActivity 內(nèi)部創(chuàng)建完 FlutterView 后就會(huì)執(zhí)行一個(gè) delayFirstAndroidViewDraw 的操作。
這里主要注意一個(gè)參數(shù): isFlutterUiDisplayed 。
當(dāng) Flutter 被完成展示的時(shí)候, isFlutterUiDisplayed 就會(huì)被設(shè)置為 true。
所以當(dāng) Flutter 沒(méi)有執(zhí)行完成之前, FlutterView 的 onPreDraw 就會(huì)一直返回 false ,這也是 Flutter 2.5 開始之后適配啟動(dòng)頁(yè)的新調(diào)整。
看了這么多,大概可以看到其實(shí)開源項(xiàng)目的推進(jìn)并不是一帆風(fēng)順的,沒(méi)有什么是一開始就是最優(yōu)解,而是經(jīng)過(guò)多方嘗試和交流,才有了現(xiàn)在的版本,事實(shí)上開源項(xiàng)目里,類似這樣的經(jīng)歷數(shù)不勝數(shù):
這篇將會(huì)解決手動(dòng)切換主題以及跟隨手機(jī)切換主題來(lái)更新UI(包括自己創(chuàng)建的Widget)
主題切換有個(gè)問(wèn)題,就是如果是我們自定義或者在 build() 自己創(chuàng)建的部件是不會(huì)隨著系統(tǒng)的主題切換而發(fā)生主題色變化的(實(shí)際測(cè)試中,如果頁(yè)面在頂層(沒(méi)有被push)切換主題并不會(huì)觸發(fā) build() 方法,(push之后的頁(yè)面切換系統(tǒng)主題是可以觸發(fā) build() 的,而且會(huì)頻繁觸發(fā)好多次...),既然無(wú)法通過(guò)重新 build 更新組件的主題色,那么我們?cè)谇袚Q主題后,強(qiáng)制觸發(fā)整個(gè)app的 build() 就可以了)
Flutter中有兩個(gè)常用的狀態(tài)Widget分為StatefulWidget和StatelessWidget,分別為動(dòng)態(tài)視圖和靜態(tài)視圖,視圖的更新需要調(diào)用StatefulWidget的setState方法,這會(huì)遍歷調(diào)用子Widget的build方法。如果一個(gè)頁(yè)面內(nèi)容比較復(fù)雜時(shí),會(huì)包含多個(gè)widget,如果直接調(diào)用setState,會(huì)遍歷所有子Widget的build,這樣會(huì)造成很多不必要的開銷,所以非常有必要了解Flutter中局部刷新的方式:
globalkey唯一定義了某個(gè)element,它使你能夠訪問(wèn)與element相關(guān)聯(lián)的其他對(duì)象,例如buildContext、state等。應(yīng)用場(chǎng)景:跨widget訪問(wèn)狀態(tài)。
例如:可以通過(guò)key.currentState拿到它的狀態(tài)對(duì)象,然后就可以調(diào)用其中的onPressed方法。
Flutter框架內(nèi)部提供了一個(gè)非常小巧精致的組件,專門用于局部組件的刷新。適用于值改動(dòng)的刷新。
實(shí)現(xiàn)原理:在 initState 中對(duì)傳入的可監(jiān)聽對(duì)象進(jìn)行監(jiān)聽,執(zhí)行 _valueChanged 方法,_valueChanged 中進(jìn)行了 setState 來(lái)觸發(fā)當(dāng)前狀態(tài)的刷新。觸發(fā) build 方法,從而觸發(fā) widget.builder 回調(diào),這樣就實(shí)現(xiàn)了局部刷新??梢钥吹竭@里回調(diào)的 child 是組件傳入的 child,所以直接使用,這就是對(duì) child 的優(yōu)化的根源。
可以看到 ValueListenableBuilder 實(shí)現(xiàn)局部刷新的本質(zhì),也是進(jìn)行組件的抽離,讓組件狀態(tài)的改變框定在狀態(tài)內(nèi)部,并通過(guò) builder 回調(diào)控制局部刷新,暴露給用戶使用。
通過(guò)這個(gè)可以創(chuàng)建一個(gè)支持局部刷新的widget樹,比如你可以在StatelessWidget里面刷新某個(gè)布局,但是不需要改變成StatefulWidget;也可以在StatefulWidget中使用做部分刷新而不需要刷新整個(gè)頁(yè)面,這個(gè)刷新是不會(huì)調(diào)用Widget build(BuildContext context)刷新整個(gè)布局樹的。
異步UI更新:
很多時(shí)候我們會(huì)依賴一些異步數(shù)據(jù)來(lái)動(dòng)態(tài)更新UI,比如在打開一個(gè)頁(yè)面時(shí)我們需要先從互聯(lián)網(wǎng)上獲取數(shù)據(jù),在獲取數(shù)據(jù)的過(guò)程中顯示一個(gè)加載框,等獲取到數(shù)據(jù)時(shí)我們?cè)黉秩卷?yè)面;又比如我們想展示Stream(比如文件流、互聯(lián)網(wǎng)數(shù)據(jù)接收流)的進(jìn)度。當(dāng)然StatefulWidget我們完全可以實(shí)現(xiàn)以上功能。但由于在實(shí)際開發(fā)中依賴異步數(shù)據(jù)更新UI的這種場(chǎng)景非常常見,并且當(dāng)StatefulWidget中控件樹較大時(shí),更新一個(gè)屬性導(dǎo)致整個(gè)樹重建,消耗性能,因此Flutter專門提供了FutureBuilder和SteamBuilder兩個(gè)組件來(lái)快速實(shí)現(xiàn)這種功能。
通常情況下,子Widget無(wú)法單獨(dú)感知父Widget的變化,當(dāng)父state變化時(shí),通過(guò)其build重建所有子widget;
InheriteddWidget可以避免這種全局創(chuàng)建,實(shí)現(xiàn)局部子Widget更新。InheritedWidget提供了一種在Widget樹中從上到下傳遞、共享數(shù)據(jù)的方式。Flutter SDK正是通過(guò)InheritedWidget來(lái)共享應(yīng)用主題和Locale等信息。
InheritedWidgetData
TestData
InheritedTest1Page
provider是Google I/O 2019大會(huì)上宣布的現(xiàn)在官方推薦的管理方式,而ChangeNotifierProvider可以說(shuō)是Provider的一種:
yaml文件需要引入provider: ^3.1.0
頂層嵌套ChangeNotifierProvider
創(chuàng)建共享數(shù)據(jù)類DataInfo:
數(shù)據(jù)類需要with ChangeNotifier 以使用 notifyListeners()函數(shù)通知監(jiān)聽者更新界面。
使用Provider.of(context)獲取DataInfo
nextPage:
使用Consumer包住需要使用共享數(shù)據(jù)的Widget
RepaintBoundary就是重繪邊界,用于重繪時(shí)獨(dú)立于父視圖。頁(yè)面需要更新的頁(yè)面結(jié)構(gòu)可以用 RepaintBoundary組件嵌套,flutter 會(huì)將包含的組件獨(dú)立出一層"畫布",去繪制。官方很多組件 外層也包了層 RepaintBoundary 標(biāo)簽。如果你的自定義view比較復(fù)雜,應(yīng)該盡可能的避免重繪。
以上總結(jié)了幾種Flutter的局部刷新的方式,可根據(jù)實(shí)際需要使用不同的方式,最適合的才是最好的。
一年半前玩過(guò)flutter,忘光光...現(xiàn)在是時(shí)候重新拾取了。~
啟動(dòng)頁(yè)一般只放圖片或者加幾行文字。
1、創(chuàng)建好flutter項(xiàng)目之后,在lib文件下面新建launch.dart或xx.dart.
2、在根目錄下新建images文件夾,如已有直接放入圖片
3、flutter_yijiake.iml中加入注入該圖片,并注意空格
4、在根目錄下的test/widget_test.dart中更改默認(rèn)的啟動(dòng)頁(yè)為當(dāng)前的啟動(dòng)頁(yè)路徑
5、最后重新設(shè)置啟動(dòng)時(shí)的頁(yè)面
6、非常簡(jiǎn)單的啟動(dòng)頁(yè)面放logo圖片
值得一說(shuō)的是,flutter框架的UI組件需要已new 組件的形式展開。
在耗時(shí)操作的時(shí)候,一般都要彈出一個(gè)加載框,然后在完成的時(shí)候再把加載框關(guān)掉,在Flutter中可以直接用showDialog()來(lái)彈出一個(gè)對(duì)話框。
這是一個(gè)簡(jiǎn)單的提示對(duì)話框,包含了關(guān)閉按鈕,點(diǎn)擊就能關(guān)閉。但一般的耗時(shí)操作完成,就需要我們自己把dialog關(guān)閉掉。
首先,開啟dialog的時(shí)機(jī)。由于我們需要獲取到BuildContext,所以就得等build()方法走完,這里可以用Future.delayed()來(lái)等創(chuàng)建好BuildContext再進(jìn)行創(chuàng)建,或者用Timer來(lái)延遲操作,我選擇了前者。
其中delayed()在initState()結(jié)尾來(lái)做就行,這里參考網(wǎng)友封裝了一個(gè)LoadingDialog。
那么接下來(lái)要在什么時(shí)機(jī)關(guān)閉呢?
一開始,我理所當(dāng)然的以為,是在異步方法結(jié)束后,去更新界面的時(shí)候關(guān)閉,也就是setState(() {})的時(shí)候,可是不管怎么嘗試,用Navigator.pop()不行,用Navigator.of(context, rootNavigator: true).pop(result)也不行,用FlutterBoost.singleton.close(id)也不行,用FlutterBoost.singleton.closeCurrent()也不行,都會(huì)直接把非Dialog的頁(yè)面也關(guān)閉掉,這讓我百思不得其解,因?yàn)閟howDialog()的本質(zhì)也是新建了一個(gè)Route出來(lái),也就是最頂層的頁(yè)面是彈出的Dialog,可是為什么關(guān)不掉呢。
一番思前想后,把showDialog的邏輯移到和異步邏輯同級(jí),也就是setState(() {})外面,然后把showDialog()自身創(chuàng)建的BuildContext傳進(jìn)去就能正常關(guān)閉了。也就是,在setState(() {})的時(shí)候,其實(shí)用的context還是非Dialog頁(yè)面的,所以關(guān)閉的當(dāng)然就不是Dialog了。
持有Dialog自己的BuildContext,然后在異步以后調(diào)用就行了。
相對(duì)于iOS開發(fā),F(xiàn)lutter的布局更具有靈活性,每個(gè)頁(yè)面設(shè)計(jì)都不一樣,相同頁(yè)面可選擇的布局方式也不一樣,如果單純的說(shuō)應(yīng)該如何去布局,我覺(jué)得不現(xiàn)實(shí),大家可以參考下 Flutter官方的布局教程 。接下來(lái),筆者,通過(guò)項(xiàng)目中的一個(gè)頁(yè)面,來(lái)一步一步的拆解布局的流程。整個(gè)過(guò)程,基本上按照拆解、組件封裝、具體布局這三步來(lái)的。
根據(jù)設(shè)計(jì)圖,可以看出整體可以分成兩部分,上面一部分是系統(tǒng)介紹模塊,下面一部分是真正的登錄內(nèi)容,因?yàn)樯婕暗蒋B加,因此考慮用Stack;
系統(tǒng)介紹模塊部分:整體也是涉及到疊加,考慮用Stack,分為四部分。最底部漸變色背景用一個(gè)contanier,無(wú)須指定位置,全視圖擴(kuò)展;載放logo圖標(biāo)在上一層,用Image。最后兩個(gè)Text同級(jí)放在最上層。Image,Text各用Positioned包裹去指定位置。
登錄內(nèi)容模塊是最外層是一個(gè)Contanier容器,去控制背景色和圓角。然后是一個(gè)Column元素,逐行排列。
第一行為Image,
第二行為Text,
第三行可以看成一個(gè)小Column,分兩塊進(jìn)行布局
第四行可以看成一個(gè)小Column,分兩塊進(jìn)行布局
第五行可以看作一個(gè)TextButton,
第六行可以看作一個(gè)Row,分三塊進(jìn)行布局
通過(guò)上面這樣一步一步的分析后,基本上對(duì)大致的布局有了一個(gè)了解,最外層的控件大致選對(duì)(只要能實(shí)現(xiàn)的話,就是復(fù)雜度以及效率的問(wèn)題),然后一步一步的拆解每一行的元素,如果有重復(fù)的或者覺(jué)得可以封裝出來(lái)的部分,則進(jìn)行下一步。
每一行的拆解,大致也是按照這個(gè)思路來(lái)進(jìn)行,因此筆者在這里就不做講解了。
在做到第三第四行的時(shí)候,發(fā)現(xiàn)這兩個(gè)很相似,而且設(shè)計(jì)到一些交互邏輯,筆者就想對(duì)第三第四行的這種展示進(jìn)行封裝,覺(jué)得今后的布局可能會(huì)用到,因此在這一步,可以先把這一塊兒抽離出一個(gè)控件。利用TextField來(lái)實(shí)現(xiàn)這種輸入操作,具體的實(shí)現(xiàn)筆者不再詳細(xì)的描述了。
經(jīng)過(guò)這一步,整體的規(guī)劃設(shè)計(jì)圖已經(jīng)有了,各個(gè)組件也都有了,接下來(lái)的工作就是組裝了。
具體布局設(shè)計(jì)到一些細(xì)節(jié)的地方,例如整體Column的居中對(duì)齊(crossAxisAlignment)、間隔(Padding或Container包裹,筆者更喜歡用SizedBox占位)、居左居右居中(Align)、點(diǎn)擊事件(GestureDetector)以及圓角(BorderRadius)等一些特殊情況。
像第六行row是放在底部的,就可以在第六行前面增加一個(gè)Spacer()去填充空白區(qū)域。
對(duì)文字顏色大小等,可以用TextStyle直接設(shè)置。
對(duì)于輸入框的刪除按鈕,可以用Offstage這種Flutter特有的控制顯示隱藏的控件。
網(wǎng)站題目:flutter頂層頁(yè),flutter 蒙層
網(wǎng)站路徑:http://www.rwnh.cn/article28/dsiipjp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站導(dǎo)航、網(wǎng)站排名、網(wǎng)站改版、微信公眾號(hào)、品牌網(wǎng)站建設(shè)、云服務(wù)器
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)